Skip to content

Commit f7fac97

Browse files
committed
cqle: backport cqlengine to support Python 2.6
https://datastax-oss.atlassian.net/browse/PYTHON-288 Add Python 2.6 compliancy: - Remove dict/set comprehensions. - Use cassandra's OrderedDict. - Replace str.format() implicit placeholders ('{}') with explicit ones ('{0} {1} ...'). - Implement cqlengine.functions.get_total_seconds() as alternative to timedelta.total_seconds(). cqlengine integration tests remain to be backported.
1 parent c050a96 commit f7fac97

10 files changed

Lines changed: 145 additions & 136 deletions

File tree

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ A modern, `feature-rich <https://github.com/datastax/python-driver#features>`_ a
88

99
The driver supports Python 2.6, 2.7, 3.3, and 3.4*.
1010

11-
\* cqlengine component presently supports Python 2.7+
11+
\* cqlengine component presently supports Python 2.6+
1212

1313
Feedback Requested
1414
------------------

cassandra/cqlengine/columns.py

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from cassandra import util
2222
from cassandra.cqltypes import DateType, SimpleDateType
2323
from cassandra.cqlengine import ValidationError
24+
from cassandra.cqlengine.functions import get_total_seconds
2425

2526
log = logging.getLogger(__name__)
2627

@@ -186,7 +187,7 @@ def validate(self, value):
186187
"""
187188
if value is None:
188189
if self.required:
189-
raise ValidationError('{} - None values are not allowed'.format(self.column_name or self.db_field))
190+
raise ValidationError('{0} - None values are not allowed'.format(self.column_name or self.db_field))
190191
return value
191192

192193
def to_python(self, value):
@@ -228,7 +229,7 @@ def get_column_def(self):
228229
Returns a column definition for CQL table definition
229230
"""
230231
static = "static" if self.static else ""
231-
return '{} {} {}'.format(self.cql, self.db_type, static)
232+
return '{0} {1} {2}'.format(self.cql, self.db_type, static)
232233

233234
# TODO: make columns use cqltypes under the hood
234235
# until then, this bridges the gap in using types along with cassandra.metadata for CQL generation
@@ -250,14 +251,14 @@ def db_field_name(self):
250251
@property
251252
def db_index_name(self):
252253
""" Returns the name of the cql index """
253-
return 'index_{}'.format(self.db_field_name)
254+
return 'index_{0}'.format(self.db_field_name)
254255

255256
@property
256257
def cql(self):
257258
return self.get_cql()
258259

259260
def get_cql(self):
260-
return '"{}"'.format(self.db_field_name)
261+
return '"{0}"'.format(self.db_field_name)
261262

262263
def _val_is_null(self, val):
263264
""" determines if the given value equates to a null value for the given column type """
@@ -323,13 +324,13 @@ def validate(self, value):
323324
if value is None:
324325
return
325326
if not isinstance(value, (six.string_types, bytearray)) and value is not None:
326-
raise ValidationError('{} {} is not a string'.format(self.column_name, type(value)))
327+
raise ValidationError('{0} {1} is not a string'.format(self.column_name, type(value)))
327328
if self.max_length:
328329
if len(value) > self.max_length:
329-
raise ValidationError('{} is longer than {} characters'.format(self.column_name, self.max_length))
330+
raise ValidationError('{0} is longer than {1} characters'.format(self.column_name, self.max_length))
330331
if self.min_length:
331332
if len(value) < self.min_length:
332-
raise ValidationError('{} is shorter than {} characters'.format(self.column_name, self.min_length))
333+
raise ValidationError('{0} is shorter than {1} characters'.format(self.column_name, self.min_length))
333334
return value
334335

335336

@@ -347,7 +348,7 @@ def validate(self, value):
347348
try:
348349
return int(val)
349350
except (TypeError, ValueError):
350-
raise ValidationError("{} {} can't be converted to integral value".format(self.column_name, value))
351+
raise ValidationError("{0} {1} can't be converted to integral value".format(self.column_name, value))
351352

