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