This repository has been archived by the owner on Aug 4, 2020. It is now read-only.
/
types.py
289 lines (228 loc) · 8.73 KB
/
types.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
"""
Data type definitions that are used when converting data to and from
the binary format that the data will be stored in.
In addition to the default classes included here, you may also define
custom types by creating a new class that extends :class:`~.CassandraType`.
For example, IntString, which stores an arbitrary integer as a string, may
be defined as follows:
.. code-block:: python
>>> class IntString(pycassa.types.CassandraType):
...
... @staticmethod
... def pack(intval):
... return str(intval)
...
... @staticmethod
... def unpack(strval):
... return int(strval)
"""
import calendar
from datetime import datetime
import pycassa.marshal as marshal
__all__ = ('CassandraType', 'BytesType', 'LongType', 'IntegerType',
'AsciiType', 'UTF8Type', 'TimeUUIDType', 'LexicalUUIDType',
'CounterColumnType', 'DoubleType', 'FloatType', 'DecimalType',
'BooleanType', 'DateType', 'OldPycassaDateType',
'IntermediateDateType', 'CompositeType',
'UUIDType', 'DynamicCompositeType')
class CassandraType(object):
"""
A data type that Cassandra is aware of and knows
how to validate and sort. All of the other classes in this
module are subclasses of this class.
If `reversed` is true and this is used as a column comparator,
the columns will be sorted in reverse order.
The `default` parameter only applies to use of this
with ColumnFamilyMap, where `default` is used if a row
does not contain a column corresponding to this item.
"""
def __init__(self, reversed=False, default=None):
self.reversed = reversed
self.default = default
if not hasattr(self.__class__, 'pack'):
self.pack = marshal.packer_for(self.__class__.__name__)
if not hasattr(self.__class__, 'unpack'):
self.unpack = marshal.unpacker_for(self.__class__.__name__)
def __str__(self):
return self.__class__.__name__ + "(reversed=" + str(self.reversed).lower() + ")"
class BytesType(CassandraType):
""" Stores data as a byte array """
pass
class LongType(CassandraType):
""" Stores data as an 8 byte integer """
pass
class IntegerType(CassandraType):
"""
Stores data as a variable-length integer. This
is a more compact format for storing small integers
than :class:`~.LongType`, and the limits
on the size of the integer are much higher.
.. versionchanged:: 1.2.0
Prior to 1.2.0, this was always stored as a 4 byte
integer.
"""
pass
class Int32Type(CassandraType):
""" Stores data as a 4 byte integer """
pass
class AsciiType(CassandraType):
""" Stores data as ASCII text """
pass
class UTF8Type(CassandraType):
""" Stores data as UTF8 encoded text """
pass
class UUIDType(CassandraType):
""" Stores data as a type 1 or type 4 UUID """
pass
class TimeUUIDType(CassandraType):
""" Stores data as a version 1 UUID """
pass
class LexicalUUIDType(CassandraType):
""" Stores data as a non-version 1 UUID """
pass
class CounterColumnType(CassandraType):
""" A 64bit counter column """
pass
class DoubleType(CassandraType):
""" Stores data as an 8 byte double """
pass
class FloatType(CassandraType):
""" Stores data as a 4 byte float """
pass
class DecimalType(CassandraType):
"""
Stores an unlimited precision decimal number. `decimal.Decimal`
objects are used by pycassa to represent these objects.
"""
pass
class BooleanType(CassandraType):
""" Stores data as a 1 byte boolean """
pass
class DateType(CassandraType):
"""
An 8 byte timestamp. This will be returned
as a :class:`datetime.datetime` instance by pycassa. Either
:class:`datetime` instances or timestamps will be accepted.
.. versionchanged:: 1.7.0
Prior to 1.7.0, datetime objects were expected to be in
local time. In 1.7.0 and beyond, naive datetimes are
assumed to be in UTC and tz-aware objects will be
automatically converted to UTC for storage in Cassandra.
"""
pass
def _to_timestamp(v, use_micros=False):
# Expects Value to be either date or datetime
if use_micros:
scale = 1e6
micro_scale = 1.0
else:
scale = 1e3
micro_scale = 1e3
try:
converted = calendar.timegm(v.utctimetuple())
converted = (converted * scale) + \
(getattr(v, 'microsecond', 0) / micro_scale)
except AttributeError:
# Ints and floats are valid timestamps too
if type(v) not in marshal._number_types:
raise TypeError('DateType arguments must be a datetime or timestamp')
converted = v * scale
return long(converted)
class OldPycassaDateType(CassandraType):
"""
This class can only read and write the DateType format
used by pycassa versions 1.2.0 to 1.5.0.
This formats store the number of microseconds since the
unix epoch, rather than the number of milliseconds, which
is what cassandra-cli and other clients supporting DateType
use.
.. versionchanged:: 1.7.0
Prior to 1.7.0, datetime objects were expected to be in
local time. In 1.7.0 and beyond, naive datetimes are
assumed to be in UTC and tz-aware objects will be
automatically converted to UTC for storage in Cassandra.
"""
@staticmethod
def pack(v, *args, **kwargs):
ts = _to_timestamp(v, use_micros=True)
return marshal._long_packer.pack(ts)
@staticmethod
def unpack(v):
ts = marshal._long_packer.unpack(v)[0] / 1e6
return datetime.utcfromtimestamp(ts)
class IntermediateDateType(CassandraType):
"""
This class is capable of reading either the DateType
format by pycassa versions 1.2.0 to 1.5.0 or the correct
format used in pycassa 1.5.1+. It will only write the
new, correct format.
This type is a good choice when you are using DateType
as the validator for non-indexed column values and you are
in the process of converting from thee old format to
the new format.
It almost certainly *should not be used* for row keys,
column names (if you care about the sorting), or column
values that have a secondary index on them.
.. versionchanged:: 1.7.0
Prior to 1.7.0, datetime objects were expected to be in
local time. In 1.7.0 and beyond, naive datetimes are
assumed to be in UTC and tz-aware objects will be
automatically converted to UTC for storage in Cassandra.
"""
@staticmethod
def pack(v, *args, **kwargs):
ts = _to_timestamp(v, use_micros=False)
return marshal._long_packer.pack(ts)
@staticmethod
def unpack(v):
raw_ts = marshal._long_packer.unpack(v)[0] / 1e3
try:
return datetime.utcfromtimestamp(raw_ts)
except ValueError:
# convert from bad microsecond format to millis
corrected_ts = raw_ts / 1e3
return datetime.utcfromtimestamp(corrected_ts)
class CompositeType(CassandraType):
"""
A type composed of one or more components, each of
which have their own type. When sorted, items are
primarily sorted by their first component, secondarily
by their second component, and so on.
Each of `*components` should be an instance of
a subclass of :class:`CassandraType`.
.. seealso:: :ref:`composite-types`
"""
def __init__(self, *components):
self.components = components
def __str__(self):
return "CompositeType(" + ", ".join(map(str, self.components)) + ")"
@property
def pack(self):
return marshal.get_composite_packer(composite_type=self)
@property
def unpack(self):
return marshal.get_composite_unpacker(composite_type=self)
class DynamicCompositeType(CassandraType):
"""
A type composed of one or more components, each of
which have their own type. When sorted, items are
primarily sorted by their first component, secondarily
by their second component, and so on.
Unlike CompositeType, DynamicCompositeType columns
need not all be of the same structure. Each column
can be composed of different component types.
Components are specified using a 2-tuple made up of
a comparator type and value. Aliases for comparator
types can optionally be specified with a dictionary
during instantiation.
"""
def __init__(self, *aliases):
self.aliases = {}
for alias in aliases:
if isinstance(alias, dict):
self.aliases.update(alias)
def __str__(self):
aliases = []
for k, v in self.aliases.iteritems():
aliases.append(k + '=>' + str(v))
return "DynamicCompositeType(" + ", ".join(aliases) + ")"