352353
def to_python(self, value):
353354
return self.validate(value)
@@ -399,7 +400,7 @@ def validate(self, value):
399400
return int(val)
400401
except (TypeError, ValueError):
401402
raise ValidationError(
402-
"{} {} can't be converted to integral value".format(self.column_name, value))
403+
"{0} {1} can't be converted to integral value".format(self.column_name, value))
403404

404405
def to_python(self, value):
405406
return self.validate(value)
@@ -463,11 +464,11 @@ def to_database(self, value):
463464
if isinstance(value, date):
464465
value = datetime(value.year, value.month, value.day)
465466
else:
466-
raise ValidationError("{} '{}' is not a datetime object".format(self.column_name, value))
467+
raise ValidationError("{0} '{1}' is not a datetime object".format(self.column_name, value))
467468
epoch = datetime(1970, 1, 1, tzinfo=value.tzinfo)
468-
offset = epoch.tzinfo.utcoffset(epoch).total_seconds() if epoch.tzinfo else 0
469+
offset = get_total_seconds(epoch.tzinfo.utcoffset(epoch)) if epoch.tzinfo else 0
469470

470-
return int(((value - epoch).total_seconds() - offset) * 1000)
471+
return int((get_total_seconds(value - epoch) - offset) * 1000)
471472

472473

