Skip to content

Commit e473c00

Browse files
committed
Sanitize column names for named_tuple_factory
Fixes PYTHON-31
1 parent 321a519 commit e473c00

2 files changed

Lines changed: 28 additions & 26 deletions

File tree

cassandra/decoder.py

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,17 @@
1-
# Licensed to the Apache Software Foundation (ASF) under one
2-
# or more contributor license agreements. See the NOTICE file
3-
# distributed with this work for additional information
4-
# regarding copyright ownership. The ASF licenses this file
5-
# to you under the Apache License, Version 2.0 (the
6-
# "License"); you may not use this file except in compliance
7-
# with the License. You may obtain a copy of the License at
8-
#
9-
# http://www.apache.org/licenses/LICENSE-2.0
10-
#
11-
# Unless required by applicable law or agreed to in writing, software
12-
# distributed under the License is distributed on an "AS IS" BASIS,
13-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14-
# See the License for the specific language governing permissions and
15-
# limitations under the License.
16-
171
from binascii import hexlify
182
from collections import namedtuple
19-
try:
20-
from collections import OrderedDict
21-
except ImportError: # Python <2.7
22-
from cassandra.util import OrderedDict # NOQA
23-
243
import datetime
254
import logging
5+
import re
266
import socket
277
import types
288
from uuid import UUID
9+
10+
try:
11+
from collections import OrderedDict
12+
except ImportError: # Python <2.7
13+
from cassandra.util import OrderedDict # NOQA
14+
2915
try:
3016
from cStringIO import StringIO
3117
except ImportError:
@@ -60,12 +46,20 @@ class InternalError(Exception):
6046
HEADER_DIRECTION_MASK = 0x80
6147

6248

49+
NON_ALPHA_REGEX = re.compile('\W')
50+
END_UNDERSCORE_REGEX = re.compile('^_*(\w*[a-zA-Z0-9])_*$')
51+
52+
53+
def _clean_column_name(name):
54+
return END_UNDERSCORE_REGEX.sub("\g<1>", NON_ALPHA_REGEX.sub("_", name))
55+
56+
6357
def tuple_factory(colnames, rows):
6458
return rows
6559

6660

6761
def named_tuple_factory(colnames, rows):
68-
Row = namedtuple('Row', colnames)
62+
Row = namedtuple('Row', map(_clean_column_name, colnames))
6963
return [Row(*row) for row in rows]
7064

7165

tests/unit/test_types.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import unittest
22
import datetime
33
import cassandra
4-
from cassandra.cqltypes import CassandraType, BooleanType, lookup_casstype_simple, lookup_casstype, \
5-
AsciiType, LongType, DecimalType, DoubleType, FloatType, Int32Type, UTF8Type, IntegerType, SetType, cql_typename
6-
7-
from cassandra.cluster import Cluster
4+
from cassandra.cqltypes import (BooleanType, lookup_casstype_simple, lookup_casstype,
5+
LongType, DecimalType, SetType, cql_typename)
6+
from cassandra.decoder import named_tuple_factory
87

98

109
class TypeTests(unittest.TestCase):
@@ -105,3 +104,12 @@ def test_cql_typename(self):
105104

106105
self.assertEqual(cql_typename('DateType'), 'timestamp')
107106
self.assertEqual(cql_typename('org.apache.cassandra.db.marshal.ListType(IntegerType)'), 'list<varint>')
107+
108+
def test_named_tuple_colname_substitution(self):
109+
colnames = ("func(abc)", "[applied]", "func(func(abc))", "foo_bar")
110+
rows = [(1, 2, 3, 4)]
111+
result = named_tuple_factory(colnames, rows)[0]
112+
self.assertEqual(result[0], result.func_abc)
113+
self.assertEqual(result[1], result.applied)
114+
self.assertEqual(result[2], result.func_func_abc)
115+
self.assertEqual(result[3], result.foo_bar)

0 commit comments

Comments
 (0)