Skip to content

Commit 818d417

Browse files
committed
add implementation
1 parent 99d62f9 commit 818d417

File tree

1 file changed

+110
-13
lines changed

1 file changed

+110
-13
lines changed

Lib/uuid.py

Lines changed: 110 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
r"""UUID objects (universally unique identifiers) according to RFC 4122.
22
33
This module provides immutable UUID objects (class UUID) and the functions
4-
uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5
5-
UUIDs as specified in RFC 4122.
4+
uuid1(), uuid3(), uuid4(), uuid5() for generating version 1 to 8 UUIDs as
5+
specified in RFC 4122 (superseeded by RFC 9562 but will still be referred
6+
to as RFC 4122 in the future for compatibility purposes).
67
78
If all you want is a unique ID, you should probably call uuid1() or uuid4().
89
Note that uuid1() may compromise privacy since it creates a UUID containing
@@ -129,7 +130,7 @@ class UUID:
129130
variant the UUID variant (one of the constants RESERVED_NCS,
130131
RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE)
131132
132-
version the UUID version number (1 through 5, meaningful only
133+
version the UUID version number (1 through 8, meaningful only
133134
when the variant is RFC_4122)
134135
135136
is_safe An enum indicating whether the UUID has been generated in
@@ -214,9 +215,9 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
214215
if not 0 <= int < 1<<128:
215216
raise ValueError('int is out of range (need a 128-bit value)')
216217
if version is not None:
217-
if not 1 <= version <= 5:
218+
if not 1 <= version <= 8:
218219
raise ValueError('illegal version number')
219-
# Set the variant to RFC 4122.
220+
# Set the variant to RFC 9562.
220221
int &= ~(0xc000 << 48)
221222
int |= 0x8000 << 48
222223
# Set the version number.
@@ -297,17 +298,29 @@ def bytes_le(self):
297298

298299
@property
299300
def fields(self):
301+
if self.version == 6:
302+
# the first field should be a 32-bit integer
303+
return (self.time_hi, self.time_mid, self.time_hi_version,
304+
self.clock_seq_hi_variant, self.clock_seq_low, self.node)
300305
return (self.time_low, self.time_mid, self.time_hi_version,
301306
self.clock_seq_hi_variant, self.clock_seq_low, self.node)
302307

303308
@property
304309
def time_low(self):
310+
if self.version == 6:
311+
return (self.int >> 64) & 0x0fff
305312
return self.int >> 96
306313

307314
@property
308315
def time_mid(self):
309316
return (self.int >> 80) & 0xffff
310317

318+
@property
319+
def time_hi(self):
320+
if self.version == 6:
321+
return self.int >> 96
322+
return (self.int >> 64) & 0x0fff
323+
311324
@property
312325
def time_hi_version(self):
313326
return (self.int >> 64) & 0xffff
@@ -322,8 +335,9 @@ def clock_seq_low(self):
322335

323336
@property
324337
def time(self):
325-
return (((self.time_hi_version & 0x0fff) << 48) |
326-
(self.time_mid << 32) | self.time_low)
338+
if self.version == 6:
339+
return (self.time_hi << 28) | (self.time_mid << 12) | self.time_low
340+
return (self.time_hi << 48) | (self.time_mid << 32) | self.time_low
327341

328342
@property
329343
def clock_seq(self):
@@ -656,7 +670,7 @@ def getnode():
656670
assert False, '_random_getnode() returned invalid value: {}'.format(_node)
657671

658672

659-
_last_timestamp = None
673+
_last_timestamp_v1 = None
660674

661675
def uuid1(node=None, clock_seq=None):
662676
"""Generate a UUID from a host ID, sequence number, and the current time.
@@ -674,15 +688,15 @@ def uuid1(node=None, clock_seq=None):
674688
is_safe = SafeUUID.unknown
675689
return UUID(bytes=uuid_time, is_safe=is_safe)
676690

677-
global _last_timestamp
691+
global _last_timestamp_v1
678692
import time
679693
nanoseconds = time.time_ns()
680694
# 0x01b21dd213814000 is the number of 100-ns intervals between the
681695
# UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
682696
timestamp = nanoseconds // 100 + 0x01b21dd213814000
683-
if _last_timestamp is not None and timestamp <= _last_timestamp:
684-
timestamp = _last_timestamp + 1
685-
_last_timestamp = timestamp
697+
if _last_timestamp_v1 is not None and timestamp <= _last_timestamp_v1:
698+
timestamp = _last_timestamp_v1 + 1
699+
_last_timestamp_v1 = timestamp
686700
if clock_seq is None:
687701
import random
688702
clock_seq = random.getrandbits(14) # instead of stable storage
@@ -719,14 +733,97 @@ def uuid5(namespace, name):
719733
hash = sha1(namespace.bytes + name).digest()
720734
return UUID(bytes=hash[:16], version=5)
721735

