1# Copyright DataStax, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from __future__ import with_statement
16import calendar
17import datetime
18from functools import total_ordering
19import random
20import six
21import uuid
22import sys
23
24DATETIME_EPOC = datetime.datetime(1970, 1, 1)
25
26assert sys.byteorder in ('little', 'big')
27is_little_endian = sys.byteorder == 'little'
28
29def datetime_from_timestamp(timestamp):
30    """
31    Creates a timezone-agnostic datetime from timestamp (in seconds) in a consistent manner.
32    Works around a Windows issue with large negative timestamps (PYTHON-119),
33    and rounding differences in Python 3.4 (PYTHON-340).
34
35    :param timestamp: a unix timestamp, in seconds
36    """
37    dt = DATETIME_EPOC + datetime.timedelta(seconds=timestamp)
38    return dt
39
40
41def unix_time_from_uuid1(uuid_arg):
42    """
43    Converts a version 1 :class:`uuid.UUID` to a timestamp with the same precision
44    as :meth:`time.time()` returns.  This is useful for examining the
45    results of queries returning a v1 :class:`~uuid.UUID`.
46
47    :param uuid_arg: a version 1 :class:`~uuid.UUID`
48    """
49    return (uuid_arg.time - 0x01B21DD213814000) / 1e7
50
51
52def datetime_from_uuid1(uuid_arg):
53    """
54    Creates a timezone-agnostic datetime from the timestamp in the
55    specified type-1 UUID.
56
57    :param uuid_arg: a version 1 :class:`~uuid.UUID`
58    """
59    return datetime_from_timestamp(unix_time_from_uuid1(uuid_arg))
60
61
62def min_uuid_from_time(timestamp):
63    """
64    Generates the minimum TimeUUID (type 1) for a given timestamp, as compared by Cassandra.
65
66    See :func:`uuid_from_time` for argument and return types.
67    """
68    return uuid_from_time(timestamp, 0x808080808080, 0x80)  # Cassandra does byte-wise comparison; fill with min signed bytes (0x80 = -128)
69
70
71def max_uuid_from_time(timestamp):
72    """
73    Generates the maximum TimeUUID (type 1) for a given timestamp, as compared by Cassandra.
74
75    See :func:`uuid_from_time` for argument and return types.
76    """
77    return uuid_from_time(timestamp, 0x7f7f7f7f7f7f, 0x3f7f)  # Max signed bytes (0x7f = 127)
78
79
80def uuid_from_time(time_arg, node=None, clock_seq=None):
81    """
82    Converts a datetime or timestamp to a type 1 :class:`uuid.UUID`.
83
84    :param time_arg:
85      The time to use for the timestamp portion of the UUID.
86      This can either be a :class:`datetime` object or a timestamp
87      in seconds (as returned from :meth:`time.time()`).
88    :type datetime: :class:`datetime` or timestamp
89
90    :param node:
91      None integer for the UUID (up to 48 bits). If not specified, this
92      field is randomized.
93    :type node: long
94
95    :param clock_seq:
96      Clock sequence field for the UUID (up to 14 bits). If not specified,
97      a random sequence is generated.
98    :type clock_seq: int
99
100    :rtype: :class:`uuid.UUID`
101
102    """
103    if hasattr(time_arg, 'utctimetuple'):
104        seconds = int(calendar.timegm(time_arg.utctimetuple()))
105        microseconds = (seconds * 1e6) + time_arg.time().microsecond
106    else:
107        microseconds = int(time_arg * 1e6)
108
109    # 0x01b21dd213814000 is the number of 100-ns intervals between the
110    # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
111    intervals = int(microseconds * 10) + 0x01b21dd213814000
112
113    time_low = intervals & 0xffffffff
114    time_mid = (intervals >> 32) & 0xffff
115    time_hi_version = (intervals >> 48) & 0x0fff
116
117    if clock_seq is None:
118        clock_seq = random.getrandbits(14)
119    else:
120        if clock_seq > 0x3fff:
121            raise ValueError('clock_seq is out of range (need a 14-bit value)')
122
123    clock_seq_low = clock_seq & 0xff
124    clock_seq_hi_variant = 0x80 | ((clock_seq >> 8) & 0x3f)
125
126    if node is None:
127        node = random.getrandbits(48)
128
129    return uuid.UUID(fields=(time_low, time_mid, time_hi_version,
130                             clock_seq_hi_variant, clock_seq_low, node), version=1)
131
132LOWEST_TIME_UUID = uuid.UUID('00000000-0000-1000-8080-808080808080')
133""" The lowest possible TimeUUID, as sorted by Cassandra. """
134
135HIGHEST_TIME_UUID = uuid.UUID('ffffffff-ffff-1fff-bf7f-7f7f7f7f7f7f')
136""" The highest possible TimeUUID, as sorted by Cassandra. """
137
138
139try:
140    from collections import OrderedDict
141except ImportError:
142    # OrderedDict from Python 2.7+
143
144    # Copyright (c) 2009 Raymond Hettinger
145    #
146    # Permission is hereby granted, free of charge, to any person
147    # obtaining a copy of this software and associated documentation files
148    # (the "Software"), to deal in the Software without restriction,
149    # including without limitation the rights to use, copy, modify, merge,
150    # publish, distribute, sublicense, and/or sell copies of the Software,
151    # and to permit persons to whom the Software is furnished to do so,
152    # subject to the following conditions:
153    #
154    #     The above copyright notice and this permission notice shall be
155    #     included in all copies or substantial portions of the Software.
156    #
157    #     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
158    #     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
159    #     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
160    #     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
161    #     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
162    #     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
163    #     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
164    #     OTHER DEALINGS IN THE SOFTWARE.
165    from UserDict import DictMixin
166
167    class OrderedDict(dict, DictMixin):  # noqa
168        """ A dictionary which maintains the insertion order of keys. """
169
170        def __init__(self, *args, **kwds):
171            """ A dictionary which maintains the insertion order of keys. """
172
173            if len(args) > 1:
174                raise TypeError('expected at most 1 arguments, got %d' % len(args))
175            try:
176                self.__end
177            except AttributeError:
178                self.clear()
179            self.update(*args, **kwds)
180
181        def clear(self):
182            self.__end = end = []
183            end += [None, end, end]         # sentinel node for doubly linked list
184            self.__map = {}                 # key --> [key, prev, next]
185            dict.clear(self)
186
187        def __setitem__(self, key, value):
188            if key not in self:
189                end = self.__end
190                curr = end[1]
191                curr[2] = end[1] = self.__map[key] = [key, curr, end]
192            dict.__setitem__(self, key, value)
193
194        def __delitem__(self, key):
195            dict.__delitem__(self, key)
196            key, prev, next = self.__map.pop(key)
197            prev[2] = next
198            next[1] = prev
199
200        def __iter__(self):
201            end = self.__end
202            curr = end[2]
203            while curr is not end:
204                yield curr[0]
205                curr = curr[2]
206
207        def __reversed__(self):
208            end = self.__end
209            curr = end[1]
210            while curr is not end:
211                yield curr[0]
212                curr = curr[1]
213
214        def popitem(self, last=True):
215            if not self:
216                raise KeyError('dictionary is empty')
217            if last:
218                key = next(reversed(self))
219            else:
220                key = next(iter(self))
221            value = self.pop(key)
222            return key, value
223
224        def __reduce__(self):
225            items = [[k, self[k]] for k in self]
226            tmp = self.__map, self.__end
227            del self.__map, self.__end
228            inst_dict = vars(self).copy()
229            self.__map, self.__end = tmp
230            if inst_dict:
231                return (self.__class__, (items,), inst_dict)
232            return self.__class__, (items,)
233
234        def keys(self):
235            return list(self)
236
237        setdefault = DictMixin.setdefault
238        update = DictMixin.update
239        pop = DictMixin.pop
240        values = DictMixin.values
241        items = DictMixin.items
242        iterkeys = DictMixin.iterkeys
243        itervalues = DictMixin.itervalues
244        iteritems = DictMixin.iteritems
245
246        def __repr__(self):
247            if not self:
248                return '%s()' % (self.__class__.__name__,)
249            return '%s(%r)' % (self.__class__.__name__, self.items())
250
251        def copy(self):
252            return self.__class__(self)
253
254        @classmethod
255        def fromkeys(cls, iterable, value=None):
256            d = cls()
257            for key in iterable:
258                d[key] = value
259            return d
260
261        def __eq__(self, other):
262            if isinstance(other, OrderedDict):
263                if len(self) != len(other):
264                    return False
265                for p, q in zip(self.items(), other.items()):
266                    if p != q:
267                        return False
268                return True
269            return dict.__eq__(self, other)
270
271        def __ne__(self, other):
272            return not self == other
273
274
275# WeakSet from Python 2.7+ (https://code.google.com/p/weakrefset)
276
277from _weakref import ref
278
279
280class _IterationGuard(object):
281    # This context manager registers itself in the current iterators of the
282    # weak container, such as to delay all removals until the context manager
283    # exits.
284    # This technique should be relatively thread-safe (since sets are).
285
286    def __init__(self, weakcontainer):
287        # Don't create cycles
288        self.weakcontainer = ref(weakcontainer)
289
290    def __enter__(self):
291        w = self.weakcontainer()
292        if w is not None:
293            w._iterating.add(self)
294        return self
295
296    def __exit__(self, e, t, b):
297        w = self.weakcontainer()
298        if w is not None:
299            s = w._iterating
300            s.remove(self)
301            if not s:
302                w._commit_removals()
303
304
305class WeakSet(object):
306    def __init__(self, data=None):
307        self.data = set()
308
309        def _remove(item, selfref=ref(self)):
310            self = selfref()
311            if self is not None:
312                if self._iterating:
313                    self._pending_removals.append(item)
314                else:
315                    self.data.discard(item)
316
317        self._remove = _remove
318        # A list of keys to be removed
319        self._pending_removals = []
320        self._iterating = set()
321        if data is not None:
322            self.update(data)
323
324    def _commit_removals(self):
325        l = self._pending_removals
326        discard = self.data.discard
327        while l:
328            discard(l.pop())
329
330    def __iter__(self):
331        with _IterationGuard(self):
332            for itemref in self.data:
333                item = itemref()
334                if item is not None:
335                    yield item
336
337    def __len__(self):
338        return sum(x() is not None for x in self.data)
339
340    def __contains__(self, item):
341        return ref(item) in self.data
342
343    def __reduce__(self):
344        return (self.__class__, (list(self),),
345                getattr(self, '__dict__', None))
346
347    __hash__ = None
348
349    def add(self, item):
350        if self._pending_removals:
351            self._commit_removals()
352        self.data.add(ref(item, self._remove))
353
354    def clear(self):
355        if self._pending_removals:
356            self._commit_removals()
357        self.data.clear()
358
359    def copy(self):
360        return self.__class__(self)
361
362    def pop(self):
363        if self._pending_removals:
364            self._commit_removals()
365        while True:
366            try:
367                itemref = self.data.pop()
368            except KeyError:
369                raise KeyError('pop from empty WeakSet')
370            item = itemref()
371            if item is not None:
372                return item
373
374    def remove(self, item):
375        if self._pending_removals:
376            self._commit_removals()
377        self.data.remove(ref(item))
378
379    def discard(self, item):
380        if self._pending_removals:
381            self._commit_removals()
382        self.data.discard(ref(item))
383
384    def update(self, other):
385        if self._pending_removals:
386            self._commit_removals()
387        if isinstance(other, self.__class__):
388            self.data.update(other.data)
389        else:
390            for element in other:
391                self.add(element)
392
393    def __ior__(self, other):
394        self.update(other)
395        return self
396
397    # Helper functions for simple delegating methods.
398    def _apply(self, other, method):
399        if not isinstance(other, self.__class__):
400            other = self.__class__(other)
401        newdata = method(other.data)
402        newset = self.__class__()
403        newset.data = newdata
404        return newset
405
406    def difference(self, other):
407        return self._apply(other, self.data.difference)
408    __sub__ = difference
409
410    def difference_update(self, other):
411        if self._pending_removals:
412            self._commit_removals()
413        if self is other:
414            self.data.clear()
415        else:
416            self.data.difference_update(ref(item) for item in other)
417
418    def __isub__(self, other):
419        if self._pending_removals:
420            self._commit_removals()
421        if self is other:
422            self.data.clear()
423        else:
424            self.data.difference_update(ref(item) for item in other)
425        return self
426
427    def intersection(self, other):
428        return self._apply(other, self.data.intersection)
429    __and__ = intersection
430
431    def intersection_update(self, other):
432        if self._pending_removals:
433            self._commit_removals()
434        self.data.intersection_update(ref(item) for item in other)
435
436    def __iand__(self, other):
437        if self._pending_removals:
438            self._commit_removals()
439        self.data.intersection_update(ref(item) for item in other)
440        return self
441
442    def issubset(self, other):
443        return self.data.issubset(ref(item) for item in other)
444    __lt__ = issubset
445
446    def __le__(self, other):
447        return self.data <= set(ref(item) for item in other)
448
449    def issuperset(self, other):
450        return self.data.issuperset(ref(item) for item in other)
451    __gt__ = issuperset
452
453    def __ge__(self, other):
454        return self.data >= set(ref(item) for item in other)
455
456    def __eq__(self, other):
457        if not isinstance(other, self.__class__):
458            return NotImplemented
459        return self.data == set(ref(item) for item in other)
460
461    def symmetric_difference(self, other):
462        return self._apply(other, self.data.symmetric_difference)
463    __xor__ = symmetric_difference
464
465    def symmetric_difference_update(self, other):
466        if self._pending_removals:
467            self._commit_removals()
468        if self is other:
469            self.data.clear()
470        else:
471            self.data.symmetric_difference_update(ref(item) for item in other)
472
473    def __ixor__(self, other):
474        if self._pending_removals:
475            self._commit_removals()
476        if self is other:
477            self.data.clear()
478        else:
479            self.data.symmetric_difference_update(ref(item) for item in other)
480        return self
481
482    def union(self, other):
483        return self._apply(other, self.data.union)
484    __or__ = union
485
486    def isdisjoint(self, other):
487        return len(self.intersection(other)) == 0
488
489
490class SortedSet(object):
491    '''
492    A sorted set based on sorted list
493
494    A sorted set implementation is used in this case because it does not
495    require its elements to be immutable/hashable.
496
497    #Not implemented: update functions, inplace operators
498    '''
499
500    def __init__(self, iterable=()):
501        self._items = []
502        self.update(iterable)
503
504    def __len__(self):
505        return len(self._items)
506
507    def __getitem__(self, i):
508        return self._items[i]
509
510    def __iter__(self):
511        return iter(self._items)
512
513    def __reversed__(self):
514        return reversed(self._items)
515
516    def __repr__(self):
517        return '%s(%r)' % (
518            self.__class__.__name__,
519            self._items)
520
521    def __reduce__(self):
522        return self.__class__, (self._items,)
523
524    def __eq__(self, other):
525        if isinstance(other, self.__class__):
526            return self._items == other._items
527        else:
528            try:
529                return len(other) == len(self._items) and all(item in self for item in other)
530            except TypeError:
531                return NotImplemented
532
533    def __ne__(self, other):
534        if isinstance(other, self.__class__):
535            return self._items != other._items
536        else:
537            try:
538                return len(other) != len(self._items) or any(item not in self for item in other)
539            except TypeError:
540                return NotImplemented
541
542    def __le__(self, other):
543        return self.issubset(other)
544
545    def __lt__(self, other):
546        return len(other) > len(self._items) and self.issubset(other)
547
548    def __ge__(self, other):
549        return self.issuperset(other)
550
551    def __gt__(self, other):
552        return len(self._items) > len(other) and self.issuperset(other)
553
554    def __and__(self, other):
555        return self._intersect(other)
556    __rand__ = __and__
557
558    def __iand__(self, other):
559        isect = self._intersect(other)
560        self._items = isect._items
561        return self
562
563    def __or__(self, other):
564        return self.union(other)
565    __ror__ = __or__
566
567    def __ior__(self, other):
568        union = self.union(other)
569        self._items = union._items
570        return self
571
572    def __sub__(self, other):
573        return self._diff(other)
574
575    def __rsub__(self, other):
576        return sortedset(other) - self
577
578    def __isub__(self, other):
579        diff = self._diff(other)
580        self._items = diff._items
581        return self
582
583    def __xor__(self, other):
584        return self.symmetric_difference(other)
585    __rxor__ = __xor__
586
587    def __ixor__(self, other):
588        sym_diff = self.symmetric_difference(other)
589        self._items = sym_diff._items
590        return self
591
592    def __contains__(self, item):
593        i = self._find_insertion(item)
594        return i < len(self._items) and self._items[i] == item
595
596    def __delitem__(self, i):
597        del self._items[i]
598
599    def __delslice__(self, i, j):
600        del self._items[i:j]
601
602    def add(self, item):
603        i = self._find_insertion(item)
604        if i < len(self._items):
605            if self._items[i] != item:
606                self._items.insert(i, item)
607        else:
608            self._items.append(item)
609
610    def update(self, iterable):
611        for i in iterable:
612            self.add(i)
613
614    def clear(self):
615        del self._items[:]
616
617    def copy(self):
618        new = sortedset()
619        new._items = list(self._items)
620        return new
621
622    def isdisjoint(self, other):
623        return len(self._intersect(other)) == 0
624
625    def issubset(self, other):
626        return len(self._intersect(other)) == len(self._items)
627
628    def issuperset(self, other):
629        return len(self._intersect(other)) == len(other)
630
631    def pop(self):
632        if not self._items:
633            raise KeyError("pop from empty set")
634        return self._items.pop()
635
636    def remove(self, item):
637        i = self._find_insertion(item)
638        if i < len(self._items):
639            if self._items[i] == item:
640                self._items.pop(i)
641                return
642        raise KeyError('%r' % item)
643
644    def union(self, *others):
645        union = sortedset()
646        union._items = list(self._items)
647        for other in others:
648            for item in other:
649                union.add(item)
650        return union
651
652    def intersection(self, *others):
653        isect = self.copy()
654        for other in others:
655            isect = isect._intersect(other)
656            if not isect:
657                break
658        return isect
659
660    def difference(self, *others):
661        diff = self.copy()
662        for other in others:
663            diff = diff._diff(other)
664            if not diff:
665                break
666        return diff
667
668    def symmetric_difference(self, other):
669        diff_self_other = self._diff(other)
670        diff_other_self = other.difference(self)
671        return diff_self_other.union(diff_other_self)
672
673    def _diff(self, other):
674        diff = sortedset()
675        for item in self._items:
676            if item not in other:
677                diff.add(item)
678        return diff
679
680    def _intersect(self, other):
681        isect = sortedset()
682        for item in self._items:
683            if item in other:
684                isect.add(item)
685        return isect
686
687    def _find_insertion(self, x):
688        # this uses bisect_left algorithm unless it has elements it can't compare,
689        # in which case it defaults to grouping non-comparable items at the beginning or end,
690        # and scanning sequentially to find an insertion point
691        a = self._items
692        lo = 0
693        hi = len(a)
694        try:
695            while lo < hi:
696                mid = (lo + hi) // 2
697                if a[mid] < x: lo = mid + 1
698                else: hi = mid
699        except TypeError:
700            # could not compare a[mid] with x
701            # start scanning to find insertion point while swallowing type errors
702            lo = 0
703            compared_one = False  # flag is used to determine whether uncomparables are grouped at the front or back
704            while lo < hi:
705                try:
706                    if a[lo] == x or a[lo] >= x: break
707                    compared_one = True
708                except TypeError:
709                    if compared_one: break
710                lo += 1
711        return lo
712
713sortedset = SortedSet  # backwards-compatibility
714
715
716from collections import Mapping
717from six.moves import cPickle
718
719
720class OrderedMap(Mapping):
721    '''
722    An ordered map that accepts non-hashable types for keys. It also maintains the
723    insertion order of items, behaving as OrderedDict in that regard. These maps
724    are constructed and read just as normal mapping types, exept that they may
725    contain arbitrary collections and other non-hashable items as keys::
726
727        >>> od = OrderedMap([({'one': 1, 'two': 2}, 'value'),
728        ...                  ({'three': 3, 'four': 4}, 'value2')])
729        >>> list(od.keys())
730        [{'two': 2, 'one': 1}, {'three': 3, 'four': 4}]
731        >>> list(od.values())
732        ['value', 'value2']
733
734    These constructs are needed to support nested collections in Cassandra 2.1.3+,
735    where frozen collections can be specified as parameters to others\*::
736
737        CREATE TABLE example (
738            ...
739            value map<frozen<map<int, int>>, double>
740            ...
741        )
742
743    This class derives from the (immutable) Mapping API. Objects in these maps
744    are not intended be modified.
745
746    \* Note: Because of the way Cassandra encodes nested types, when using the
747    driver with nested collections, :attr:`~.Cluster.protocol_version` must be 3
748    or higher.
749
750    '''
751
752    def __init__(self, *args, **kwargs):
753        if len(args) > 1:
754            raise TypeError('expected at most 1 arguments, got %d' % len(args))
755
756        self._items = []
757        self._index = {}
758        if args:
759            e = args[0]
760            if callable(getattr(e, 'keys', None)):
761                for k in e.keys():
762                    self._insert(k, e[k])
763            else:
764                for k, v in e:
765                    self._insert(k, v)
766
767        for k, v in six.iteritems(kwargs):
768            self._insert(k, v)
769
770    def _insert(self, key, value):
771        flat_key = self._serialize_key(key)
772        i = self._index.get(flat_key, -1)
773        if i >= 0:
774            self._items[i] = (key, value)
775        else:
776            self._items.append((key, value))
777            self._index[flat_key] = len(self._items) - 1
778
779    __setitem__ = _insert
780
781    def __getitem__(self, key):
782        try:
783            index = self._index[self._serialize_key(key)]
784            return self._items[index][1]
785        except KeyError:
786            raise KeyError(str(key))
787
788    def __delitem__(self, key):
789        # not efficient -- for convenience only
790        try:
791            index = self._index.pop(self._serialize_key(key))
792            self._index = dict((k, i if i < index else i - 1) for k, i in self._index.items())
793            self._items.pop(index)
794        except KeyError:
795            raise KeyError(str(key))
796
797    def __iter__(self):
798        for i in self._items:
799            yield i[0]
800
801    def __len__(self):
802        return len(self._items)
803
804    def __eq__(self, other):
805        if isinstance(other, OrderedMap):
806            return self._items == other._items
807        try:
808            d = dict(other)
809            return len(d) == len(self._items) and all(i[1] == d[i[0]] for i in self._items)
810        except KeyError:
811            return False
812        except TypeError:
813            pass
814        return NotImplemented
815
816    def __repr__(self):
817        return '%s([%s])' % (
818            self.__class__.__name__,
819            ', '.join("(%r, %r)" % (k, v) for k, v in self._items))
820
821    def __str__(self):
822        return '{%s}' % ', '.join("%r: %r" % (k, v) for k, v in self._items)
823
824    def popitem(self):
825        try:
826            kv = self._items.pop()
827            del self._index[self._serialize_key(kv[0])]
828            return kv
829        except IndexError:
830            raise KeyError()
831
832    def _serialize_key(self, key):
833        return cPickle.dumps(key)
834
835
836class OrderedMapSerializedKey(OrderedMap):
837
838    def __init__(self, cass_type, protocol_version):
839        super(OrderedMapSerializedKey, self).__init__()
840        self.cass_key_type = cass_type
841        self.protocol_version = protocol_version
842
843    def _insert_unchecked(self, key, flat_key, value):
844        self._items.append((key, value))
845        self._index[flat_key] = len(self._items) - 1
846
847    def _serialize_key(self, key):
848        return self.cass_key_type.serialize(key, self.protocol_version)
849
850
851import datetime
852import time
853
854if six.PY3:
855    long = int
856
857
858@total_ordering
859class Time(object):
860    '''
861    Idealized time, independent of day.
862
863    Up to nanosecond resolution
864    '''
865
866    MICRO = 1000
867    MILLI = 1000 * MICRO
868    SECOND = 1000 * MILLI
869    MINUTE = 60 * SECOND
870    HOUR = 60 * MINUTE
871    DAY = 24 * HOUR
872
873    nanosecond_time = 0
874
875    def __init__(self, value):
876        """
877        Initializer value can be:
878
879        - integer_type: absolute nanoseconds in the day
880        - datetime.time: built-in time
881        - string_type: a string time of the form "HH:MM:SS[.mmmuuunnn]"
882        """
883        if isinstance(value, six.integer_types):
884            self._from_timestamp(value)
885        elif isinstance(value, datetime.time):
886            self._from_time(value)
887        elif isinstance(value, six.string_types):
888            self._from_timestring(value)
889        else:
890            raise TypeError('Time arguments must be a whole number, datetime.time, or string')
891
892    @property
893    def hour(self):
894        """
895        The hour component of this time (0-23)
896        """
897        return self.nanosecond_time // Time.HOUR
898
899    @property
900    def minute(self):
901        """
902        The minute component of this time (0-59)
903        """
904        minutes = self.nanosecond_time // Time.MINUTE
905        return minutes % 60
906
907    @property
908    def second(self):
909        """
910        The second component of this time (0-59)
911        """
912        seconds = self.nanosecond_time // Time.SECOND
913        return seconds % 60
914
915    @property
916    def nanosecond(self):
917        """
918        The fractional seconds component of the time, in nanoseconds
919        """
920        return self.nanosecond_time % Time.SECOND
921
922    def time(self):
923        """
924        Return a built-in datetime.time (nanosecond precision truncated to micros).
925        """
926        return datetime.time(hour=self.hour, minute=self.minute, second=self.second,
927                             microsecond=self.nanosecond // Time.MICRO)
928
929    def _from_timestamp(self, t):
930        if t >= Time.DAY:
931            raise ValueError("value must be less than number of nanoseconds in a day (%d)" % Time.DAY)
932        self.nanosecond_time = t
933
934    def _from_timestring(self, s):
935        try:
936            parts = s.split('.')
937            base_time = time.strptime(parts[0], "%H:%M:%S")
938            self.nanosecond_time = (base_time.tm_hour * Time.HOUR +
939                                    base_time.tm_min * Time.MINUTE +
940                                    base_time.tm_sec * Time.SECOND)
941
942            if len(parts) > 1:
943                # right pad to 9 digits
944                nano_time_str = parts[1] + "0" * (9 - len(parts[1]))
945                self.nanosecond_time += int(nano_time_str)
946
947        except ValueError:
948            raise ValueError("can't interpret %r as a time" % (s,))
949
950    def _from_time(self, t):
951        self.nanosecond_time = (t.hour * Time.HOUR +
952                                t.minute * Time.MINUTE +
953                                t.second * Time.SECOND +
954                                t.microsecond * Time.MICRO)
955
956    def __hash__(self):
957        return self.nanosecond_time
958
959    def __eq__(self, other):
960        if isinstance(other, Time):
961            return self.nanosecond_time == other.nanosecond_time
962
963        if isinstance(other, six.integer_types):
964            return self.nanosecond_time == other
965
966        return self.nanosecond_time % Time.MICRO == 0 and \
967            datetime.time(hour=self.hour, minute=self.minute, second=self.second,
968                          microsecond=self.nanosecond // Time.MICRO) == other
969
970    def __ne__(self, other):
971        return not self.__eq__(other)
972
973    def __lt__(self, other):
974        if not isinstance(other, Time):
975            return NotImplemented
976        return self.nanosecond_time < other.nanosecond_time
977
978    def __repr__(self):
979        return "Time(%s)" % self.nanosecond_time
980
981    def __str__(self):
982        return "%02d:%02d:%02d.%09d" % (self.hour, self.minute,
983                                        self.second, self.nanosecond)
984
985
986@total_ordering
987class Date(object):
988    '''
989    Idealized date: year, month, day
990
991    Offers wider year range than datetime.date. For Dates that cannot be represented
992    as a datetime.date (because datetime.MINYEAR, datetime.MAXYEAR), this type falls back
993    to printing days_from_epoch offset.
994    '''
995
996    MINUTE = 60
997    HOUR = 60 * MINUTE
998    DAY = 24 * HOUR
999
1000    date_format = "%Y-%m-%d"
1001
1002    days_from_epoch = 0
1003
1004    def __init__(self, value):
1005        """
1006        Initializer value can be:
1007
1008        - integer_type: absolute days from epoch (1970, 1, 1). Can be negative.
1009        - datetime.date: built-in date
1010        - string_type: a string time of the form "yyyy-mm-dd"
1011        """
1012        if isinstance(value, six.integer_types):
1013            self.days_from_epoch = value
1014        elif isinstance(value, (datetime.date, datetime.datetime)):
1015            self._from_timetuple(value.timetuple())
1016        elif isinstance(value, six.string_types):
1017            self._from_datestring(value)
1018        else:
1019            raise TypeError('Date arguments must be a whole number, datetime.date, or string')
1020
1021    @property
1022    def seconds(self):
1023        """
1024        Absolute seconds from epoch (can be negative)
1025        """
1026        return self.days_from_epoch * Date.DAY
1027
1028    def date(self):
1029        """
1030        Return a built-in datetime.date for Dates falling in the years [datetime.MINYEAR, datetime.MAXYEAR]
1031
1032        ValueError is raised for Dates outside this range.
1033        """
1034        try:
1035            dt = datetime_from_timestamp(self.seconds)
1036            return datetime.date(dt.year, dt.month, dt.day)
1037        except Exception:
1038            raise ValueError("%r exceeds ranges for built-in datetime.date" % self)
1039
1040    def _from_timetuple(self, t):
1041        self.days_from_epoch = calendar.timegm(t) // Date.DAY
1042
1043    def _from_datestring(self, s):
1044        if s[0] == '+':
1045            s = s[1:]
1046        dt = datetime.datetime.strptime(s, self.date_format)
1047        self._from_timetuple(dt.timetuple())
1048
1049    def __hash__(self):
1050        return self.days_from_epoch
1051
1052    def __eq__(self, other):
1053        if isinstance(other, Date):
1054            return self.days_from_epoch == other.days_from_epoch
1055
1056        if isinstance(other, six.integer_types):
1057            return self.days_from_epoch == other
1058
1059        try:
1060            return self.date() == other
1061        except Exception:
1062            return False
1063
1064    def __ne__(self, other):
1065        return not self.__eq__(other)
1066
1067    def __lt__(self, other):
1068        if not isinstance(other, Date):
1069            return NotImplemented
1070        return self.days_from_epoch < other.days_from_epoch
1071
1072    def __repr__(self):
1073        return "Date(%s)" % self.days_from_epoch
1074
1075    def __str__(self):
1076        try:
1077            dt = datetime_from_timestamp(self.seconds)
1078            return "%04d-%02d-%02d" % (dt.year, dt.month, dt.day)
1079        except:
1080            # If we overflow datetime.[MIN|MAX]
1081            return str(self.days_from_epoch)
1082
1083import socket
1084if hasattr(socket, 'inet_pton'):
1085    inet_pton = socket.inet_pton
1086    inet_ntop = socket.inet_ntop
1087else:
1088    """
1089    Windows doesn't have socket.inet_pton and socket.inet_ntop until Python 3.4
1090    This is an alternative impl using ctypes, based on this win_inet_pton project:
1091    https://github.com/hickeroar/win_inet_pton
1092    """
1093    import ctypes
1094
1095    class sockaddr(ctypes.Structure):
1096        """
1097        Shared struct for ipv4 and ipv6.
1098
1099        https://msdn.microsoft.com/en-us/library/windows/desktop/ms740496(v=vs.85).aspx
1100
1101        ``__pad1`` always covers the port.
1102
1103        When being used for ``sockaddr_in6``, ``ipv4_addr`` actually covers ``sin6_flowinfo``, resulting
1104        in proper alignment for ``ipv6_addr``.
1105        """
1106        _fields_ = [("sa_family", ctypes.c_short),
1107                    ("__pad1", ctypes.c_ushort),
1108                    ("ipv4_addr", ctypes.c_byte * 4),
1109                    ("ipv6_addr", ctypes.c_byte * 16),
1110                    ("__pad2", ctypes.c_ulong)]
1111
1112    if hasattr(ctypes, 'windll'):
1113        WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA
1114        WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA
1115    else:
1116        def not_windows(*args):
1117            raise OSError("IPv6 addresses cannot be handled on Windows. "
1118                            "Missing ctypes.windll")
1119        WSAStringToAddressA = not_windows
1120        WSAAddressToStringA = not_windows
1121
1122    def inet_pton(address_family, ip_string):
1123        if address_family == socket.AF_INET:
1124            return socket.inet_aton(ip_string)
1125
1126        addr = sockaddr()
1127        addr.sa_family = address_family
1128        addr_size = ctypes.c_int(ctypes.sizeof(addr))
1129
1130        if WSAStringToAddressA(
1131                ip_string,
1132                address_family,
1133                None,
1134                ctypes.byref(addr),
1135                ctypes.byref(addr_size)
1136        ) != 0:
1137            raise socket.error(ctypes.FormatError())
1138
1139        if address_family == socket.AF_INET6:
1140            return ctypes.string_at(addr.ipv6_addr, 16)
1141
1142        raise socket.error('unknown address family')
1143
1144    def inet_ntop(address_family, packed_ip):
1145        if address_family == socket.AF_INET:
1146            return socket.inet_ntoa(packed_ip)
1147
1148        addr = sockaddr()
1149        addr.sa_family = address_family
1150        addr_size = ctypes.c_int(ctypes.sizeof(addr))
1151        ip_string = ctypes.create_string_buffer(128)
1152        ip_string_size = ctypes.c_int(ctypes.sizeof(ip_string))
1153
1154        if address_family == socket.AF_INET6:
1155            if len(packed_ip) != ctypes.sizeof(addr.ipv6_addr):
1156                raise socket.error('packed IP wrong length for inet_ntoa')
1157            ctypes.memmove(addr.ipv6_addr, packed_ip, 16)
1158        else:
1159            raise socket.error('unknown address family')
1160
1161        if WSAAddressToStringA(
1162                ctypes.byref(addr),
1163                addr_size,
1164                None,
1165                ip_string,
1166                ctypes.byref(ip_string_size)
1167        ) != 0:
1168            raise socket.error(ctypes.FormatError())
1169
1170        return ip_string[:ip_string_size.value - 1]
1171
1172
1173import keyword
1174
1175
1176# similar to collections.namedtuple, reproduced here because Python 2.6 did not have the rename logic
1177def _positional_rename_invalid_identifiers(field_names):
1178    names_out = list(field_names)
1179    for index, name in enumerate(field_names):
1180        if (not all(c.isalnum() or c == '_' for c in name)
1181            or keyword.iskeyword(name)
1182            or not name
1183            or name[0].isdigit()
1184            or name.startswith('_')):
1185            names_out[index] = 'field_%d_' % index
1186    return names_out
1187
1188
1189def _sanitize_identifiers(field_names):
1190    names_out = _positional_rename_invalid_identifiers(field_names)
1191    if len(names_out) != len(set(names_out)):
1192        observed_names = set()
1193        for index, name in enumerate(names_out):
1194            while names_out[index] in observed_names:
1195                names_out[index] = "%s_" % (names_out[index],)
1196            observed_names.add(names_out[index])
1197    return names_out
1198
1199
1200class Duration(object):
1201    """
1202    Cassandra Duration Type
1203    """
1204
1205    months = 0
1206    days = 0
1207    nanoseconds = 0
1208
1209    def __init__(self, months=0, days=0, nanoseconds=0):
1210        self.months = months
1211        self.days = days
1212        self.nanoseconds = nanoseconds
1213
1214    def __eq__(self, other):
1215        return isinstance(other, self.__class__) and self.months == other.months and self.days == other.days and self.nanoseconds == other.nanoseconds
1216
1217    def __repr__(self):
1218        return "Duration({0}, {1}, {2})".format(self.months, self.days, self.nanoseconds)
1219
1220    def __str__(self):
1221        has_negative_values = self.months < 0 or self.days < 0 or self.nanoseconds < 0
1222        return '%s%dmo%dd%dns' % (
1223            '-' if has_negative_values else '',
1224            abs(self.months),
1225            abs(self.days),
1226            abs(self.nanoseconds)
1227        )
1228
1229
1230@total_ordering
1231class Version(object):
1232    """
1233    Internal minimalist class to compare versions.
1234    A valid version is: <int>.<int>.<int>.<int or str>.
1235
1236    TODO: when python2 support is removed, use packaging.version.
1237    """
1238
1239    _version = None
1240    major = None
1241    minor = 0
1242    patch = 0
1243    build = 0
1244
1245    def __init__(self, version):
1246        self._version = version
1247        parts = list(reversed(version.split('.')))
1248        if len(parts) > 4:
1249            raise ValueError("Invalid version: {}. Only 4 "
1250                             "components are supported".format(version))
1251
1252        self.major = int(parts.pop())
1253        self.minor = int(parts.pop()) if parts else 0
1254        self.patch = int(parts.pop()) if parts else 0
1255
1256        if parts:  # we have a build version
1257            build = parts.pop()
1258            try:
1259                self.build = int(build)
1260            except ValueError:
1261                self.build = build
1262
1263    def __hash__(self):
1264        return self._version
1265
1266    def __repr__(self):
1267        if self.build:
1268            return "Version({0}, {1}, {2}, {3})".format(self.major, self.minor, self.patch, self.build)
1269
1270        return "Version({0}, {1}, {2})".format(self.major, self.minor, self.patch)
1271
1272    def __str__(self):
1273        return self._version
1274
1275    @staticmethod
1276    def _compare_build(build, other_build, cmp):
1277        if not (isinstance(build, six.integer_types) and
1278                isinstance(other_build, six.integer_types)):
1279            build = str(build)
1280            other_build = str(other_build)
1281
1282        return cmp(build, other_build)
1283
1284    def __eq__(self, other):
1285        if not isinstance(other, Version):
1286            return NotImplemented
1287
1288        return (self.major == other.major and
1289                self.minor == other.minor and
1290                self.patch == other.patch and
1291                self._compare_build(self.build, other.build, lambda s, o: s == o))
1292
1293    def __gt__(self, other):
1294        if not isinstance(other, Version):
1295            return NotImplemented
1296
1297        is_major_ge = self.major >= other.major
1298        is_minor_ge = self.minor >= other.minor
1299        is_patch_ge = self.patch >= other.patch
1300        is_build_gt = self._compare_build(
1301            self.build, other.build, lambda s, o: s > o)
1302
1303        return (self.major > other.major or
1304            (is_major_ge and self.minor > other.minor) or
1305            (is_major_ge and is_minor_ge and self.patch > other.patch) or
1306            (is_major_ge and is_minor_ge and is_patch_ge and is_build_gt))
1307