From b43a947d4a7a88e395939b370bd616f396dee6a1 Mon Sep 17 00:00:00 2001 From: Adam Holmberg Date: Tue, 19 May 2015 16:27:48 -0500 Subject: [PATCH] cqle: introduce 'date' and 'time' column types PYTHON-245 --- cassandra/cqlengine/columns.py | 65 +++++++++++++++--------- cassandra/cqltypes.py | 28 +++++----- cassandra/util.py | 4 +- docs/api/cassandra/cqlengine/columns.rst | 8 ++- 4 files changed, 64 insertions(+), 41 deletions(-) diff --git a/cassandra/cqlengine/columns.py b/cassandra/cqlengine/columns.py index 15c05532..9edf0d65 100644 --- a/cassandra/cqlengine/columns.py +++ b/cassandra/cqlengine/columns.py @@ -19,9 +19,8 @@ import re import six import warnings -from cassandra.cqltypes import DateType -from cassandra.encoder import cql_quote - +from cassandra import util +from cassandra.cqltypes import DateType, SimpleDateType from cassandra.cqlengine import ValidationError log = logging.getLogger(__name__) @@ -361,6 +360,10 @@ class Integer(Column): class TinyInt(Integer): """ Stores an 8-bit signed integer value + + .. versionadded:: 2.6.0 + + requires C* 2.2+ and protocol v4+ """ db_type = 'tinyint' @@ -368,6 +371,10 @@ class TinyInt(Integer): class SmallInt(Integer): """ Stores a 16-bit signed integer value + + .. versionadded:: 2.6.0 + + requires C* 2.2+ and protocol v4+ """ db_type = 'smallint' @@ -466,35 +473,43 @@ class DateTime(Column): class Date(Column): """ - *Note: this type is overloaded, and will likely be changed or removed to accommodate distinct date type - in a future version* + Stores a simple date, with no time-of-day - Stores a date value, with no time-of-day + .. versionchanged:: 2.6.0 + + removed overload of Date and DateTime. DateTime is a drop-in replacement for legacy models + + requires C* 2.2+ and protocol v4+ """ - db_type = 'timestamp' - - def to_python(self, value): - if value is None: - return - if isinstance(value, datetime): - return value.date() - elif isinstance(value, date): - return value - try: - return datetime.utcfromtimestamp(value).date() - except TypeError: - return datetime.utcfromtimestamp(DateType.deserialize(value)).date() + db_type = 'date' def to_database(self, value): value = super(Date, self).to_database(value) if value is None: return - if isinstance(value, datetime): - value = value.date() - if not isinstance(value, date): - raise ValidationError("{} '{}' is not a date object".format(self.column_name, repr(value))) - return int((value - date(1970, 1, 1)).total_seconds() * 1000) + # need to translate to int version because some dates are not representable in + # string form (datetime limitation) + d = value if isinstance(value, util.Date) else util.Date(value) + return d.days_from_epoch + SimpleDateType.EPOCH_OFFSET_DAYS + + +class Time(Column): + """ + Stores a timezone-naive time-of-day, with nanosecond precision + + .. versionadded:: 2.6.0 + + requires C* 2.2+ and protocol v4+ + """ + db_type = 'time' + + def to_database(self, value): + value = super(Time, self).to_database(value) + if value is None: + return + # str(util.Time) yields desired CQL encoding + return value if isinstance(value, util.Time) else util.Time(value) class UUID(Column): @@ -852,7 +867,7 @@ class UserDefinedType(Column): def __init__(self, user_type, **kwargs): """ - :param type user_type: specifies the :class:`~.UserType` model of the column + :param type user_type: specifies the :class:`~.cqlengine.usertype.UserType` model of the column """ self.user_type = user_type self.db_type = "frozen<%s>" % user_type.type_name() diff --git a/cassandra/cqltypes.py b/cassandra/cqltypes.py index 6e9306c7..db001134 100644 --- a/cassandra/cqltypes.py +++ b/cassandra/cqltypes.py @@ -638,27 +638,29 @@ class SimpleDateType(_CassandraType): typename = 'date' date_format = "%Y-%m-%d" + # Values of the 'date'` type are encoded as 32-bit unsigned integers + # representing a number of days with epoch (January 1st, 1970) at the center of the + # range (2^31). + EPOCH_OFFSET_DAYS = 2 ** 31 + @classmethod def validate(cls, val): if not isinstance(val, util.Date): val = util.Date(val) return val + @staticmethod + def deserialize(byts, protocol_version): + days = uint32_unpack(byts) - SimpleDateType.EPOCH_OFFSET_DAYS + return util.Date(days) + @staticmethod def serialize(val, protocol_version): - # Values of the 'date'` type are encoded as 32-bit unsigned integers - # representing a number of days with epoch (January 1st, 1970) at the center of the - # range (2^31). try: days = val.days_from_epoch except AttributeError: days = util.Date(val).days_from_epoch - return uint32_pack(days + 2 ** 31) - - @staticmethod - def deserialize(byts, protocol_version): - days = uint32_unpack(byts) - 2 ** 31 - return util.Date(days) + return uint32_pack(days + SimpleDateType.EPOCH_OFFSET_DAYS) class ShortType(_CassandraType): @@ -682,6 +684,10 @@ class TimeType(_CassandraType): val = util.Time(val) return val + @staticmethod + def deserialize(byts, protocol_version): + return util.Time(int64_unpack(byts)) + @staticmethod def serialize(val, protocol_version): try: @@ -690,10 +696,6 @@ class TimeType(_CassandraType): nano = util.Time(val).nanosecond_time return int64_pack(nano) - @staticmethod - def deserialize(byts, protocol_version): - return util.Time(int64_unpack(byts)) - class UTF8Type(_CassandraType): typename = 'text' diff --git a/cassandra/util.py b/cassandra/util.py index 35e1e5e4..dd463359 100644 --- a/cassandra/util.py +++ b/cassandra/util.py @@ -917,7 +917,7 @@ class Time(object): class Date(object): ''' - Idealized naive date: year, month, day + Idealized date: year, month, day Offers wider year range than datetime.date. For Dates that cannot be represented as a datetime.date (because datetime.MINYEAR, datetime.MAXYEAR), this type falls back @@ -997,5 +997,5 @@ class Date(object): dt = datetime_from_timestamp(self.seconds) return "%04d-%02d-%02d" % (dt.year, dt.month, dt.day) except: - # If we overflow datetime.[MIN|M + # If we overflow datetime.[MIN|MAX] return str(self.days_from_epoch) diff --git a/docs/api/cassandra/cqlengine/columns.rst b/docs/api/cassandra/cqlengine/columns.rst index a214d2cd..aad164b6 100644 --- a/docs/api/cassandra/cqlengine/columns.rst +++ b/docs/api/cassandra/cqlengine/columns.rst @@ -52,7 +52,7 @@ Columns of all types are initialized by passing :class:`.Column` attributes to t .. autoclass:: Counter -.. autoclass:: Date(**kwargs) +.. autoclass:: Date .. autoclass:: DateTime(**kwargs) @@ -70,10 +70,16 @@ Columns of all types are initialized by passing :class:`.Column` attributes to t .. autoclass:: Set +.. autoclass:: SmallInt + .. autoclass:: Text +.. autoclass:: Time + .. autoclass:: TimeUUID(**kwargs) +.. autoclass:: TinyInt + .. autoclass:: UserDefinedType .. autoclass:: UUID(**kwargs)