736+
_last_timestamp_v6 = None
737+
738+
def uuid6(node=None, clock_seq=None):
739+
"""Similar to :func:`uuid1` but where fields are ordered differently
740+
for improved DB locality.
741+
742+
More precisely, given a 60-bit timestamp value as specified for UUIDv1,
743+
for UUIDv6 the first 48 most significant bits are stored first, followed
744+
by the 4-bit version (same position), followed by the remaining 12 bits
745+
of the original 60-bit timestamp.
746+
"""
747+
global _last_timestamp_v6
748+
import time
749+
nanoseconds = time.time_ns()
750+
# 0x01b21dd213814000 is the number of 100-ns intervals between the
751+
# UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
752+
timestamp = nanoseconds // 100 + 0x01b21dd213814000
753+
if _last_timestamp_v6 is not None and timestamp <= _last_timestamp_v6:
754+
timestamp = _last_timestamp_v6 + 1
755+
_last_timestamp_v6 = timestamp
756+
if clock_seq is None:
757+
import random
758+
clock_seq = random.getrandbits(14) # instead of stable storage
759+
time_hi_and_mid = (timestamp >> 12) & 0xffffffffffff
760+
time_ver_and_lo = timestamp & 0xffff
761+
var_and_clock_s = clock_seq & 0x3fff
762+
if node is None:
763+
node = getnode()
764+
int_uuid_6 = time_hi_and_mid << 80
765+
int_uuid_6 |= time_ver_and_lo << 64
766+
int_uuid_6 |= var_and_clock_s << 48
767+
int_uuid_6 |= node & 0xffffffffffff
768+
return UUID(int=int_uuid_6, version=6)
769+
770+
_last_timestamp_v7 = None
771+
772+
def uuid7():
773+
"""Generate a UUID from a Unix timestamp in milliseconds and random bits."""
774+
global _last_timestamp_v7
775+
import time
776+
nanoseconds = time.time_ns()
777+
timestamp_ms = nanoseconds // 10 ** 6 # may be improved
778+
if _last_timestamp_v7 is not None and timestamp_ms <= _last_timestamp_v7:
779+
timestamp_ms = _last_timestamp_v7 + 1
780+
_last_timestamp_v7 = timestamp_ms
781+
int_uuid_7 = (timestamp_ms & 0xffffffffffff) << 80
782+
# Ideally, we would have 'rand_a' = first 12 bits of 'rand'
783+
# and 'rand_b' = lowest 62 bits, but it is easier to test
784+
# when we pick 'rand_a' from the lowest bits of 'rand' and
785+
# 'rand_b' from the next 62 bits, ignoring the first bits
786+
# of 'rand'.
787+
rand = int.from_bytes(os.urandom(19)) # 76 random bits (ignore 2 first)
788+
int_uuid_7 |= (rand & 0x0fff) << 64 # rand_a
789+
int_uuid_7 |= (rand >> 12) & 0x3fffffffffffffff # rand_b
790+
return UUID(int=int_uuid_7, version=7)
791+
792+
def uuid8(a=None, b=None, c=None):
793+
"""Generate a UUID from three custom blocks.
794+
795+
'a' is the first 48-bit chunk of the UUID (octets 0-5);
796+
'b' is the mid 12-bit chunk (octets 6-7);
797+
'c' is the last 62-bit chunk (octets 8-15).
798+
799+
When a value is not specified, a random value is generated.
800+
"""
801+
if a is None:
802+
import random
803+
a = random.getrandbits(48)
804+
if b is None:
805+
import random
806+
b = random.getrandbits(12)
807+
if c is None:
808+
import random
809+
c = random.getrandbits(62)
810+
811+
int_uuid_8 = (a & 0xffffffffffff) << 80
812+
int_uuid_8 |= (b & 0xfff) << 64
813+
int_uuid_8 |= c & 0x3fffffffffffffff
814+
return UUID(int=int_uuid_8, version=8)
815+
722816

723817
def main():
724818
"""Run the uuid command line interface."""
725819
uuid_funcs = {
726820
"uuid1": uuid1,
727821
"uuid3": uuid3,
728822
"uuid4": uuid4,
729-
"uuid5": uuid5
823+
"uuid5": uuid5,
824+
"uuid6": uuid6,
825+
"uuid7": uuid7,
826+
"uuid8": uuid8,
730827
}
731828
uuid_namespace_funcs = ("uuid3", "uuid5")
732829
namespaces = {

0 commit comments

Comments
 (0)