473474
class Date(Column):
@@ -530,7 +531,7 @@ def validate(self, value):
530531
except ValueError:
531532
# fall-through to error
532533
pass
533-
raise ValidationError("{} {} is not a valid uuid".format(
534+
raise ValidationError("{0} {1} is not a valid uuid".format(
534535
self.column_name, value))
535536

536537
def to_python(self, value):
@@ -561,8 +562,8 @@ def from_datetime(self, dt):
561562
global _last_timestamp
562563

563564
epoch = datetime(1970, 1, 1, tzinfo=dt.tzinfo)
564-
offset = epoch.tzinfo.utcoffset(epoch).total_seconds() if epoch.tzinfo else 0
565-
timestamp = (dt - epoch).total_seconds() - offset
565+
offset = get_total_seconds(epoch.tzinfo.utcoffset(epoch)) if epoch.tzinfo else 0
566+
timestamp = get_total_seconds(dt - epoch) - offset
566567

567568
node = None
568569
clock_seq = None
@@ -611,7 +612,7 @@ def validate(self, value):
611612
try:
612613
return float(value)
613614
except (TypeError, ValueError):
614-
raise ValidationError("{} {} is not a valid float".format(self.column_name, value))
615+
raise ValidationError("{0} {1} is not a valid float".format(self.column_name, value))
615616

616617
def to_python(self, value):
617618
return self.validate(value)
@@ -660,7 +661,7 @@ def validate(self, value):
660661
try:
661662
return _Decimal(val)
662663
except InvalidOperation:
663-
raise ValidationError("{} '{}' can't be coerced to decimal".format(self.column_name, val))
664+
raise ValidationError("{0} '{1}' can't be coerced to decimal".format(self.column_name, val))
664665

665666
def to_python(self, value):
666667
return self.validate(value)
@@ -702,7 +703,7 @@ def validate(self, value):
702703
# It is dangerous to let collections have more than 65535.
703704
# See: https://issues.apache.org/jira/browse/CASSANDRA-5428
704705
if value is not None and len(value) > 65535:
705-
raise ValidationError("{} Collection can't have more than 65535 elements.".format(self.column_name))
706+
raise ValidationError("{0} Collection can't have more than 65535 elements.".format(self.column_name))
706707
return value
707708

708709
def _val_is_null(self, val):
@@ -726,7 +727,7 @@ def __init__(self, value_type, strict=True, default=set, **kwargs):
726727
type on validation, or raise a validation error, defaults to True
727728
"""
728729
self.strict = strict
729-
self.db_type = 'set<{}>'.format(value_type.db_type)
730+
self.db_type = 'set<{0}>'.format(value_type.db_type)
730731
super(Set, self).__init__(value_type, default=default, **kwargs)
731732

732733
def validate(self, value):
@@ -736,24 +737,24 @@ def validate(self, value):
736737
types = (set,) if self.strict else (set, list, tuple)
737738
if not isinstance(val, types):
738739
if self.strict:
739-
raise ValidationError('{} {} is not a set object'.format(self.column_name, val))
740+
raise ValidationError('{0} {1} is not a set object'.format(self.column_name, val))
740741
else:
741-
raise ValidationError('{} {} cannot be coerced to a set object'.format(self.column_name, val))
742+
raise ValidationError('{0} {1} cannot be coerced to a set object'.format(self.column_name, val))
742743

743744
if None in val:
744-
raise ValidationError("{} None not allowed in a set".format(self.column_name))
745+
raise ValidationError("{0} None not allowed in a set".format(self.column_name))
745746

746-
return {self.value_col.validate(v) for v in val}
747+
return set(self.value_col.validate(v) for v in val)
747748

748749
def to_python(self, value):
749750
if value is None:
750751
return set()
751-
return {self.value_col.to_python(v) for v in value}
752+
return set(self.value_col.to_python(v) for v in value)
752753

753754
def to_database(self, value):
754755
if value is None:
755756
return None
756-
return {self.value_col.to_database(v) for v in value}
757+
return set(self.value_col.to_database(v) for v in value)
757758

758759

759760
class List(BaseContainerColumn):
@@ -766,17 +767,17 @@ def __init__(self, value_type, default=list, **kwargs):
766767
"""
767768
:param value_type: a column class indicating the types of the value
768769
"""
769-
self.db_type = 'list<{}>'.format(value_type.db_type)
770+
self.db_type = 'list<{0}>'.format(value_type.db_type)
770771
return super(List, self).__init__(value_type=value_type, default=default, **kwargs)
771772

772773
def validate(self, value):
773774
val = super(List, self).validate(value)
774775
if val is None:
775776
return
776777
if not isinstance(val, (set, list, tuple)):
777-
raise ValidationError('{} {} is not a list object'.format(self.column_name, val))
778+
raise ValidationError('{0} {1} is not a list object'.format(self.column_name, val))
778779
if None in val:
779-
raise ValidationError("{} None is not allowed in a list".format(self.column_name))
780+
raise ValidationError("{0} None is not allowed in a list".format(self.column_name))
780781
return [self.value_col.validate(v) for v in val]
781782

782783
def to_python(self, value):
@@ -802,7 +803,7 @@ def __init__(self, key_type, value_type, default=dict, **kwargs):
802803
:param value_type: a column class indicating the types of the value
803804
"""
804805

805-
self.db_type = 'map<{}, {}>'.format(key_type.db_type, value_type.db_type)
806+
self.db_type = 'map<{0}, {1}>'.format(key_type.db_type, value_type.db_type)
806807

807808
inheritance_comparator = issubclass if isinstance(key_type, type) else isinstance
808809
if not inheritance_comparator(key_type, Column):
@@ -825,21 +826,21 @@ def validate(self, value):
825826
if val is None:
826827
return
827828
if not isinstance(val, dict):
828-
raise ValidationError('{} {} is not a dict object'.format(self.column_name, val))
829+
raise ValidationError('{0} {1} is not a dict object'.format(self.column_name, val))
829830
if None in val:
830831
raise ValidationError("{} None is not allowed in a map".format(self.column_name))
831-
return {self.key_col.validate(k): self.value_col.validate(v) for k, v in val.items()}
832+
return dict((self.key_col.validate(k), self.value_col.validate(v)) for k, v in val.items())
832833

833834
def to_python(self, value):
834835
if value is None:
835836
return {}
836837
if value is not None:
837-
return {self.key_col.to_python(k): self.value_col.to_python(v) for k, v in value.items()}
838+
return dict((self.key_col.to_python(k), self.value_col.to_python(v)) for k, v in value.items())
838839

839840
def to_database(self, value):
840841
if value is None:
841842
return None
842-
return {self.key_col.to_database(k): self.value_col.to_database(v) for k, v in value.items()}
843+
return dict((self.key_col.to_database(k), self.value_col.to_database(v)) for k, v in value.items())
843844

844845
@property
845846
def sub_columns(self):
@@ -901,7 +902,7 @@ def __init__(self, model):
901902

902903
@property
903904
def db_field_name(self):
904-
return 'token({})'.format(', '.join(['"{}"'.format(c.db_field_name) for c in self.partition_columns]))
905+
return 'token({0})'.format(', '.join(['"{0}"'.format(c.db_field_name) for c in self.partition_columns]))
905906

906907
def to_database(self, value):
907908
from cqlengine.functions import Token
@@ -910,4 +911,4 @@ def to_database(self, value):
910911
return value
911912

912913
def get_cql(self):
913-
return "token({})".format(", ".join(c.cql for c in self.partition_columns))
914+
return "token({0})".format(", ".join(c.cql for c in self.partition_columns))

cassandra/cqlengine/functions.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@
1616

1717
from cassandra.cqlengine import UnicodeMixin, ValidationError
1818

19+
import sys
20+
21+
if sys.version_info >= (2, 7):
22+
def get_total_seconds(td):
23+
return td.total_seconds()
24+
else:
25+
def get_total_seconds(td):
26+
return 86400*td.days + td.seconds + td.microseconds/1e6
1927

2028
class QueryValue(UnicodeMixin):
2129
"""
2230
Base class for query filter values. Subclasses of these classes can
2331
be passed into .filter() keyword args
2432
"""
2533

26-
format_string = '%({})s'
34+
format_string = '%({0})s'
2735

2836
def __init__(self, value):
2937
self.value = value
@@ -58,7 +66,7 @@ class MinTimeUUID(BaseQueryFunction):
5866
http://cassandra.apache.org/doc/cql3/CQL.html#timeuuidFun
5967
"""
6068

61-
format_string = 'MinTimeUUID(%({})s)'
69+
format_string = 'MinTimeUUID(%({0})s)'
6270

6371
def __init__(self, value):
6472
"""
@@ -71,8 +79,8 @@ def __init__(self, value):
7179

7280
def to_database(self, val):
7381
epoch = datetime(1970, 1, 1, tzinfo=val.tzinfo)
74-
offset = epoch.tzinfo.utcoffset(epoch).total_seconds() if epoch.tzinfo else 0
75-
return int(((val - epoch).total_seconds() - offset) * 1000)
82+
offset = get_total_seconds(epoch.tzinfo.utcoffset(epoch)) if epoch.tzinfo else 0
83+
return int((get_total_seconds(val - epoch) - offset) * 1000)
7684

7785
def update_context(self, ctx):
7886
ctx[str(self.context_id)] = self.to_database(self.value)
@@ -85,7 +93,7 @@ class MaxTimeUUID(BaseQueryFunction):
8593
http://cassandra.apache.org/doc/cql3/CQL.html#timeuuidFun
8694
"""
8795

88-
format_string = 'MaxTimeUUID(%({})s)'
96+
format_string = 'MaxTimeUUID(%({0})s)'
8997

9098
def __init__(self, value):
9199
"""
@@ -125,8 +133,8 @@ def get_context_size(self):
125133
return len(self.value)
126134

127135
def __unicode__(self):
128-
token_args = ', '.join('%({})s'.format(self.context_id + i) for i in range(self.get_context_size()))
129-
return "token({})".format(token_args)
136+
token_args = ', '.join('%({0})s'.format(self.context_id + i) for i in range(self.get_context_size()))
137+
return "token({0})".format(token_args)
130138

131139
def update_context(self, ctx):
132140
for i, (col, val) in enumerate(zip(self._columns, self.value)):

0 commit comments

Comments
 (0)