id.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. """Adapted from
  2. https://github.com/oittaa/uuid6-python/blob/main/src/uuid6/__init__.py#L95
  3. Bundled in to avoid install issues with uuid6 package
  4. """
  5. from __future__ import annotations
  6. import random
  7. import time
  8. import uuid
  9. _last_v6_timestamp = None
  10. class UUID(uuid.UUID):
  11. r"""UUID draft version objects"""
  12. __slots__ = ()
  13. def __init__(
  14. self,
  15. hex: str | None = None,
  16. bytes: bytes | None = None,
  17. bytes_le: bytes | None = None,
  18. fields: tuple[int, int, int, int, int, int] | None = None,
  19. int: int | None = None,
  20. version: int | None = None,
  21. *,
  22. is_safe: uuid.SafeUUID = uuid.SafeUUID.unknown,
  23. ) -> None:
  24. r"""Create a UUID."""
  25. if int is None or [hex, bytes, bytes_le, fields].count(None) != 4:
  26. return super().__init__(
  27. hex=hex,
  28. bytes=bytes,
  29. bytes_le=bytes_le,
  30. fields=fields,
  31. int=int,
  32. version=version,
  33. is_safe=is_safe,
  34. )
  35. if not 0 <= int < 1 << 128:
  36. raise ValueError("int is out of range (need a 128-bit value)")
  37. if version is not None:
  38. if not 6 <= version <= 8:
  39. raise ValueError("illegal version number")
  40. # Set the variant to RFC 4122.
  41. int &= ~(0xC000 << 48)
  42. int |= 0x8000 << 48
  43. # Set the version number.
  44. int &= ~(0xF000 << 64)
  45. int |= version << 76
  46. super().__init__(int=int, is_safe=is_safe)
  47. @property
  48. def subsec(self) -> int:
  49. return ((self.int >> 64) & 0x0FFF) << 8 | ((self.int >> 54) & 0xFF)
  50. @property
  51. def time(self) -> int:
  52. if self.version == 6:
  53. return (
  54. (self.time_low << 28)
  55. | (self.time_mid << 12)
  56. | (self.time_hi_version & 0x0FFF)
  57. )
  58. if self.version == 7:
  59. return self.int >> 80
  60. if self.version == 8:
  61. return (self.int >> 80) * 10**6 + _subsec_decode(self.subsec)
  62. return super().time
  63. def _subsec_decode(value: int) -> int:
  64. return -(-value * 10**6 // 2**20)
  65. def uuid6(node: int | None = None, clock_seq: int | None = None) -> UUID:
  66. r"""UUID version 6 is a field-compatible version of UUIDv1, reordered for
  67. improved DB locality. It is expected that UUIDv6 will primarily be
  68. used in contexts where there are existing v1 UUIDs. Systems that do
  69. not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
  70. If 'node' is not given, a random 48-bit number is chosen.
  71. If 'clock_seq' is given, it is used as the sequence number;
  72. otherwise a random 14-bit sequence number is chosen."""
  73. global _last_v6_timestamp
  74. nanoseconds = time.time_ns()
  75. # 0x01b21dd213814000 is the number of 100-ns intervals between the
  76. # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
  77. timestamp = nanoseconds // 100 + 0x01B21DD213814000
  78. if _last_v6_timestamp is not None and timestamp <= _last_v6_timestamp:
  79. timestamp = _last_v6_timestamp + 1
  80. _last_v6_timestamp = timestamp
  81. if clock_seq is None:
  82. clock_seq = random.getrandbits(14) # instead of stable storage
  83. if node is None:
  84. node = random.getrandbits(48)
  85. time_high_and_time_mid = (timestamp >> 12) & 0xFFFFFFFFFFFF
  86. time_low_and_version = timestamp & 0x0FFF
  87. uuid_int = time_high_and_time_mid << 80
  88. uuid_int |= time_low_and_version << 64
  89. uuid_int |= (clock_seq & 0x3FFF) << 48
  90. uuid_int |= node & 0xFFFFFFFFFFFF
  91. return UUID(int=uuid_int, version=6)