-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy path__init__.py
More file actions
173 lines (141 loc) · 5.85 KB
/
__init__.py
File metadata and controls
173 lines (141 loc) · 5.85 KB
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
r"""UUID objects (universally unique identifiers) according to RFC 9562.
This module provides the functions uuid6(), uuid7(), and uuid8() for
generating version 6, 7, and 8 UUIDs as specified in RFC 9562.
"""
import secrets
import time
import uuid
from typing import Optional, Tuple
class UUID(uuid.UUID):
r"""Instances of the UUID class represent UUIDs as specified in RFC 9562."""
__slots__ = ()
def __init__(
self,
hex: Optional[str] = None,
bytes: Optional[bytes] = None,
bytes_le: Optional[bytes] = None,
fields: Optional[Tuple[int, int, int, int, int, int]] = None,
int: Optional[int] = None,
version: Optional[int] = None,
*,
is_safe: uuid.SafeUUID = uuid.SafeUUID.unknown
) -> None:
r"""Create a UUID."""
if int is None or [hex, bytes, bytes_le, fields].count(None) != 4:
return super().__init__(
hex=hex,
bytes=bytes,
bytes_le=bytes_le,
fields=fields,
int=int,
version=version,
is_safe=is_safe,
)
if not 0 <= int < 1 << 128:
raise ValueError("int is out of range (need a 128-bit value)")
if version is not None:
if not 6 <= version <= 8:
raise ValueError("illegal version number")
# Set the variant to RFC 4122.
int &= ~(0xC000 << 48)
int |= 0x8000 << 48
# Set the version number.
int &= ~(0xF000 << 64)
int |= version << 76
super().__init__(int=int, is_safe=is_safe)
@property
def subsec(self) -> int:
return ((self.int >> 64) & 0x0FFF) << 8 | ((self.int >> 54) & 0xFF)
@property
def time(self) -> int:
if self.version == 6:
return (
(self.time_low << 28)
| (self.time_mid << 12)
| (self.time_hi_version & 0x0FFF)
)
if self.version == 7:
return self.int >> 80
if self.version == 8:
return (self.int >> 80) * 10**6 + _subsec_decode(self.subsec)
return super().time
def _subsec_decode(value: int) -> int:
return -(-value * 10**6 // 2**20)
def _subsec_encode(value: int) -> int:
return value * 2**20 // 10**6
def uuid1_to_uuid6(uuid1: uuid.UUID) -> UUID:
r"""Generate a UUID version 6 object from a UUID version 1 object."""
if uuid1.version != 1:
raise ValueError("given UUID's version number must be 1")
h = uuid1.hex
h = h[13:16] + h[8:12] + h[0:5] + "6" + h[5:8] + h[16:]
return UUID(hex=h, is_safe=uuid1.is_safe)
_last_v6_timestamp = None
_last_v7_timestamp = None
_last_v8_timestamp = None
def uuid6(node: Optional[int] = None, clock_seq: Optional[int] = None) -> UUID:
r"""UUID version 6 is a field-compatible version of UUIDv1, reordered for
improved DB locality. It is expected that UUIDv6 will primarily be
used in contexts where there are existing v1 UUIDs. Systems that do
not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
If 'node' is not given, a random 48-bit number is chosen.
If 'clock_seq' is given, it is used as the sequence number;
otherwise a random 14-bit sequence number is chosen."""
global _last_v6_timestamp
nanoseconds = time.time_ns()
# 0x01b21dd213814000 is the number of 100-ns intervals between the
# UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
timestamp = nanoseconds // 100 + 0x01B21DD213814000
if _last_v6_timestamp is not None and timestamp <= _last_v6_timestamp:
timestamp = _last_v6_timestamp + 1
_last_v6_timestamp = timestamp
if clock_seq is None:
clock_seq = secrets.randbits(14) # instead of stable storage
if node is None:
node = secrets.randbits(48)
time_high_and_time_mid = (timestamp >> 12) & 0xFFFFFFFFFFFF
time_low_and_version = timestamp & 0x0FFF
uuid_int = time_high_and_time_mid << 80
uuid_int |= time_low_and_version << 64
uuid_int |= (clock_seq & 0x3FFF) << 48
uuid_int |= node & 0xFFFFFFFFFFFF
return UUID(int=uuid_int, version=6)
def uuid7(at_ts_ms = None) -> UUID:
r"""UUID version 7 features a time-ordered value field derived from the
widely implemented and well known Unix Epoch timestamp source, the
number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds
excluded. As well as improved entropy characteristics over versions
1 or 6.
Implementations SHOULD utilize UUID version 7 over UUID version 1 and
6 if possible."""
global _last_v7_timestamp
if at_ts_ms is None:
nanoseconds = time.time_ns()
timestamp_ms = nanoseconds // 10**6
if _last_v7_timestamp is not None and timestamp_ms <= _last_v7_timestamp:
timestamp_ms = _last_v7_timestamp + 1
_last_v7_timestamp = timestamp_ms
else:
timestamp_ms = at_ts_ms
uuid_int = (timestamp_ms & 0xFFFFFFFFFFFF) << 80
uuid_int |= secrets.randbits(76)
return UUID(int=uuid_int, version=7)
def uuid8() -> UUID:
r"""UUID version 8 features a time-ordered value field derived from the
widely implemented and well known Unix Epoch timestamp source, the
number of nanoseconds since midnight 1 Jan 1970 UTC, leap seconds
excluded."""
global _last_v8_timestamp
nanoseconds = time.time_ns()
if _last_v8_timestamp is not None and nanoseconds <= _last_v8_timestamp:
nanoseconds = _last_v8_timestamp + 1
_last_v8_timestamp = nanoseconds
timestamp_ms, timestamp_ns = divmod(nanoseconds, 10**6)
subsec = _subsec_encode(timestamp_ns)
subsec_a = subsec >> 8
subsec_b = subsec & 0xFF
uuid_int = (timestamp_ms & 0xFFFFFFFFFFFF) << 80
uuid_int |= subsec_a << 64
uuid_int |= subsec_b << 54
uuid_int |= secrets.randbits(54)
return UUID(int=uuid_int, version=8)