From 818d41748fd4cbf8f8f22ae0cb55d41bebd9f53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:29:57 +0200 Subject: [PATCH 01/53] add implementation --- Lib/uuid.py | 123 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 13 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index c286eac38e1ef4..fe0a6e5361035b 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -1,8 +1,9 @@ r"""UUID objects (universally unique identifiers) according to RFC 4122. This module provides immutable UUID objects (class UUID) and the functions -uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5 -UUIDs as specified in RFC 4122. +uuid1(), uuid3(), uuid4(), uuid5() for generating version 1 to 8 UUIDs as +specified in RFC 4122 (superseeded by RFC 9562 but will still be referred +to as RFC 4122 in the future for compatibility purposes). If all you want is a unique ID, you should probably call uuid1() or uuid4(). Note that uuid1() may compromise privacy since it creates a UUID containing @@ -129,7 +130,7 @@ class UUID: variant the UUID variant (one of the constants RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE) - version the UUID version number (1 through 5, meaningful only + version the UUID version number (1 through 8, meaningful only when the variant is RFC_4122) 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, 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 1 <= version <= 5: + if not 1 <= version <= 8: raise ValueError('illegal version number') - # Set the variant to RFC 4122. + # Set the variant to RFC 9562. int &= ~(0xc000 << 48) int |= 0x8000 << 48 # Set the version number. @@ -297,17 +298,29 @@ def bytes_le(self): @property def fields(self): + if self.version == 6: + # the first field should be a 32-bit integer + return (self.time_hi, self.time_mid, self.time_hi_version, + self.clock_seq_hi_variant, self.clock_seq_low, self.node) return (self.time_low, self.time_mid, self.time_hi_version, self.clock_seq_hi_variant, self.clock_seq_low, self.node) @property def time_low(self): + if self.version == 6: + return (self.int >> 64) & 0x0fff return self.int >> 96 @property def time_mid(self): return (self.int >> 80) & 0xffff + @property + def time_hi(self): + if self.version == 6: + return self.int >> 96 + return (self.int >> 64) & 0x0fff + @property def time_hi_version(self): return (self.int >> 64) & 0xffff @@ -322,8 +335,9 @@ def clock_seq_low(self): @property def time(self): - return (((self.time_hi_version & 0x0fff) << 48) | - (self.time_mid << 32) | self.time_low) + if self.version == 6: + return (self.time_hi << 28) | (self.time_mid << 12) | self.time_low + return (self.time_hi << 48) | (self.time_mid << 32) | self.time_low @property def clock_seq(self): @@ -656,7 +670,7 @@ def getnode(): assert False, '_random_getnode() returned invalid value: {}'.format(_node) -_last_timestamp = None +_last_timestamp_v1 = None def uuid1(node=None, clock_seq=None): """Generate a UUID from a host ID, sequence number, and the current time. @@ -674,15 +688,15 @@ def uuid1(node=None, clock_seq=None): is_safe = SafeUUID.unknown return UUID(bytes=uuid_time, is_safe=is_safe) - global _last_timestamp + global _last_timestamp_v1 import time 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_timestamp is not None and timestamp <= _last_timestamp: - timestamp = _last_timestamp + 1 - _last_timestamp = timestamp + if _last_timestamp_v1 is not None and timestamp <= _last_timestamp_v1: + timestamp = _last_timestamp_v1 + 1 + _last_timestamp_v1 = timestamp if clock_seq is None: import random clock_seq = random.getrandbits(14) # instead of stable storage @@ -719,6 +733,86 @@ def uuid5(namespace, name): hash = sha1(namespace.bytes + name).digest() return UUID(bytes=hash[:16], version=5) +_last_timestamp_v6 = None + +def uuid6(node=None, clock_seq=None): + """Similar to :func:`uuid1` but where fields are ordered differently + for improved DB locality. + + More precisely, given a 60-bit timestamp value as specified for UUIDv1, + for UUIDv6 the first 48 most significant bits are stored first, followed + by the 4-bit version (same position), followed by the remaining 12 bits + of the original 60-bit timestamp. + """ + global _last_timestamp_v6 + import time + 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_timestamp_v6 is not None and timestamp <= _last_timestamp_v6: + timestamp = _last_timestamp_v6 + 1 + _last_timestamp_v6 = timestamp + if clock_seq is None: + import random + clock_seq = random.getrandbits(14) # instead of stable storage + time_hi_and_mid = (timestamp >> 12) & 0xffffffffffff + time_ver_and_lo = timestamp & 0xffff + var_and_clock_s = clock_seq & 0x3fff + if node is None: + node = getnode() + int_uuid_6 = time_hi_and_mid << 80 + int_uuid_6 |= time_ver_and_lo << 64 + int_uuid_6 |= var_and_clock_s << 48 + int_uuid_6 |= node & 0xffffffffffff + return UUID(int=int_uuid_6, version=6) + +_last_timestamp_v7 = None + +def uuid7(): + """Generate a UUID from a Unix timestamp in milliseconds and random bits.""" + global _last_timestamp_v7 + import time + nanoseconds = time.time_ns() + timestamp_ms = nanoseconds // 10 ** 6 # may be improved + if _last_timestamp_v7 is not None and timestamp_ms <= _last_timestamp_v7: + timestamp_ms = _last_timestamp_v7 + 1 + _last_timestamp_v7 = timestamp_ms + int_uuid_7 = (timestamp_ms & 0xffffffffffff) << 80 + # Ideally, we would have 'rand_a' = first 12 bits of 'rand' + # and 'rand_b' = lowest 62 bits, but it is easier to test + # when we pick 'rand_a' from the lowest bits of 'rand' and + # 'rand_b' from the next 62 bits, ignoring the first bits + # of 'rand'. + rand = int.from_bytes(os.urandom(19)) # 76 random bits (ignore 2 first) + int_uuid_7 |= (rand & 0x0fff) << 64 # rand_a + int_uuid_7 |= (rand >> 12) & 0x3fffffffffffffff # rand_b + return UUID(int=int_uuid_7, version=7) + +def uuid8(a=None, b=None, c=None): + """Generate a UUID from three custom blocks. + + 'a' is the first 48-bit chunk of the UUID (octets 0-5); + 'b' is the mid 12-bit chunk (octets 6-7); + 'c' is the last 62-bit chunk (octets 8-15). + + When a value is not specified, a random value is generated. + """ + if a is None: + import random + a = random.getrandbits(48) + if b is None: + import random + b = random.getrandbits(12) + if c is None: + import random + c = random.getrandbits(62) + + int_uuid_8 = (a & 0xffffffffffff) << 80 + int_uuid_8 |= (b & 0xfff) << 64 + int_uuid_8 |= c & 0x3fffffffffffffff + return UUID(int=int_uuid_8, version=8) + def main(): """Run the uuid command line interface.""" @@ -726,7 +820,10 @@ def main(): "uuid1": uuid1, "uuid3": uuid3, "uuid4": uuid4, - "uuid5": uuid5 + "uuid5": uuid5, + "uuid6": uuid6, + "uuid7": uuid7, + "uuid8": uuid8, } uuid_namespace_funcs = ("uuid3", "uuid5") namespaces = { From 16565f29d7197d0a2eaf25d66f4cd2ff052b6f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:30:00 +0200 Subject: [PATCH 02/53] add tests --- Lib/test/test_uuid.py | 86 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index e177464c00f7a6..ed1c18ad8f62d6 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1,3 +1,4 @@ +import random import unittest from test import support from test.support import import_helper @@ -10,6 +11,7 @@ import pickle import sys import weakref +from itertools import product from unittest import mock py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid']) @@ -267,7 +269,7 @@ def test_exceptions(self): # Version number out of range. badvalue(lambda: self.uuid.UUID('00'*16, version=0)) - badvalue(lambda: self.uuid.UUID('00'*16, version=6)) + badvalue(lambda: self.uuid.UUID('00'*16, version=42)) # Integer value out of range. badvalue(lambda: self.uuid.UUID(int=-1)) @@ -588,7 +590,7 @@ def test_uuid1_bogus_return_value(self): def test_uuid1_time(self): with mock.patch.object(self.uuid, '_generate_time_safe', None), \ - mock.patch.object(self.uuid, '_last_timestamp', None), \ + mock.patch.object(self.uuid, '_last_timestamp_v1', None), \ mock.patch.object(self.uuid, 'getnode', return_value=93328246233727), \ mock.patch('time.time_ns', return_value=1545052026752910643), \ mock.patch('random.getrandbits', return_value=5317): # guaranteed to be random @@ -596,7 +598,7 @@ def test_uuid1_time(self): self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f')) with mock.patch.object(self.uuid, '_generate_time_safe', None), \ - mock.patch.object(self.uuid, '_last_timestamp', None), \ + mock.patch.object(self.uuid, '_last_timestamp_v1', None), \ mock.patch('time.time_ns', return_value=1545052026752910643): u = self.uuid.uuid1(node=93328246233727, clock_seq=5317) self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f')) @@ -681,6 +683,84 @@ def test_uuid5(self): equal(u, self.uuid.UUID(v)) equal(str(u), v) + def test_uuid6(self): + equal = self.assertEqual + u = self.uuid.uuid6() + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 6) + + fake_nanoseconds = 1545052026752910643 + fake_node_value = 93328246233727 + fake_clock_seq = 5317 + with mock.patch.object(self.uuid, '_generate_time_safe', None), \ + mock.patch.object(self.uuid, '_last_timestamp_v6', None), \ + mock.patch.object(self.uuid, 'getnode', return_value=fake_node_value), \ + mock.patch('time.time_ns', return_value=fake_nanoseconds), \ + mock.patch('random.getrandbits', return_value=fake_clock_seq): + u = self.uuid.uuid6() + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 6) + + # time_hi time_mid time_lo + # 00011110100100000001111111001010 0111101001010101 101110010010 + timestamp = 137643448267529106 + equal(u.time_hi, 0b00011110100100000001111111001010) + equal(u.time_mid, 0b0111101001010101) + equal(u.time_low, 0b101110010010) + equal(u.time, timestamp) + equal(u.fields[0], u.time_hi) + equal(u.fields[1], u.time_mid) + equal(u.fields[2], u.time_hi_version) + + def test_uuid7(self): + equal = self.assertEqual + u = self.uuid.uuid7() + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 7) + + fake_nanoseconds = 1545052026752910643 + # some fake 74 = 12 + 62 random bits speared over 76 bits + # are generated by generating a random 76-bit number, and + # split into chunks of 62 (hi) and 12 (lo) bits. It is a + rand_a = random.getrandbits(12) + rand_b = random.getrandbits(62) + fake_bytes = (rand_b << 12) | rand_a + fake_bytes = fake_bytes.to_bytes(19, byteorder='big') + + with mock.patch.object(self.uuid, '_last_timestamp_v7', None), \ + mock.patch('time.time_ns', return_value=fake_nanoseconds), \ + mock.patch('os.urandom', return_value=fake_bytes): + u = self.uuid.uuid7() + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 7) + fake_milliseconds = (fake_nanoseconds // 1_000_000) & 0xffffffffffff + equal((u.int >> 80) & 0xffffffffffff, fake_milliseconds) + equal((u.int >> 64) & 0x0fff, rand_a) + equal(u.int & 0x3fffffffffffffff, rand_b) + + def test_uuid8(self): + equal = self.assertEqual + u = self.uuid.uuid8() + + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 8) + + for (_, hi, mid, lo) in product( + range(10), # repeat 10 times + [None, 0, random.getrandbits(48)], + [None, 0, random.getrandbits(12)], + [None, 0, random.getrandbits(62)], + ): + u = self.uuid.uuid8(hi, mid, lo) + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 8) + if hi is not None: + equal((u.int >> 80) & 0xffffffffffff, hi) + if mid is not None: + equal((u.int >> 64) & 0xfff, mid) + if lo is not None: + equal(u.int & 0x3fffffffffffffff, lo) + @support.requires_fork() def testIssue8621(self): # On at least some versions of OSX self.uuid.uuid4 generates From e6c1d5f591127e37c4d36afcffd2b8bded9ee965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:30:02 +0200 Subject: [PATCH 03/53] add docs --- Doc/library/uuid.rst | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 0f2d7820cb25c8..0115f781b8acd6 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -149,9 +149,12 @@ which relays any information about the UUID's safety, using this enumeration: .. attribute:: UUID.version - The UUID version number (1 through 5, meaningful only when the variant is + The UUID version number (1 through 8, meaningful only when the variant is :const:`RFC_4122`). + .. versionadded:: 3.14 + Added UUID versions 6, 7, and 8. + .. attribute:: UUID.is_safe An enumeration of :class:`SafeUUID` which indicates whether the platform @@ -216,6 +219,34 @@ The :mod:`uuid` module defines the following functions: .. index:: single: uuid5 + +.. function:: uuid6(node=None, clock_seq=None) + + TODO + + .. versionadded:: 3.14 + +.. index:: single: uuid6 + + +.. function:: uuid7() + + TODO + + .. versionadded:: 3.14 + +.. index:: single: uuid7 + + +.. function:: uuid8(a=None, b=None, c=None) + + TODO + + .. versionadded:: 3.14 + +.. index:: single: uuid8 + + The :mod:`uuid` module defines the following namespace identifiers for use with :func:`uuid3` or :func:`uuid5`. From cbaaff44dfb675ff766b87c7288716fece3b5533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:33:36 +0200 Subject: [PATCH 04/53] add WhatsNew --- Doc/whatsnew/3.14.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index a102af13a08362..875258c7a85121 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -118,6 +118,17 @@ symtable (Contributed by Bénédikt Tran in :gh:`120029`.) +uuid +---- + +* Add support for UUID versions 6, 7, and 8 as specified by + :rfc:`9562` to the :mod:`uuid` module: + + * :meth:`~uuid.uuid6` + * :meth:`~uuid.uuid7` + * :meth:`~uuid.uuid8` + + (Contributed by Bénédikt Tran in :gh:`89083`.) Optimizations ============= From 4ef04b907d3dcfabe2ce005ee034617abebf3b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:33:41 +0200 Subject: [PATCH 05/53] blurb --- .../next/Library/2024-06-17-17-31-27.gh-issue-89083.nW00Yq.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-06-17-17-31-27.gh-issue-89083.nW00Yq.rst diff --git a/Misc/NEWS.d/next/Library/2024-06-17-17-31-27.gh-issue-89083.nW00Yq.rst b/Misc/NEWS.d/next/Library/2024-06-17-17-31-27.gh-issue-89083.nW00Yq.rst new file mode 100644 index 00000000000000..55cb8bd637c2f5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-17-17-31-27.gh-issue-89083.nW00Yq.rst @@ -0,0 +1,2 @@ +Add :func:`~uuid.uuid6`, :func:`~uuid.uuid7` and :func:`~uuid.uuid8` to the +:mod:`uuid` module as specified by :rfc:`9562`. Patch by Bénédikt Tran. From 943d13e5c5661ffe381586cf93301f0581c2c674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:55:00 +0200 Subject: [PATCH 06/53] fix a mask --- Lib/uuid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index fe0a6e5361035b..d4298481c02aaf 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -757,7 +757,7 @@ def uuid6(node=None, clock_seq=None): import random clock_seq = random.getrandbits(14) # instead of stable storage time_hi_and_mid = (timestamp >> 12) & 0xffffffffffff - time_ver_and_lo = timestamp & 0xffff + time_ver_and_lo = timestamp & 0x0fff var_and_clock_s = clock_seq & 0x3fff if node is None: node = getnode() From 8344e64309ce633e42059cae3207864f387c4554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:23:01 +0200 Subject: [PATCH 07/53] fix random bytes generation --- Lib/test/test_uuid.py | 33 +++++++++++++++++---------------- Lib/uuid.py | 4 ++-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index ed1c18ad8f62d6..0fb167640482f7 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -721,22 +721,23 @@ def test_uuid7(self): fake_nanoseconds = 1545052026752910643 # some fake 74 = 12 + 62 random bits speared over 76 bits # are generated by generating a random 76-bit number, and - # split into chunks of 62 (hi) and 12 (lo) bits. It is a - rand_a = random.getrandbits(12) - rand_b = random.getrandbits(62) - fake_bytes = (rand_b << 12) | rand_a - fake_bytes = fake_bytes.to_bytes(19, byteorder='big') - - with mock.patch.object(self.uuid, '_last_timestamp_v7', None), \ - mock.patch('time.time_ns', return_value=fake_nanoseconds), \ - mock.patch('os.urandom', return_value=fake_bytes): - u = self.uuid.uuid7() - equal(u.variant, self.uuid.RFC_4122) - equal(u.version, 7) - fake_milliseconds = (fake_nanoseconds // 1_000_000) & 0xffffffffffff - equal((u.int >> 80) & 0xffffffffffff, fake_milliseconds) - equal((u.int >> 64) & 0x0fff, rand_a) - equal(u.int & 0x3fffffffffffffff, rand_b) + # split into chunks of 62 (hi) and 12 (lo) bits. + for _ in range(100): + rand_a = random.getrandbits(12) + rand_b = random.getrandbits(62) + fake_bytes = (rand_b << 12) | rand_a + fake_bytes = fake_bytes.to_bytes(10, byteorder='big') + + with mock.patch.object(self.uuid, '_last_timestamp_v7', None), \ + mock.patch('time.time_ns', return_value=fake_nanoseconds), \ + mock.patch('os.urandom', return_value=fake_bytes): + u = self.uuid.uuid7() + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 7) + fake_milliseconds = (fake_nanoseconds // 1_000_000) & 0xffffffffffff + equal((u.int >> 80) & 0xffffffffffff, fake_milliseconds) + equal((u.int >> 64) & 0x0fff, rand_a) + equal(u.int & 0x3fffffffffffffff, rand_b) def test_uuid8(self): equal = self.assertEqual diff --git a/Lib/uuid.py b/Lib/uuid.py index d4298481c02aaf..d9539105d9b8f7 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -782,9 +782,9 @@ def uuid7(): # Ideally, we would have 'rand_a' = first 12 bits of 'rand' # and 'rand_b' = lowest 62 bits, but it is easier to test # when we pick 'rand_a' from the lowest bits of 'rand' and - # 'rand_b' from the next 62 bits, ignoring the first bits + # 'rand_b' from the next 62 bits, ignoring the 6 first bits # of 'rand'. - rand = int.from_bytes(os.urandom(19)) # 76 random bits (ignore 2 first) + rand = int.from_bytes(os.urandom(10)) # 80 random bits (ignore 6 first) int_uuid_7 |= (rand & 0x0fff) << 64 # rand_a int_uuid_7 |= (rand >> 12) & 0x3fffffffffffffff # rand_b return UUID(int=int_uuid_7, version=7) From 295d82d513717adb96d9faab7e255dbedb237d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:38:39 +0200 Subject: [PATCH 08/53] fixup some comments --- Lib/uuid.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index d9539105d9b8f7..0d0c226ef5e824 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -1,9 +1,9 @@ r"""UUID objects (universally unique identifiers) according to RFC 4122. This module provides immutable UUID objects (class UUID) and the functions -uuid1(), uuid3(), uuid4(), uuid5() for generating version 1 to 8 UUIDs as -specified in RFC 4122 (superseeded by RFC 9562 but will still be referred -to as RFC 4122 in the future for compatibility purposes). +uuid1(), uuid3(), uuid4(), uuid5(), uuid6(), uuid7(), and uuid8() for +generating version 1 to 8 UUIDs as specified in RFC 4122 (superseeded +by RFC 9562 but still referred to as RFC 4122 for compatibility purposes). If all you want is a unique ID, you should probably call uuid1() or uuid4(). Note that uuid1() may compromise privacy since it creates a UUID containing @@ -217,7 +217,7 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, if version is not None: if not 1 <= version <= 8: raise ValueError('illegal version number') - # Set the variant to RFC 9562. + # Set the variant to RFC 4122. int &= ~(0xc000 << 48) int |= 0x8000 << 48 # Set the version number. From 1aaa483d248a9e038351f4778321b3efdde797a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 21 Jun 2024 23:40:33 +0200 Subject: [PATCH 09/53] Update Lib/uuid.py --- Lib/uuid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index 0d0c226ef5e824..285a8d15a1d0cd 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -774,7 +774,7 @@ def uuid7(): global _last_timestamp_v7 import time nanoseconds = time.time_ns() - timestamp_ms = nanoseconds // 10 ** 6 # may be improved + timestamp_ms = nanoseconds // 1_000_000 if _last_timestamp_v7 is not None and timestamp_ms <= _last_timestamp_v7: timestamp_ms = _last_timestamp_v7 + 1 _last_timestamp_v7 = timestamp_ms From 6847b776f3dd2af1150df5d3592606a2319cbb8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 22 Jun 2024 11:47:26 +0200 Subject: [PATCH 10/53] Update Doc/whatsnew/3.14.rst --- Doc/whatsnew/3.14.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 875258c7a85121..8e4bda42cdddc7 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -124,9 +124,9 @@ uuid * Add support for UUID versions 6, 7, and 8 as specified by :rfc:`9562` to the :mod:`uuid` module: - * :meth:`~uuid.uuid6` - * :meth:`~uuid.uuid7` - * :meth:`~uuid.uuid8` + * :func:`~uuid.uuid6` + * :func:`~uuid.uuid7` + * :func:`~uuid.uuid8` (Contributed by Bénédikt Tran in :gh:`89083`.) From 4d9862ed764845e59620e6cefa8484455766c35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 22 Jun 2024 20:40:52 +0200 Subject: [PATCH 11/53] revert modifications on properties for now --- Lib/test/test_uuid.py | 18 +++++++++--------- Lib/uuid.py | 24 ++++++++++-------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 0fb167640482f7..39d11eecb5c373 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -699,18 +699,18 @@ def test_uuid6(self): mock.patch('random.getrandbits', return_value=fake_clock_seq): u = self.uuid.uuid6() equal(u.variant, self.uuid.RFC_4122) - equal(u.version, 6) + equal(u.version, 0b0110) # 6 - # time_hi time_mid time_lo - # 00011110100100000001111111001010 0111101001010101 101110010010 timestamp = 137643448267529106 - equal(u.time_hi, 0b00011110100100000001111111001010) - equal(u.time_mid, 0b0111101001010101) - equal(u.time_low, 0b101110010010) + # 32 (hi) | 16 (mid) | 12 (lo) == 60 bits of timestamp + equal(timestamp, 0b_00011110100100000001111111001010_0111101001010101_101110010010) equal(u.time, timestamp) - equal(u.fields[0], u.time_hi) - equal(u.fields[1], u.time_mid) - equal(u.fields[2], u.time_hi_version) + equal(u.fields[0], 0b00011110100100000001111111001010) # 32 high bits of time + equal(u.fields[1], 0b0111101001010101) # 16 bits of time (mid) + equal(u.fields[2], 0b0110_101110010010) # 4 bits of version + 12 low bits of time + equal(u.fields[3], 0b10_010100) # 2 bits of variant + 6 high bits of clock_seq + equal(u.fields[4], 0b11000101) # 8 low bits of clock_seq + equal(u.fields[5], fake_node_value) def test_uuid7(self): equal = self.assertEqual diff --git a/Lib/uuid.py b/Lib/uuid.py index 285a8d15a1d0cd..8bbdfcc83dfe18 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -298,29 +298,17 @@ def bytes_le(self): @property def fields(self): - if self.version == 6: - # the first field should be a 32-bit integer - return (self.time_hi, self.time_mid, self.time_hi_version, - self.clock_seq_hi_variant, self.clock_seq_low, self.node) return (self.time_low, self.time_mid, self.time_hi_version, self.clock_seq_hi_variant, self.clock_seq_low, self.node) @property def time_low(self): - if self.version == 6: - return (self.int >> 64) & 0x0fff return self.int >> 96 @property def time_mid(self): return (self.int >> 80) & 0xffff - @property - def time_hi(self): - if self.version == 6: - return self.int >> 96 - return (self.int >> 64) & 0x0fff - @property def time_hi_version(self): return (self.int >> 64) & 0xffff @@ -336,8 +324,16 @@ def clock_seq_low(self): @property def time(self): if self.version == 6: - return (self.time_hi << 28) | (self.time_mid << 12) | self.time_low - return (self.time_hi << 48) | (self.time_mid << 32) | self.time_low + # In version 1, the first field contains the 32 MSBs + # and the field after the version contains the 12 LSBs. + time_hi = self.int >> 96 # == fields[0] + time_lo = (self.int >> 64) & 0x0fff # == fields[2] & 0x0fff + return time_hi << 28 | (self.time_mid << 12) | time_lo + else: + # In version 1, the first field contains the 32 LSBs + # and the field after the version contains the 12 MSBs. + time_hi = (self.int >> 64) & 0x0fff # == fields[2] & 0x0fff + return time_hi << 48 | (self.time_mid << 32) | self.time_low @property def clock_seq(self): From 08607f7b66fbdfea4dffdedadf5fc072930ec7af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 22 Jun 2024 21:09:27 +0200 Subject: [PATCH 12/53] fixup --- Lib/uuid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index 8bbdfcc83dfe18..a209e663debe6f 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -324,7 +324,7 @@ def clock_seq_low(self): @property def time(self): if self.version == 6: - # In version 1, the first field contains the 32 MSBs + # In version 6, the first field contains the 32 MSBs # and the field after the version contains the 12 LSBs. time_hi = self.int >> 96 # == fields[0] time_lo = (self.int >> 64) & 0x0fff # == fields[2] & 0x0fff From 55edd0c04d6578c0b1da280ba981db4f41f46b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 22 Jun 2024 21:12:58 +0200 Subject: [PATCH 13/53] update variable names --- Lib/uuid.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index a209e663debe6f..4c54338f121212 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -323,17 +323,17 @@ def clock_seq_low(self): @property def time(self): + field_1 = self.int >> 96 + field_3_no_ver = (self.int >> 64) & 0x0fff + if self.version == 6: # In version 6, the first field contains the 32 MSBs # and the field after the version contains the 12 LSBs. - time_hi = self.int >> 96 # == fields[0] - time_lo = (self.int >> 64) & 0x0fff # == fields[2] & 0x0fff - return time_hi << 28 | (self.time_mid << 12) | time_lo + return field_1 << 28 | (self.time_mid << 12) | field_3_no_ver else: # In version 1, the first field contains the 32 LSBs # and the field after the version contains the 12 MSBs. - time_hi = (self.int >> 64) & 0x0fff # == fields[2] & 0x0fff - return time_hi << 48 | (self.time_mid << 32) | self.time_low + return field_3_no_ver << 48 | (self.time_mid << 32) | field_1 @property def clock_seq(self): From 5b151340614b277b9753268eee9c8a1ee5b68122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:52:11 +0200 Subject: [PATCH 14/53] remove references to v7 and v8 --- Doc/library/uuid.rst | 24 +------- Doc/whatsnew/3.14.rst | 8 +-- Lib/test/test_uuid.py | 52 ---------------- Lib/uuid.py | 59 ++----------------- ...4-06-17-17-31-27.gh-issue-89083.nW00Yq.rst | 4 +- 5 files changed, 12 insertions(+), 135 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 0115f781b8acd6..93d31a3c3f74b6 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -12,7 +12,7 @@ This module provides immutable :class:`UUID` objects (the :class:`UUID` class) and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5` for -generating version 1, 3, 4, and 5 UUIDs as specified in :rfc:`4122`. +generating version 1, 3, 4, 5, and 6 UUIDs as specified in :rfc:`4122`. If all you want is a unique ID, you should probably call :func:`uuid1` or :func:`uuid4`. Note that :func:`uuid1` may compromise privacy since it creates @@ -149,11 +149,11 @@ which relays any information about the UUID's safety, using this enumeration: .. attribute:: UUID.version - The UUID version number (1 through 8, meaningful only when the variant is + The UUID version number (1 through 6, meaningful only when the variant is :const:`RFC_4122`). .. versionadded:: 3.14 - Added UUID versions 6, 7, and 8. + Added UUID version 6. .. attribute:: UUID.is_safe @@ -229,24 +229,6 @@ The :mod:`uuid` module defines the following functions: .. index:: single: uuid6 -.. function:: uuid7() - - TODO - - .. versionadded:: 3.14 - -.. index:: single: uuid7 - - -.. function:: uuid8(a=None, b=None, c=None) - - TODO - - .. versionadded:: 3.14 - -.. index:: single: uuid8 - - The :mod:`uuid` module defines the following namespace identifiers for use with :func:`uuid3` or :func:`uuid5`. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 8e4bda42cdddc7..e19543a7013588 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -121,12 +121,8 @@ symtable uuid ---- -* Add support for UUID versions 6, 7, and 8 as specified by - :rfc:`9562` to the :mod:`uuid` module: - - * :func:`~uuid.uuid6` - * :func:`~uuid.uuid7` - * :func:`~uuid.uuid8` +* Add support for UUID version 6 via :func:`uuid.uuid6` as specified + in :rfc:`9562`. (Contributed by Bénédikt Tran in :gh:`89083`.) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 39d11eecb5c373..5be52a9de377b1 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1,4 +1,3 @@ -import random import unittest from test import support from test.support import import_helper @@ -11,7 +10,6 @@ import pickle import sys import weakref -from itertools import product from unittest import mock py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid']) @@ -712,56 +710,6 @@ def test_uuid6(self): equal(u.fields[4], 0b11000101) # 8 low bits of clock_seq equal(u.fields[5], fake_node_value) - def test_uuid7(self): - equal = self.assertEqual - u = self.uuid.uuid7() - equal(u.variant, self.uuid.RFC_4122) - equal(u.version, 7) - - fake_nanoseconds = 1545052026752910643 - # some fake 74 = 12 + 62 random bits speared over 76 bits - # are generated by generating a random 76-bit number, and - # split into chunks of 62 (hi) and 12 (lo) bits. - for _ in range(100): - rand_a = random.getrandbits(12) - rand_b = random.getrandbits(62) - fake_bytes = (rand_b << 12) | rand_a - fake_bytes = fake_bytes.to_bytes(10, byteorder='big') - - with mock.patch.object(self.uuid, '_last_timestamp_v7', None), \ - mock.patch('time.time_ns', return_value=fake_nanoseconds), \ - mock.patch('os.urandom', return_value=fake_bytes): - u = self.uuid.uuid7() - equal(u.variant, self.uuid.RFC_4122) - equal(u.version, 7) - fake_milliseconds = (fake_nanoseconds // 1_000_000) & 0xffffffffffff - equal((u.int >> 80) & 0xffffffffffff, fake_milliseconds) - equal((u.int >> 64) & 0x0fff, rand_a) - equal(u.int & 0x3fffffffffffffff, rand_b) - - def test_uuid8(self): - equal = self.assertEqual - u = self.uuid.uuid8() - - equal(u.variant, self.uuid.RFC_4122) - equal(u.version, 8) - - for (_, hi, mid, lo) in product( - range(10), # repeat 10 times - [None, 0, random.getrandbits(48)], - [None, 0, random.getrandbits(12)], - [None, 0, random.getrandbits(62)], - ): - u = self.uuid.uuid8(hi, mid, lo) - equal(u.variant, self.uuid.RFC_4122) - equal(u.version, 8) - if hi is not None: - equal((u.int >> 80) & 0xffffffffffff, hi) - if mid is not None: - equal((u.int >> 64) & 0xfff, mid) - if lo is not None: - equal(u.int & 0x3fffffffffffffff, lo) - @support.requires_fork() def testIssue8621(self): # On at least some versions of OSX self.uuid.uuid4 generates diff --git a/Lib/uuid.py b/Lib/uuid.py index 4c54338f121212..7065f33148c31a 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -1,9 +1,9 @@ r"""UUID objects (universally unique identifiers) according to RFC 4122. This module provides immutable UUID objects (class UUID) and the functions -uuid1(), uuid3(), uuid4(), uuid5(), uuid6(), uuid7(), and uuid8() for -generating version 1 to 8 UUIDs as specified in RFC 4122 (superseeded -by RFC 9562 but still referred to as RFC 4122 for compatibility purposes). +uuid1(), uuid3(), uuid4(), uuid5() and uuid6() for generating version 1, 3, +4, 5, and 6 UUIDs as specified in RFC 4122 (superseeded by RFC 9562 but still +referred to as RFC 4122 for compatibility purposes). If all you want is a unique ID, you should probably call uuid1() or uuid4(). Note that uuid1() may compromise privacy since it creates a UUID containing @@ -130,7 +130,7 @@ class UUID: variant the UUID variant (one of the constants RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE) - version the UUID version number (1 through 8, meaningful only + version the UUID version number (1 through 6, meaningful only when the variant is RFC_4122) is_safe An enum indicating whether the UUID has been generated in @@ -215,7 +215,7 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, 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 1 <= version <= 8: + if not 1 <= version <= 6: raise ValueError('illegal version number') # Set the variant to RFC 4122. int &= ~(0xc000 << 48) @@ -763,53 +763,6 @@ def uuid6(node=None, clock_seq=None): int_uuid_6 |= node & 0xffffffffffff return UUID(int=int_uuid_6, version=6) -_last_timestamp_v7 = None - -def uuid7(): - """Generate a UUID from a Unix timestamp in milliseconds and random bits.""" - global _last_timestamp_v7 - import time - nanoseconds = time.time_ns() - timestamp_ms = nanoseconds // 1_000_000 - if _last_timestamp_v7 is not None and timestamp_ms <= _last_timestamp_v7: - timestamp_ms = _last_timestamp_v7 + 1 - _last_timestamp_v7 = timestamp_ms - int_uuid_7 = (timestamp_ms & 0xffffffffffff) << 80 - # Ideally, we would have 'rand_a' = first 12 bits of 'rand' - # and 'rand_b' = lowest 62 bits, but it is easier to test - # when we pick 'rand_a' from the lowest bits of 'rand' and - # 'rand_b' from the next 62 bits, ignoring the 6 first bits - # of 'rand'. - rand = int.from_bytes(os.urandom(10)) # 80 random bits (ignore 6 first) - int_uuid_7 |= (rand & 0x0fff) << 64 # rand_a - int_uuid_7 |= (rand >> 12) & 0x3fffffffffffffff # rand_b - return UUID(int=int_uuid_7, version=7) - -def uuid8(a=None, b=None, c=None): - """Generate a UUID from three custom blocks. - - 'a' is the first 48-bit chunk of the UUID (octets 0-5); - 'b' is the mid 12-bit chunk (octets 6-7); - 'c' is the last 62-bit chunk (octets 8-15). - - When a value is not specified, a random value is generated. - """ - if a is None: - import random - a = random.getrandbits(48) - if b is None: - import random - b = random.getrandbits(12) - if c is None: - import random - c = random.getrandbits(62) - - int_uuid_8 = (a & 0xffffffffffff) << 80 - int_uuid_8 |= (b & 0xfff) << 64 - int_uuid_8 |= c & 0x3fffffffffffffff - return UUID(int=int_uuid_8, version=8) - - def main(): """Run the uuid command line interface.""" uuid_funcs = { @@ -818,8 +771,6 @@ def main(): "uuid4": uuid4, "uuid5": uuid5, "uuid6": uuid6, - "uuid7": uuid7, - "uuid8": uuid8, } uuid_namespace_funcs = ("uuid3", "uuid5") namespaces = { diff --git a/Misc/NEWS.d/next/Library/2024-06-17-17-31-27.gh-issue-89083.nW00Yq.rst b/Misc/NEWS.d/next/Library/2024-06-17-17-31-27.gh-issue-89083.nW00Yq.rst index 55cb8bd637c2f5..f4bda53d1a67d5 100644 --- a/Misc/NEWS.d/next/Library/2024-06-17-17-31-27.gh-issue-89083.nW00Yq.rst +++ b/Misc/NEWS.d/next/Library/2024-06-17-17-31-27.gh-issue-89083.nW00Yq.rst @@ -1,2 +1,2 @@ -Add :func:`~uuid.uuid6`, :func:`~uuid.uuid7` and :func:`~uuid.uuid8` to the -:mod:`uuid` module as specified by :rfc:`9562`. Patch by Bénédikt Tran. +Add :func:`uuid.uuid6` for generating UUIDv6 objects as specified in +:rfc:`9562`. Patch by Bénédikt Tran. From c3d474519e63ad34777e3f3f26b9793bc0fececf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:18:57 +0200 Subject: [PATCH 15/53] add UUIDv8 implementation --- Lib/uuid.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index 4d4f06cfc9ebbe..2ff64fc39fb83e 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -1,8 +1,9 @@ r"""UUID objects (universally unique identifiers) according to RFC 4122. This module provides immutable UUID objects (class UUID) and the functions -uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5 -UUIDs as specified in RFC 4122. +uuid1(), uuid3(), uuid4(), uuid5(), and uuid8() for generating version 1, 3, +4, 5, and 8 UUIDs as specified in RFC 4122 (superseeded by RFC 9562 but still +referred to as RFC 4122 for compatibility purposes). If all you want is a unique ID, you should probably call uuid1() or uuid4(). Note that uuid1() may compromise privacy since it creates a UUID containing @@ -129,7 +130,7 @@ class UUID: variant the UUID variant (one of the constants RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE) - version the UUID version number (1 through 5, meaningful only + version the UUID version number (1 through 8, meaningful only when the variant is RFC_4122) is_safe An enum indicating whether the UUID has been generated in @@ -214,7 +215,7 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, 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 1 <= version <= 5: + if not 1 <= version <= 8: raise ValueError('illegal version number') # Set the variant to RFC 4122. int &= ~(0xc000 << 48) @@ -719,6 +720,27 @@ def uuid5(namespace, name): hash = sha1(namespace.bytes + name).digest() return UUID(bytes=hash[:16], version=5) +def uuid8(a=None, b=None, c=None): + """Generate a UUID from three custom blocks. + 'a' is the first 48-bit chunk of the UUID (octets 0-5); + 'b' is the mid 12-bit chunk (octets 6-7); + 'c' is the last 62-bit chunk (octets 8-15). + When a value is not specified, a random value is generated. + """ + if a is None: + import random + a = random.getrandbits(48) + if b is None: + import random + b = random.getrandbits(12) + if c is None: + import random + c = random.getrandbits(62) + + int_uuid_8 = (a & 0xffffffffffff) << 80 + int_uuid_8 |= (b & 0xfff) << 64 + int_uuid_8 |= c & 0x3fffffffffffffff + return UUID(int=int_uuid_8, version=8) def main(): """Run the uuid command line interface.""" @@ -726,7 +748,8 @@ def main(): "uuid1": uuid1, "uuid3": uuid3, "uuid4": uuid4, - "uuid5": uuid5 + "uuid5": uuid5, + "uuid8": uuid8, } uuid_namespace_funcs = ("uuid3", "uuid5") namespaces = { From 392d289f549d6412dd9e9cef009edc37fcd4f334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:19:01 +0200 Subject: [PATCH 16/53] add tests --- Lib/test/test_uuid.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index e177464c00f7a6..f89f14b82a0fe9 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -8,8 +8,10 @@ import io import os import pickle +import random import sys import weakref +from itertools import product from unittest import mock py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid']) @@ -267,7 +269,7 @@ def test_exceptions(self): # Version number out of range. badvalue(lambda: self.uuid.UUID('00'*16, version=0)) - badvalue(lambda: self.uuid.UUID('00'*16, version=6)) + badvalue(lambda: self.uuid.UUID('00'*16, version=42)) # Integer value out of range. badvalue(lambda: self.uuid.UUID(int=-1)) @@ -681,6 +683,29 @@ def test_uuid5(self): equal(u, self.uuid.UUID(v)) equal(str(u), v) + def test_uuid8(self): + equal = self.assertEqual + u = self.uuid.uuid8() + + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 8) + + for (_, hi, mid, lo) in product( + range(10), # repeat 10 times + [None, 0, random.getrandbits(48)], + [None, 0, random.getrandbits(12)], + [None, 0, random.getrandbits(62)], + ): + u = self.uuid.uuid8(hi, mid, lo) + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 8) + if hi is not None: + equal((u.int >> 80) & 0xffffffffffff, hi) + if mid is not None: + equal((u.int >> 64) & 0xfff, mid) + if lo is not None: + equal(u.int & 0x3fffffffffffffff, lo) + @support.requires_fork() def testIssue8621(self): # On at least some versions of OSX self.uuid.uuid4 generates From 26889ea442cf421ac383cb970ff88c8b3a566e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:19:10 +0200 Subject: [PATCH 17/53] blurb --- .../next/Library/2024-08-22-12-12-35.gh-issue-89083.b6zFh0.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-08-22-12-12-35.gh-issue-89083.b6zFh0.rst diff --git a/Misc/NEWS.d/next/Library/2024-08-22-12-12-35.gh-issue-89083.b6zFh0.rst b/Misc/NEWS.d/next/Library/2024-08-22-12-12-35.gh-issue-89083.b6zFh0.rst new file mode 100644 index 00000000000000..d37d585d51b490 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-08-22-12-12-35.gh-issue-89083.b6zFh0.rst @@ -0,0 +1,2 @@ +Add :func:`uuid.uuid8` for generating UUIDv8 objects as specified in +:rfc:`9562`. Patch by Bénédikt Tran From 44b66e6c82a4d1aefdcf1a6cbb3ffe02d53596d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:19:17 +0200 Subject: [PATCH 18/53] add What's New entry --- Doc/whatsnew/3.14.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index a34dc639ad2a94..7730dc528c59d3 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -199,6 +199,14 @@ symtable (Contributed by Bénédikt Tran in :gh:`120029`.) +uuid +---- + +* Add support for UUID version 8 via :func:`uuid.uuid8` as specified + in :rfc:`9562`. + + (Contributed by Bénédikt Tran in :gh:`89083`.) + .. Add improved modules above alphabetically, not here at the end. Optimizations From 7be6dc4b402b3d0e68b3ba3eb44247e9aca2d216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:19:19 +0200 Subject: [PATCH 19/53] add docs --- Doc/library/uuid.rst | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 0f2d7820cb25c8..f4b1a1e734ebc5 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -12,7 +12,7 @@ This module provides immutable :class:`UUID` objects (the :class:`UUID` class) and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5` for -generating version 1, 3, 4, and 5 UUIDs as specified in :rfc:`4122`. +generating version 1, 3, 4, 5, and 8 UUIDs as specified in :rfc:`4122`. If all you want is a unique ID, you should probably call :func:`uuid1` or :func:`uuid4`. Note that :func:`uuid1` may compromise privacy since it creates @@ -149,9 +149,13 @@ which relays any information about the UUID's safety, using this enumeration: .. attribute:: UUID.version - The UUID version number (1 through 5, meaningful only when the variant is + The UUID version number (1 through 8, meaningful only when the variant is :const:`RFC_4122`). + .. versionchanged:: 3.14 + Added UUID version 8. + + .. attribute:: UUID.is_safe An enumeration of :class:`SafeUUID` which indicates whether the platform @@ -216,6 +220,16 @@ The :mod:`uuid` module defines the following functions: .. index:: single: uuid5 + +.. function:: uuid8(a=None, b=None, c=None) + + TODO + + .. versionadded:: 3.14 + +.. index:: single: uuid8 + + The :mod:`uuid` module defines the following namespace identifiers for use with :func:`uuid3` or :func:`uuid5`. From a276857c1cb5e1a176b6a8afb843be58a579e4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:11:38 +0200 Subject: [PATCH 20/53] add test vectors --- Lib/test/test_uuid.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 5be52a9de377b1..f87db04a777cb2 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -690,11 +690,13 @@ def test_uuid6(self): fake_nanoseconds = 1545052026752910643 fake_node_value = 93328246233727 fake_clock_seq = 5317 - with mock.patch.object(self.uuid, '_generate_time_safe', None), \ - mock.patch.object(self.uuid, '_last_timestamp_v6', None), \ - mock.patch.object(self.uuid, 'getnode', return_value=fake_node_value), \ - mock.patch('time.time_ns', return_value=fake_nanoseconds), \ - mock.patch('random.getrandbits', return_value=fake_clock_seq): + with ( + mock.patch.object(self.uuid, '_generate_time_safe', None), + mock.patch.object(self.uuid, '_last_timestamp_v6', None), + mock.patch.object(self.uuid, 'getnode', return_value=fake_node_value), + mock.patch('time.time_ns', return_value=fake_nanoseconds), + mock.patch('random.getrandbits', return_value=fake_clock_seq) + ): u = self.uuid.uuid6() equal(u.variant, self.uuid.RFC_4122) equal(u.version, 0b0110) # 6 @@ -710,6 +712,30 @@ def test_uuid6(self): equal(u.fields[4], 0b11000101) # 8 low bits of clock_seq equal(u.fields[5], fake_node_value) + def test_uuid6_test_vectors(self): + # https://www.rfc-editor.org/rfc/rfc9562#name-test-vectors + fake_nanoseconds = (0x1EC9414C232AB00 - 0x01B21DD213814000) * 100 + # https://www.rfc-editor.org/rfc/rfc9562#name-example-of-a-uuidv6-value + node = 0x9F6BDECED846 + clock_seq = (0b11 << 12) | 0x3C8 + + with ( + mock.patch.object(self.uuid, '_generate_time_safe', None), + mock.patch.object(self.uuid, '_last_timestamp_v6', None), + mock.patch('time.time_ns', return_value=fake_nanoseconds) + ): + u = self.uuid.uuid6(node=node, clock_seq=clock_seq) + self.assertEqual(str(u).upper(), '1EC9414C-232A-6B00-B3C8-9F6BDECED846') + # 32 16 4 12 2 14 48 + # time_hi | time_mid | ver | time_lo | var | clock_seq | node + self.assertEqual(u.int & 0xFFFFFFFFFFFF, node) + self.assertEqual((u.int >> 48) & 0x3FFF, clock_seq) + self.assertEqual((u.int >> 62) & 0x3, 0b10) + self.assertEqual((u.int >> 64) & 0xFFF, 0xB00) + self.assertEqual((u.int >> 76) & 0xF, 0x6) + self.assertEqual((u.int >> 80) & 0xFFFF, 0x232A) + self.assertEqual((u.int >> 96) & 0xFFFFFFFF, 0x1EC9414C) + @support.requires_fork() def testIssue8621(self): # On at least some versions of OSX self.uuid.uuid4 generates From 8ba3d8b7d781e6a6e74f2b7563044bab7e46d90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:58:38 +0200 Subject: [PATCH 21/53] Improve hexadecimal masks reading --- Lib/uuid.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index 2ff64fc39fb83e..fac7e32deb6275 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -722,10 +722,12 @@ def uuid5(namespace, name): def uuid8(a=None, b=None, c=None): """Generate a UUID from three custom blocks. - 'a' is the first 48-bit chunk of the UUID (octets 0-5); - 'b' is the mid 12-bit chunk (octets 6-7); - 'c' is the last 62-bit chunk (octets 8-15). - When a value is not specified, a random value is generated. + + * 'a' is the first 48-bit chunk of the UUID (octets 0-5); + * 'b' is the mid 12-bit chunk (octets 6-7); + * 'c' is the last 62-bit chunk (octets 8-15). + + When a value is not specified, a pseudo-random value is generated. """ if a is None: import random @@ -736,10 +738,9 @@ def uuid8(a=None, b=None, c=None): if c is None: import random c = random.getrandbits(62) - - int_uuid_8 = (a & 0xffffffffffff) << 80 + int_uuid_8 = (a & 0xffff_ffff_ffff) << 80 int_uuid_8 |= (b & 0xfff) << 64 - int_uuid_8 |= c & 0x3fffffffffffffff + int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff return UUID(int=int_uuid_8, version=8) def main(): From a14ae9bf5e51ef0bd3bc1bfd068fab5921181e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:19:49 +0200 Subject: [PATCH 22/53] add uniqueness test --- Lib/test/test_uuid.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index f89f14b82a0fe9..39f65e2847e0ec 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -706,6 +706,14 @@ def test_uuid8(self): if lo is not None: equal(u.int & 0x3fffffffffffffff, lo) + def test_uuid8_uniqueness(self): + """Test that UUIDv8-generated values are unique (up to a negligible + probability of failure).""" + u1 = self.uuid.uuid8() + u2 = self.uuid.uuid8() + self.assertNotEqual(u1.int, u2.int) + self.assertEqual(u1.version, u2.version) + @support.requires_fork() def testIssue8621(self): # On at least some versions of OSX self.uuid.uuid4 generates From 7a169c96dc1c3a16d66d2856a144bacc1c1ebf0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:27:47 +0200 Subject: [PATCH 23/53] Update mentions to RFC 4122 to RFC 4122/9562 when possible. --- Doc/library/uuid.rst | 18 ++++++++++-------- Lib/uuid.py | 11 +++++------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index f4b1a1e734ebc5..111a313bf3bdec 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -1,8 +1,8 @@ -:mod:`!uuid` --- UUID objects according to :rfc:`4122` +:mod:`!uuid` --- UUID objects according to :rfc:`9562` ====================================================== .. module:: uuid - :synopsis: UUID objects (universally unique identifiers) according to RFC 4122 + :synopsis: UUID objects (universally unique identifiers) according to RFC 9562 .. moduleauthor:: Ka-Ping Yee .. sectionauthor:: George Yoshida @@ -12,7 +12,7 @@ This module provides immutable :class:`UUID` objects (the :class:`UUID` class) and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5` for -generating version 1, 3, 4, 5, and 8 UUIDs as specified in :rfc:`4122`. +generating version 1, 3, 4, 5, and 8 UUIDs as specified in :rfc:`9562`. If all you want is a unique ID, you should probably call :func:`uuid1` or :func:`uuid4`. Note that :func:`uuid1` may compromise privacy since it creates @@ -65,7 +65,7 @@ which relays any information about the UUID's safety, using this enumeration: Exactly one of *hex*, *bytes*, *bytes_le*, *fields*, or *int* must be given. The *version* argument is optional; if given, the resulting UUID will have its - variant and version number set according to :rfc:`4122`, overriding bits in the + variant and version number set according to :rfc:`9562`, overriding bits in the given *hex*, *bytes*, *bytes_le*, *fields*, or *int*. Comparison of UUID objects are made by way of comparing their @@ -137,7 +137,7 @@ which relays any information about the UUID's safety, using this enumeration: .. attribute:: UUID.urn - The UUID as a URN as specified in :rfc:`4122`. + The UUID as a URN as specified in :rfc:`9562`. .. attribute:: UUID.variant @@ -172,7 +172,7 @@ The :mod:`uuid` module defines the following functions: runs, it may launch a separate program, which could be quite slow. If all attempts to obtain the hardware address fail, we choose a random 48-bit number with the multicast bit (least significant bit of the first octet) - set to 1 as recommended in :rfc:`4122`. "Hardware address" means the MAC + set to 1 as recommended in :rfc:`9562`. "Hardware address" means the MAC address of a network interface. On a machine with multiple network interfaces, universally administered MAC addresses (i.e. where the second least significant bit of the first octet is *unset*) will be preferred over @@ -266,7 +266,9 @@ of the :attr:`~UUID.variant` attribute: .. data:: RFC_4122 - Specifies the UUID layout given in :rfc:`4122`. + Specifies the UUID layout given in :rfc:`4122`. This constant is kept + for backward compatibility even though :rfc:`4122` has been superseeded + by :rfc:`9562`. .. data:: RESERVED_MICROSOFT @@ -281,7 +283,7 @@ of the :attr:`~UUID.variant` attribute: .. seealso:: - :rfc:`4122` - A Universally Unique IDentifier (UUID) URN Namespace + :rfc:`9562` - A Universally Unique IDentifier (UUID) URN Namespace This specification defines a Uniform Resource Name namespace for UUIDs, the internal format of UUIDs, and methods of generating UUIDs. diff --git a/Lib/uuid.py b/Lib/uuid.py index fac7e32deb6275..9c6ad9643cf6d5 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -1,9 +1,8 @@ -r"""UUID objects (universally unique identifiers) according to RFC 4122. +r"""UUID objects (universally unique identifiers) according to RFC 4122/9562. This module provides immutable UUID objects (class UUID) and the functions uuid1(), uuid3(), uuid4(), uuid5(), and uuid8() for generating version 1, 3, -4, 5, and 8 UUIDs as specified in RFC 4122 (superseeded by RFC 9562 but still -referred to as RFC 4122 for compatibility purposes). +4, 5, and 8 UUIDs as specified in RFC 4122/9562. If all you want is a unique ID, you should probably call uuid1() or uuid4(). Note that uuid1() may compromise privacy since it creates a UUID containing @@ -125,7 +124,7 @@ class UUID: int the UUID as a 128-bit integer - urn the UUID as a URN as specified in RFC 4122 + urn the UUID as a URN as specified in RFC 4122/9562 variant the UUID variant (one of the constants RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE) @@ -217,7 +216,7 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, if version is not None: if not 1 <= version <= 8: raise ValueError('illegal version number') - # Set the variant to RFC 4122. + # Set the variant to RFC 4122/9562. int &= ~(0xc000 << 48) int |= 0x8000 << 48 # Set the version number. @@ -356,7 +355,7 @@ def variant(self): @property def version(self): - # The version bits are only meaningful for RFC 4122 UUIDs. + # The version bits are only meaningful for RFC 4122/9562 UUIDs. if self.variant == RFC_4122: return int((self.int >> 76) & 0xf) From b082c9085dd65051add3536ffc3abcd82ec9717f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:41:15 +0200 Subject: [PATCH 24/53] Update docs --- Doc/library/uuid.rst | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 111a313bf3bdec..11b15294535f50 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -12,7 +12,8 @@ This module provides immutable :class:`UUID` objects (the :class:`UUID` class) and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5` for -generating version 1, 3, 4, 5, and 8 UUIDs as specified in :rfc:`9562`. +generating version 1, 3, 4, 5, and 8 UUIDs as specified in :rfc:`9562` (which +superseeds :rfc:`4122`). If all you want is a unique ID, you should probably call :func:`uuid1` or :func:`uuid4`. Note that :func:`uuid1` may compromise privacy since it creates @@ -172,7 +173,7 @@ The :mod:`uuid` module defines the following functions: runs, it may launch a separate program, which could be quite slow. If all attempts to obtain the hardware address fail, we choose a random 48-bit number with the multicast bit (least significant bit of the first octet) - set to 1 as recommended in :rfc:`9562`. "Hardware address" means the MAC + set to 1 as recommended in :rfc:`4122`. "Hardware address" means the MAC address of a network interface. On a machine with multiple network interfaces, universally administered MAC addresses (i.e. where the second least significant bit of the first octet is *unset*) will be preferred over @@ -223,7 +224,14 @@ The :mod:`uuid` module defines the following functions: .. function:: uuid8(a=None, b=None, c=None) - TODO + Generate a pseudo-random UUID according to + :rfc:`RFC 9562, §5.8 <9562#section-5.8>`. + + When specified, the parameters *a*, *b* and *c* are expected to be + positive integers of 48, 12 and 62 bits respectively. If they exceed + their expected bit count, only their least significant bits are kept; + non-specified arguments are substituted for a pseudo-random integer of + appropriate size. .. versionadded:: 3.14 @@ -299,7 +307,7 @@ The :mod:`uuid` module can be executed as a script from the command line. .. code-block:: sh - python -m uuid [-h] [-u {uuid1,uuid3,uuid4,uuid5}] [-n NAMESPACE] [-N NAME] + python -m uuid [-h] [-u {uuid1,uuid3,uuid4,uuid5,uuid8}] [-n NAMESPACE] [-N NAME] The following options are accepted: From 5e97cc32343023f6c415ee042d00d3eeaef4913d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:44:00 +0100 Subject: [PATCH 25/53] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/uuid.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 11b15294535f50..658e8491f56bf1 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -13,7 +13,7 @@ This module provides immutable :class:`UUID` objects (the :class:`UUID` class) and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5` for generating version 1, 3, 4, 5, and 8 UUIDs as specified in :rfc:`9562` (which -superseeds :rfc:`4122`). +supersedes :rfc:`4122`). If all you want is a unique ID, you should probably call :func:`uuid1` or :func:`uuid4`. Note that :func:`uuid1` may compromise privacy since it creates @@ -275,7 +275,7 @@ of the :attr:`~UUID.variant` attribute: .. data:: RFC_4122 Specifies the UUID layout given in :rfc:`4122`. This constant is kept - for backward compatibility even though :rfc:`4122` has been superseeded + for backward compatibility even though :rfc:`4122` has been superseded by :rfc:`9562`. From 051f34e734bbbfa41563dce8129eb31d9ada329e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:44:51 +0100 Subject: [PATCH 26/53] Update Lib/test/test_uuid.py --- Lib/test/test_uuid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 39f65e2847e0ec..7bd26a8ca34b62 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -707,8 +707,8 @@ def test_uuid8(self): equal(u.int & 0x3fffffffffffffff, lo) def test_uuid8_uniqueness(self): - """Test that UUIDv8-generated values are unique (up to a negligible - probability of failure).""" + # Test that UUIDv8-generated values are unique + # (up to a negligible probability of failure). u1 = self.uuid.uuid8() u2 = self.uuid.uuid8() self.assertNotEqual(u1.int, u2.int) From bdf9a77e7eebf4d33d7bd9d9480c9784907fcff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:46:32 +0100 Subject: [PATCH 27/53] Apply suggestions from code review --- Doc/library/uuid.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 658e8491f56bf1..6166c22caedf81 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -153,7 +153,7 @@ which relays any information about the UUID's safety, using this enumeration: The UUID version number (1 through 8, meaningful only when the variant is :const:`RFC_4122`). - .. versionchanged:: 3.14 + .. versionchanged:: next Added UUID version 8. @@ -233,7 +233,7 @@ The :mod:`uuid` module defines the following functions: non-specified arguments are substituted for a pseudo-random integer of appropriate size. - .. versionadded:: 3.14 + .. versionadded:: next .. index:: single: uuid8 From c188cedace230e28e1ab10d165fdd00abb6805c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:19:11 +0100 Subject: [PATCH 28/53] post-merge --- Doc/whatsnew/3.14.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 54f2fde9ea2c9f..81fc1a94029d14 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -522,7 +522,6 @@ uuid * Add support for UUID version 6 via :func:`uuid.uuid6` as specified in :rfc:`9562`. - (Contributed by Bénédikt Tran in :gh:`89083`.) * Add support for UUID version 8 via :func:`uuid.uuid8` as specified From 7e5d3648ebc86b61989c2354c9db6e588e07ed3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:37:03 +0100 Subject: [PATCH 29/53] update docs --- Doc/library/uuid.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index fa5cf9c98a09ee..e0d656969eff3b 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -224,7 +224,16 @@ The :mod:`uuid` module defines the following functions: .. function:: uuid6(node=None, clock_seq=None) - TODO + Generate a UUID from a sequence number and the current time according to + :rfc:`9562`. + This is an alternative to :func:`uuid1` to improve DB locality. + + When *node* is not specified, :func:`getnode` is used to obtain the hardware + address as a 48-bit positive integer. When a sequence number *clock_seq* is + not specified, a pseudo-random 14-bit positive integer is generated. + + If *node* or *clock_seq* exceed their expected bit count, only their least + significant bits are kept. .. versionadded:: next From b8ddc02efaf8ff0d0e7f3656deee6a93800d691d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:37:14 +0100 Subject: [PATCH 30/53] improve readability --- Lib/uuid.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index 9f2f6b6f3107e6..416a8b1e1390c8 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -750,8 +750,8 @@ def uuid6(node=None, clock_seq=None): _last_timestamp_v6 = timestamp if clock_seq is None: import random - clock_seq = random.getrandbits(14) # instead of stable storage - time_hi_and_mid = (timestamp >> 12) & 0xffffffffffff + clock_seq = random.getrandbits(14) # instead of stable storage + time_hi_and_mid = (timestamp >> 12) & 0xffff_ffff_ffff time_ver_and_lo = timestamp & 0x0fff var_and_clock_s = clock_seq & 0x3fff if node is None: @@ -759,7 +759,7 @@ def uuid6(node=None, clock_seq=None): int_uuid_6 = time_hi_and_mid << 80 int_uuid_6 |= time_ver_and_lo << 64 int_uuid_6 |= var_and_clock_s << 48 - int_uuid_6 |= node & 0xffffffffffff + int_uuid_6 |= node & 0xffff_ffff_ffff return UUID(int=int_uuid_6, version=6) def uuid8(a=None, b=None, c=None): From 384a02e837b9f1b69748764833241434b0fd44d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:08:12 +0100 Subject: [PATCH 31/53] improve test readability --- Lib/test/test_uuid.py | 50 +++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index eaf941722d06a5..58f86b2a6c9091 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -689,9 +689,9 @@ def test_uuid6(self): equal(u.variant, self.uuid.RFC_4122) equal(u.version, 6) - fake_nanoseconds = 1545052026752910643 - fake_node_value = 93328246233727 - fake_clock_seq = 5317 + fake_nanoseconds = 0x1571_20a1_de1a_c533 + fake_node_value = 0x54e1_acf6_da7f + fake_clock_seq = 0x14c5 with ( mock.patch.object(self.uuid, '_generate_time_safe', None), mock.patch.object(self.uuid, '_last_timestamp_v6', None), @@ -701,25 +701,29 @@ def test_uuid6(self): ): u = self.uuid.uuid6() equal(u.variant, self.uuid.RFC_4122) - equal(u.version, 0b0110) # 6 - - timestamp = 137643448267529106 - # 32 (hi) | 16 (mid) | 12 (lo) == 60 bits of timestamp - equal(timestamp, 0b_00011110100100000001111111001010_0111101001010101_101110010010) - equal(u.time, timestamp) - equal(u.fields[0], 0b00011110100100000001111111001010) # 32 high bits of time - equal(u.fields[1], 0b0111101001010101) # 16 bits of time (mid) - equal(u.fields[2], 0b0110_101110010010) # 4 bits of version + 12 low bits of time - equal(u.fields[3], 0b10_010100) # 2 bits of variant + 6 high bits of clock_seq - equal(u.fields[4], 0b11000101) # 8 low bits of clock_seq + equal(u.version, 6) + + # 32 (top) | 16 (mid) | 12 (low) == 60 (timestamp) + equal(u.time, 0x1e901fca_7a55_b92) + equal(u.fields[0], 0x1e901fca) # 32 top bits of time + equal(u.fields[1], 0x7a55) # 16 mid bits of time + # 4 bits of version + 12 low bits of time + equal((u.fields[2] >> 12) & 0xf, 6) + equal((u.fields[2] & 0xfff), 0xb92) + # 2 bits of variant + 6 high bits of clock_seq + equal((u.fields[3] >> 6) & 0xf, 2) + equal(u.fields[3] & 0x3f, fake_clock_seq >> 8) + # 8 low bits of clock_seq + equal(u.fields[4], fake_clock_seq & 0xff) equal(u.fields[5], fake_node_value) def test_uuid6_test_vectors(self): # https://www.rfc-editor.org/rfc/rfc9562#name-test-vectors - fake_nanoseconds = (0x1EC9414C232AB00 - 0x01B21DD213814000) * 100 + # (separators are put at the 12th and 28th bits) + fake_nanoseconds = (0x1ec9414c_232a_b00 - 0x1b21dd21_3814_000) * 100 # https://www.rfc-editor.org/rfc/rfc9562#name-example-of-a-uuidv6-value - node = 0x9F6BDECED846 - clock_seq = (0b11 << 12) | 0x3C8 + node = 0x9f6bdeced846 + clock_seq = (0b11 << 12) | 0x3c8 with ( mock.patch.object(self.uuid, '_generate_time_safe', None), @@ -730,13 +734,13 @@ def test_uuid6_test_vectors(self): self.assertEqual(str(u).upper(), '1EC9414C-232A-6B00-B3C8-9F6BDECED846') # 32 16 4 12 2 14 48 # time_hi | time_mid | ver | time_lo | var | clock_seq | node - self.assertEqual(u.int & 0xFFFFFFFFFFFF, node) - self.assertEqual((u.int >> 48) & 0x3FFF, clock_seq) + self.assertEqual(u.int & 0xffff_ffff_ffff, node) + self.assertEqual((u.int >> 48) & 0x3fff, clock_seq) self.assertEqual((u.int >> 62) & 0x3, 0b10) - self.assertEqual((u.int >> 64) & 0xFFF, 0xB00) - self.assertEqual((u.int >> 76) & 0xF, 0x6) - self.assertEqual((u.int >> 80) & 0xFFFF, 0x232A) - self.assertEqual((u.int >> 96) & 0xFFFFFFFF, 0x1EC9414C) + self.assertEqual((u.int >> 64) & 0xfff, 0xb00) + self.assertEqual((u.int >> 76) & 0xf, 0x6) + self.assertEqual((u.int >> 80) & 0xffff, 0x232a) + self.assertEqual((u.int >> 96) & 0xffff_ffff, 0x1ec9_414c) def test_uuid8(self): equal = self.assertEqual From e4a7198f2e89014641d7da4f33b38c7d84cf92b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:23:01 +0100 Subject: [PATCH 32/53] improve test coverage --- Lib/test/test_uuid.py | 105 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 58f86b2a6c9091..89a82468df9ead 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1,4 +1,5 @@ import unittest +import uuid from test import support from test.support import import_helper import builtins @@ -693,7 +694,6 @@ def test_uuid6(self): fake_node_value = 0x54e1_acf6_da7f fake_clock_seq = 0x14c5 with ( - mock.patch.object(self.uuid, '_generate_time_safe', None), mock.patch.object(self.uuid, '_last_timestamp_v6', None), mock.patch.object(self.uuid, 'getnode', return_value=fake_node_value), mock.patch('time.time_ns', return_value=fake_nanoseconds), @@ -717,30 +717,111 @@ def test_uuid6(self): equal(u.fields[4], fake_clock_seq & 0xff) equal(u.fields[5], fake_node_value) + def test_uuid6_uniqueness(self): + # Test that UUIDv6-generated values are unique. + + # Unlike UUIDv8, only 62 bits can be randomized for UUIDv6. + # In practice, however, it remains unlikely to generate two + # identical UUIDs for the same 60-bit timestamp if neither + # the node ID nor the clock sequence is specified. + uuids = {self.uuid.uuid6() for _ in range(1000)} + self.assertEqual(len(uuids), 1000) + + # Within the same 60-bit timestamp, and for identical node IDs, + # it is likely to have collision on 14-bits. + timestamp = 0x1ec9414c_232a_b00 + fake_nanoseconds = (timestamp - 0x1b21dd21_3814_000) * 100 + + with mock.patch('time.time_ns', return_value=fake_nanoseconds): + def gen(): + with mock.patch.object(self.uuid, '_last_timestamp_v6', None): + return self.uuid.uuid6(node=0, clock_seq=None) + + # By the birthday paradox, sampling N = 1024 UUIDs with the same + # node ID and timestamp results in duplicates with probabillity + # very close to 1 (the probability of not having a duplicate is + # of order 1E-15). + N = 1024 + uuids = {gen() for _ in range(N)} + self.assertTrue(all(u.node == 0 for u in uuids)) + self.assertTrue(all(u.time == timestamp for u in uuids)) + self.assertLess(len(uuids), N, 'collision property does not hold') + + def test_uuid6_node(self): + # Make sure the given node ID appears in the UUID. + # + # Note: when no node ID is specified, the same logic as for UUIDv1 + # is applied to UUIDv6. In particular, there is need to test that + # getnode() correctly returns positive integers of exactly 48 bits + # since this is done in test_uuid1_eui64(). + self.assertLessEqual(self.uuid.uuid6().node.bit_length(), 48) + + equal = self.assertEqual + equal(self.uuid.uuid6(0).node, 0) + equal(self.uuid.uuid6(0xffff_ffff_ffff).node, 0xffff_ffff_ffff) + + for _ in range(10): + for node in ( + random.getrandbits(24), + random.getrandbits(48), + random.getrandbits(72), # node with > 48 bits is truncated + ): + with self.subTest(node): + u = self.uuid.uuid6(node) + equal(u.node, node & 0xffff_ffff_ffff) + + def test_uuid6_clock_seq(self): + # Make sure the supplied clock sequence appears in the UUID. + # + # Note: currently it is not possible to use the 'clock_seq' + # field as it only works for version 1. + # + # For UUIDv6, clock sequence bits are stored from bit 48 to bit 62, + # with the convention that the least significant bit is bit 0 and + # the most significant bit is bit 127. + get_clock_seq = lambda u: (u.int >> 48) & 0x3fff + + u = self.uuid.uuid6() + self.assertLessEqual(get_clock_seq(u).bit_length(), 14) + equal = self.assertEqual + + for _ in range(10): + for clock_seq in ( + random.getrandbits(7), + random.getrandbits(14), + random.getrandbits(28), # clock_seq with > 14 bits is truncated + ): + node = random.getrandbits(48) + with self.subTest(node=node, clock_seq=clock_seq): + u = self.uuid.uuid6(node, clock_seq) + self.assertEqual(get_clock_seq(u), clock_seq & 0x3fff) + def test_uuid6_test_vectors(self): + equal = self.assertEqual # https://www.rfc-editor.org/rfc/rfc9562#name-test-vectors # (separators are put at the 12th and 28th bits) - fake_nanoseconds = (0x1ec9414c_232a_b00 - 0x1b21dd21_3814_000) * 100 + timestamp = 0x1ec9414c_232a_b00 + fake_nanoseconds = (timestamp - 0x1b21dd21_3814_000) * 100 # https://www.rfc-editor.org/rfc/rfc9562#name-example-of-a-uuidv6-value node = 0x9f6bdeced846 - clock_seq = (0b11 << 12) | 0x3c8 + clock_seq = (3 << 12) | 0x3c8 with ( - mock.patch.object(self.uuid, '_generate_time_safe', None), mock.patch.object(self.uuid, '_last_timestamp_v6', None), mock.patch('time.time_ns', return_value=fake_nanoseconds) ): u = self.uuid.uuid6(node=node, clock_seq=clock_seq) - self.assertEqual(str(u).upper(), '1EC9414C-232A-6B00-B3C8-9F6BDECED846') + equal(str(u).upper(), '1EC9414C-232A-6B00-B3C8-9F6BDECED846') # 32 16 4 12 2 14 48 # time_hi | time_mid | ver | time_lo | var | clock_seq | node - self.assertEqual(u.int & 0xffff_ffff_ffff, node) - self.assertEqual((u.int >> 48) & 0x3fff, clock_seq) - self.assertEqual((u.int >> 62) & 0x3, 0b10) - self.assertEqual((u.int >> 64) & 0xfff, 0xb00) - self.assertEqual((u.int >> 76) & 0xf, 0x6) - self.assertEqual((u.int >> 80) & 0xffff, 0x232a) - self.assertEqual((u.int >> 96) & 0xffff_ffff, 0x1ec9_414c) + equal(u.time, timestamp) + equal(u.int & 0xffff_ffff_ffff, node) + equal((u.int >> 48) & 0x3fff, clock_seq) + equal((u.int >> 62) & 0x3, 0b10) + equal((u.int >> 64) & 0xfff, 0xb00) + equal((u.int >> 76) & 0xf, 0x6) + equal((u.int >> 80) & 0xffff, 0x232a) + equal((u.int >> 96) & 0xffff_ffff, 0x1ec9_414c) def test_uuid8(self): equal = self.assertEqual From aed5839c526a18bb7a79ba7d848614ee331228af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:03:33 +0100 Subject: [PATCH 33/53] update docs --- Doc/library/uuid.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index e0d656969eff3b..41095811e85f2a 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -11,9 +11,9 @@ -------------- This module provides immutable :class:`UUID` objects (the :class:`UUID` class) -and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5` for -generating version 1, 3, 4, 5, 6, and 8 UUIDs as specified in :rfc:`9562` (which -supersedes :rfc:`4122`). +and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5`, +and :func:`uuid6` for generating version 1, 3, 4, 5, 6, and 8 UUIDs as +specified in :rfc:`9562` (which supersedes :rfc:`4122`). If all you want is a unique ID, you should probably call :func:`uuid1` or :func:`uuid4`. Note that :func:`uuid1` may compromise privacy since it creates @@ -325,7 +325,7 @@ The :mod:`uuid` module can be executed as a script from the command line. .. code-block:: sh - python -m uuid [-h] [-u {uuid1,uuid3,uuid4,uuid5,uuid8}] [-n NAMESPACE] [-N NAME] + python -m uuid [-h] [-u {uuid1,uuid3,uuid4,uuid5,uuid6,uuid8}] [-n NAMESPACE] [-N NAME] The following options are accepted: @@ -341,6 +341,9 @@ The following options are accepted: Specify the function name to use to generate the uuid. By default :func:`uuid4` is used. + .. versionadded:: next + Allow generating UUID version 6. + .. option:: -n --namespace From 5ad6268363ae1d3d9468ad9e81cc0cd6e8938dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:51:33 +0100 Subject: [PATCH 34/53] post-merge --- Lib/test/test_uuid.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 89a82468df9ead..4dc565219fd7f3 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1,7 +1,3 @@ -import unittest -import uuid -from test import support -from test.support import import_helper import builtins import contextlib import copy @@ -11,10 +7,14 @@ import pickle import random import sys +import unittest import weakref from itertools import product from unittest import mock +from test import support +from test.support import import_helper + py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid']) c_uuid = import_helper.import_fresh_module('uuid', fresh=['_uuid']) From d49855ddd51a7d137ef7a27d13c024f6e219c98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:53:01 +0100 Subject: [PATCH 35/53] post-merge --- Doc/library/uuid.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 41095811e85f2a..0677d9e9a05320 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -12,8 +12,8 @@ This module provides immutable :class:`UUID` objects (the :class:`UUID` class) and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5`, -and :func:`uuid6` for generating version 1, 3, 4, 5, 6, and 8 UUIDs as -specified in :rfc:`9562` (which supersedes :rfc:`4122`). +:func:`uuid6`, and :func:`uuid.uuid8` for generating version 1, 3, 4, 5, 6, +and 8 UUIDs as specified in :rfc:`9562` (which supersedes :rfc:`4122`). If all you want is a unique ID, you should probably call :func:`uuid1` or :func:`uuid4`. Note that :func:`uuid1` may compromise privacy since it creates From a5682f871354df4bcfadb2e273e44b203bdde412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:54:54 +0100 Subject: [PATCH 36/53] fix comment --- Lib/test/test_uuid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 4dc565219fd7f3..7131ba9dab2b72 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -728,7 +728,7 @@ def test_uuid6_uniqueness(self): self.assertEqual(len(uuids), 1000) # Within the same 60-bit timestamp, and for identical node IDs, - # it is likely to have collision on 14-bits. + # it is likely to have collision on the 14-bit clock sequence. timestamp = 0x1ec9414c_232a_b00 fake_nanoseconds = (timestamp - 0x1b21dd21_3814_000) * 100 From cc459ddca64b2f4c41c4d1ff9d25becebc6765e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:30:42 +0100 Subject: [PATCH 37/53] use versionchanged instead of versionadded --- Doc/library/uuid.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 24468b8c549609..2556665bc10c98 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -341,7 +341,7 @@ The following options are accepted: Specify the function name to use to generate the uuid. By default :func:`uuid4` is used. - .. versionadded:: next + .. versionchanged:: next Allow generating UUID version 6 and 8. .. option:: -n From 0d9f68765211c76cf67f82d11e6bb49aae6e170b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:32:26 +0100 Subject: [PATCH 38/53] fix typo --- Doc/library/uuid.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 2556665bc10c98..654138fad3d875 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -342,7 +342,7 @@ The following options are accepted: is used. .. versionchanged:: next - Allow generating UUID version 6 and 8. + Allow generating UUID versions 6 and 8. .. option:: -n --namespace From d70279fb9466443641af600014cb94520ae5031d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 16 Nov 2024 14:11:03 +0100 Subject: [PATCH 39/53] Cosmetic change --- Lib/uuid.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index 416a8b1e1390c8..672411eba007cd 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -108,7 +108,9 @@ class UUID: fields a tuple of the six integer fields of the UUID, which are also available as six individual attributes - and two derived attributes: + and two derived attributes. The time_* attributes are + only relevant to version 1, while the others are only + relevant to versions 1 and 6: time_low the first 32 bits of the UUID time_mid the next 16 bits of the UUID @@ -322,17 +324,17 @@ def clock_seq_low(self): @property def time(self): - field_1 = self.int >> 96 - field_3_no_ver = (self.int >> 64) & 0x0fff - if self.version == 6: - # In version 6, the first field contains the 32 MSBs - # and the field after the version contains the 12 LSBs. - return field_1 << 28 | (self.time_mid << 12) | field_3_no_ver + # time_hi (32) | time_mid (16) | ver (4) | time_lo (12) | ... (64) + time_hi, time_lo = self.int >> 96, (self.int >> 64) & 0x0fff + return time_hi << 28 | (self.time_mid << 12) | time_lo else: - # In version 1, the first field contains the 32 LSBs - # and the field after the version contains the 12 MSBs. - return field_3_no_ver << 48 | (self.time_mid << 32) | field_1 + # time_low (32) | time_mid (16) | ver (4) | time_hi (12) | ... (64) + # + # For compatibility purposes, we do not warn or raise when the + # version is not 1 (timestamp is irrelevant to other versions). + time_hi, time_lo = (self.int >> 64) & 0x0fff, self.int >> 96 + return time_hi << 48 | (self.time_mid << 32) | time_lo @property def clock_seq(self): From b8a0e725f35585d0e28fea26bc104cecf0711ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:41:20 +0100 Subject: [PATCH 40/53] Update Doc/whatsnew/3.14.rst Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/whatsnew/3.14.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 47334a98f1acc8..b3efa2b614fd45 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -531,12 +531,8 @@ unittest uuid ---- -* Add support for UUID version 6 via :func:`uuid.uuid6` as specified - in :rfc:`9562`. - (Contributed by Bénédikt Tran in :gh:`89083`.) - -* Add support for UUID version 8 via :func:`uuid.uuid8` as specified - in :rfc:`9562`. +* Add support for UUID versions 6 and 8 via :func:`~uuid.uuid6` and + :func:`~uuid.uuid8` respectively, as specified in :rfc:`9562`. (Contributed by Bénédikt Tran in :gh:`89083`.) .. Add improved modules above alphabetically, not here at the end. From 6c6339bff491bcb0c0b10427ea65148b849da5de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:43:25 +0100 Subject: [PATCH 41/53] fix lint --- Doc/whatsnew/3.14.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index b3efa2b614fd45..0b4c703e7c975e 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -531,8 +531,8 @@ unittest uuid ---- -* Add support for UUID versions 6 and 8 via :func:`~uuid.uuid6` and - :func:`~uuid.uuid8` respectively, as specified in :rfc:`9562`. +* Add support for UUID versions 6 and 8 via :func:`uuid.uuid6` and + :func:`uuid.uuid8` respectively, as specified in :rfc:`9562`. (Contributed by Bénédikt Tran in :gh:`89083`.) .. Add improved modules above alphabetically, not here at the end. From 1e927b6f1435f10ec12e8b2f30fdd88db4d7da21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 17 Nov 2024 08:24:45 +0100 Subject: [PATCH 42/53] Update Lib/test/test_uuid.py --- Lib/test/test_uuid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 7131ba9dab2b72..5867c917734a8b 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -751,7 +751,7 @@ def test_uuid6_node(self): # Make sure the given node ID appears in the UUID. # # Note: when no node ID is specified, the same logic as for UUIDv1 - # is applied to UUIDv6. In particular, there is need to test that + # is applied to UUIDv6. In particular, there is no need to test that # getnode() correctly returns positive integers of exactly 48 bits # since this is done in test_uuid1_eui64(). self.assertLessEqual(self.uuid.uuid6().node.bit_length(), 48) From 68394e6d150c352b227689a7bb5392467dc5cce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 17 Nov 2024 08:26:36 +0100 Subject: [PATCH 43/53] Update Lib/test/test_uuid.py --- Lib/test/test_uuid.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 5867c917734a8b..663da4f6c6dce8 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -773,9 +773,6 @@ def test_uuid6_node(self): def test_uuid6_clock_seq(self): # Make sure the supplied clock sequence appears in the UUID. # - # Note: currently it is not possible to use the 'clock_seq' - # field as it only works for version 1. - # # For UUIDv6, clock sequence bits are stored from bit 48 to bit 62, # with the convention that the least significant bit is bit 0 and # the most significant bit is bit 127. From e47df67179ba30308058038c923a58ffabb196dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:46:15 +0100 Subject: [PATCH 44/53] update docs --- Doc/library/uuid.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 9730624c5c51ad..664cd772a7165b 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -12,7 +12,7 @@ This module provides immutable :class:`UUID` objects (the :class:`UUID` class) and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5`, -:func:`uuid6`, and :func:`uuid.uuid8` for generating version 1, 3, 4, 5, 6, +:func:`uuid6`, and :func:`uuid8` for generating version 1, 3, 4, 5, 6, and 8 UUIDs as specified in :rfc:`9562` (which supersedes :rfc:`4122`). If all you want is a unique ID, you should probably call :func:`uuid1` or @@ -153,8 +153,8 @@ which relays any information about the UUID's safety, using this enumeration: The UUID version number (1 through 8, meaningful only when the variant is :const:`RFC_4122`). - .. versionchanged:: 3.14 - Added UUID version 8. + .. versionchanged:: next + Added UUID versions 6 and 8. .. attribute:: UUID.is_safe From 2de0a05e518ae41d2ee5c396869b55273562e743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:18:16 +0100 Subject: [PATCH 45/53] improve UUIDv6 uniqueness tests --- Lib/test/test_uuid.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 663da4f6c6dce8..01d1fe6d9dabd5 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -726,9 +726,9 @@ def test_uuid6_uniqueness(self): # the node ID nor the clock sequence is specified. uuids = {self.uuid.uuid6() for _ in range(1000)} self.assertEqual(len(uuids), 1000) + versions = {u.version for u in uuids} + self.assertSetEqual(versions, {6}) - # Within the same 60-bit timestamp, and for identical node IDs, - # it is likely to have collision on the 14-bit clock sequence. timestamp = 0x1ec9414c_232a_b00 fake_nanoseconds = (timestamp - 0x1b21dd21_3814_000) * 100 @@ -737,14 +737,14 @@ def gen(): with mock.patch.object(self.uuid, '_last_timestamp_v6', None): return self.uuid.uuid6(node=0, clock_seq=None) - # By the birthday paradox, sampling N = 1024 UUIDs with the same - # node ID and timestamp results in duplicates with probabillity - # very close to 1 (the probability of not having a duplicate is - # of order 1E-15). + # By the birthday paradox, sampling N = 1024 UUIDs with identical + # node IDs and timestamps results in duplicates with probability + # close to 1 (not having a duplicate happens with probability of + # order 1E-15) since only the 14-bit clock sequence is randomized. N = 1024 uuids = {gen() for _ in range(N)} - self.assertTrue(all(u.node == 0 for u in uuids)) - self.assertTrue(all(u.time == timestamp for u in uuids)) + self.assertSetEqual({u.node for u in uuids}, {0}) + self.assertSetEqual({u.time for u in uuids}, {timestamp}) self.assertLess(len(uuids), N, 'collision property does not hold') def test_uuid6_node(self): @@ -767,7 +767,7 @@ def test_uuid6_node(self): random.getrandbits(72), # node with > 48 bits is truncated ): with self.subTest(node): - u = self.uuid.uuid6(node) + u = self.uuid.uuid6(node=node) equal(u.node, node & 0xffff_ffff_ffff) def test_uuid6_clock_seq(self): @@ -790,7 +790,7 @@ def test_uuid6_clock_seq(self): ): node = random.getrandbits(48) with self.subTest(node=node, clock_seq=clock_seq): - u = self.uuid.uuid6(node, clock_seq) + u = self.uuid.uuid6(node=node, clock_seq=clock_seq) self.assertEqual(get_clock_seq(u), clock_seq & 0x3fff) def test_uuid6_test_vectors(self): From b55adc4fec7f3856988ebafee94b2edbac2a9ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:44:57 +0100 Subject: [PATCH 46/53] explain rationale of randomized clock sequence --- Lib/uuid.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/uuid.py b/Lib/uuid.py index 672411eba007cd..2142cf081f2965 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -751,6 +751,15 @@ def uuid6(node=None, clock_seq=None): timestamp = _last_timestamp_v6 + 1 _last_timestamp_v6 = timestamp if clock_seq is None: + # If the caller does not specify a clock sequence, we may assume that + # sequentiality within the same 60-bit timestamp is less important + # than unpredictability. In particular, by using a randomized clock + # sequence, we indirectly slow down the next call, thereby allowing + # the next 60-bit timestamp to be distinct. + # + # Stated otherwise, it is unlikely that two UUIDs are generated within + # the same 100-ns interval since constructing a UUID object takes more + # than 100 ns. import random clock_seq = random.getrandbits(14) # instead of stable storage time_hi_and_mid = (timestamp >> 12) & 0xffff_ffff_ffff From 6e8abbe60a3d6f909d39761ffa61b97cf08fd22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:54:45 +0100 Subject: [PATCH 47/53] use `UUID._from_int` for UUIDv6 --- Lib/uuid.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index ede7c9433f2478..3443d67eb433db 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -93,6 +93,7 @@ class SafeUUID: _RFC_4122_VERSION_3_FLAGS = ((3 << 76) | (0x8000 << 48)) _RFC_4122_VERSION_4_FLAGS = ((4 << 76) | (0x8000 << 48)) _RFC_4122_VERSION_5_FLAGS = ((5 << 76) | (0x8000 << 48)) +_RFC_4122_VERSION_6_FLAGS = ((6 << 76) | (0x8000 << 48)) _RFC_4122_VERSION_8_FLAGS = ((8 << 76) | (0x8000 << 48)) @@ -780,27 +781,22 @@ def uuid6(node=None, clock_seq=None): timestamp = _last_timestamp_v6 + 1 _last_timestamp_v6 = timestamp if clock_seq is None: - # If the caller does not specify a clock sequence, we may assume that - # sequentiality within the same 60-bit timestamp is less important - # than unpredictability. In particular, by using a randomized clock - # sequence, we indirectly slow down the next call, thereby allowing - # the next 60-bit timestamp to be distinct. - # - # Stated otherwise, it is unlikely that two UUIDs are generated within - # the same 100-ns interval since constructing a UUID object takes more - # than 100 ns. import random clock_seq = random.getrandbits(14) # instead of stable storage time_hi_and_mid = (timestamp >> 12) & 0xffff_ffff_ffff - time_ver_and_lo = timestamp & 0x0fff - var_and_clock_s = clock_seq & 0x3fff + time_lo = timestamp & 0x0fff # keep 12 bits and clear version bits + clock_s = clock_seq & 0x3fff # keep 14 bits and clear variant bits if node is None: node = getnode() + # --- 32 + 16 --- -- 4 -- -- 12 -- -- 2 -- -- 14 --- 48 + # time_hi_and_mid | version | time_lo | variant | clock_seq | node int_uuid_6 = time_hi_and_mid << 80 - int_uuid_6 |= time_ver_and_lo << 64 - int_uuid_6 |= var_and_clock_s << 48 + int_uuid_6 |= time_lo << 64 + int_uuid_6 |= clock_s << 48 int_uuid_6 |= node & 0xffff_ffff_ffff - return UUID(int=int_uuid_6, version=6) + # by construction, the variant and version bits are already cleared + int_uuid_6 |= _RFC_4122_VERSION_6_FLAGS + return UUID._from_int(int_uuid_6) def uuid8(a=None, b=None, c=None): """Generate a UUID from three custom blocks. From 85b6382d14df7dee79d06dbfbc62b9153e65ff8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:22:04 +0100 Subject: [PATCH 48/53] fixup --- Doc/whatsnew/3.14.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 218014c926ee28..a3010f57408401 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -823,14 +823,14 @@ urllib uuid ---- -* :const:`uuid.NIL` and :const:`uuid.MAX` are now available to represent the - Nil and Max UUID formats as defined by :rfc:`9562`. - (Contributed by Nick Pope in :gh:`128427`.) - * Add support for UUID versions 6 and 8 via :func:`uuid.uuid6` and :func:`uuid.uuid8` respectively, as specified in :rfc:`9562`. (Contributed by Bénédikt Tran in :gh:`89083`.) +* :const:`uuid.NIL` and :const:`uuid.MAX` are now available to represent the + Nil and Max UUID formats as defined by :rfc:`9562`. + (Contributed by Nick Pope in :gh:`128427`.) + zipinfo ------- From 20d2e05b90d35938f021840ebda641eeaeb1fb1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 22 Feb 2025 10:53:23 +0100 Subject: [PATCH 49/53] revert `_last_timestamp` -> `_last_timestamp_v1` --- Lib/test/test_uuid.py | 4 ++-- Lib/uuid.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 3cbe956e10b7ea..9e1c76dd2e88e8 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -632,7 +632,7 @@ def test_uuid1_bogus_return_value(self): def test_uuid1_time(self): with mock.patch.object(self.uuid, '_generate_time_safe', None), \ - mock.patch.object(self.uuid, '_last_timestamp_v1', None), \ + mock.patch.object(self.uuid, '_last_timestamp', None), \ mock.patch.object(self.uuid, 'getnode', return_value=93328246233727), \ mock.patch('time.time_ns', return_value=1545052026752910643), \ mock.patch('random.getrandbits', return_value=5317): # guaranteed to be random @@ -640,7 +640,7 @@ def test_uuid1_time(self): self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f')) with mock.patch.object(self.uuid, '_generate_time_safe', None), \ - mock.patch.object(self.uuid, '_last_timestamp_v1', None), \ + mock.patch.object(self.uuid, '_last_timestamp', None), \ mock.patch('time.time_ns', return_value=1545052026752910643): u = self.uuid.uuid1(node=93328246233727, clock_seq=5317) self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f')) diff --git a/Lib/uuid.py b/Lib/uuid.py index c5d3c15ae0987c..3e399635305d45 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -699,7 +699,7 @@ def getnode(): assert False, '_random_getnode() returned invalid value: {}'.format(_node) -_last_timestamp_v1 = None +_last_timestamp = None def uuid1(node=None, clock_seq=None): """Generate a UUID from a host ID, sequence number, and the current time. @@ -717,15 +717,15 @@ def uuid1(node=None, clock_seq=None): is_safe = SafeUUID.unknown return UUID(bytes=uuid_time, is_safe=is_safe) - global _last_timestamp_v1 + global _last_timestamp import time 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_timestamp_v1 is not None and timestamp <= _last_timestamp_v1: - timestamp = _last_timestamp_v1 + 1 - _last_timestamp_v1 = timestamp + if _last_timestamp is not None and timestamp <= _last_timestamp: + timestamp = _last_timestamp + 1 + _last_timestamp = timestamp if clock_seq is None: import random clock_seq = random.getrandbits(14) # instead of stable storage From 48649e5905181ef43f4855d4e957cb1575bde828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 22 Feb 2025 11:10:57 +0100 Subject: [PATCH 50/53] address Victor's review --- Lib/test/test_uuid.py | 44 ++++++++++++++++++++++++++----------------- Lib/uuid.py | 6 ++++-- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 9e1c76dd2e88e8..e284de93fbdfd1 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -797,19 +797,24 @@ def test_uuid6_node(self): # since this is done in test_uuid1_eui64(). self.assertLessEqual(self.uuid.uuid6().node.bit_length(), 48) - equal = self.assertEqual - equal(self.uuid.uuid6(0).node, 0) - equal(self.uuid.uuid6(0xffff_ffff_ffff).node, 0xffff_ffff_ffff) + self.assertEqual(self.uuid.uuid6(0).node, 0) + + # tests with explicit values + max_node = 0xffff_ffff_ffff + self.assertEqual(self.uuid.uuid6(max_node).node, max_node) + big_node = 0xE_1234_5678_ABCD # 52-bit node + res_node = 0x0_1234_5678_ABCD # truncated to 48 bits + self.assertEqual(self.uuid.uuid6(big_node).node, res_node) + # randomized tests for _ in range(10): - for node in ( - random.getrandbits(24), - random.getrandbits(48), - random.getrandbits(72), # node with > 48 bits is truncated - ): - with self.subTest(node): + # node with > 48 bits is truncated + for b in [24, 48, 72]: + node = (1 << (b - 1)) | random.getrandbits(b) + with self.subTest(node=node, bitlen=b): + self.assertEqual(node.bit_length(), b) u = self.uuid.uuid6(node=node) - equal(u.node, node & 0xffff_ffff_ffff) + self.assertEqual(u.node, node & 0xffff_ffff_ffff) def test_uuid6_clock_seq(self): # Make sure the supplied clock sequence appears in the UUID. @@ -821,16 +826,21 @@ def test_uuid6_clock_seq(self): u = self.uuid.uuid6() self.assertLessEqual(get_clock_seq(u).bit_length(), 14) - equal = self.assertEqual + # tests with explicit values + big_clock_seq = 0xffff # 16-bit clock sequence + res_clock_seq = 0x3fff # truncated to 14 bits + u = self.uuid.uuid6(clock_seq=big_clock_seq) + self.assertEqual(get_clock_seq(u), res_clock_seq) + + # some randomized tests for _ in range(10): - for clock_seq in ( - random.getrandbits(7), - random.getrandbits(14), - random.getrandbits(28), # clock_seq with > 14 bits is truncated - ): + # clock_seq with > 14 bits is truncated + for b in [7, 14, 28]: node = random.getrandbits(48) - with self.subTest(node=node, clock_seq=clock_seq): + clock_seq = (1 << (b - 1)) | random.getrandbits(b) + with self.subTest(node=node, clock_seq=clock_seq, bitlen=b): + self.assertEqual(clock_seq.bit_length(), b) u = self.uuid.uuid6(node=node, clock_seq=clock_seq) self.assertEqual(get_clock_seq(u), clock_seq & 0x3fff) diff --git a/Lib/uuid.py b/Lib/uuid.py index 3e399635305d45..89a7c76e3269d8 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -358,14 +358,16 @@ def clock_seq_low(self): def time(self): if self.version == 6: # time_hi (32) | time_mid (16) | ver (4) | time_lo (12) | ... (64) - time_hi, time_lo = self.int >> 96, (self.int >> 64) & 0x0fff + time_hi = self.int >> 96 + time_lo = (self.int >> 64) & 0x0fff return time_hi << 28 | (self.time_mid << 12) | time_lo else: # time_low (32) | time_mid (16) | ver (4) | time_hi (12) | ... (64) # # For compatibility purposes, we do not warn or raise when the # version is not 1 (timestamp is irrelevant to other versions). - time_hi, time_lo = (self.int >> 64) & 0x0fff, self.int >> 96 + time_hi = (self.int >> 64) & 0x0fff + time_lo = self.int >> 96 return time_hi << 48 | (self.time_mid << 32) | time_lo @property From 3c231c7ad3260476310d83649daa4376640ceee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 22 Feb 2025 12:30:44 +0100 Subject: [PATCH 51/53] time_low -> time_lo in comment --- Lib/uuid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index 89a7c76e3269d8..ed69b4de07b53f 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -362,7 +362,7 @@ def time(self): time_lo = (self.int >> 64) & 0x0fff return time_hi << 28 | (self.time_mid << 12) | time_lo else: - # time_low (32) | time_mid (16) | ver (4) | time_hi (12) | ... (64) + # time_lo (32) | time_mid (16) | ver (4) | time_hi (12) | ... (64) # # For compatibility purposes, we do not warn or raise when the # version is not 1 (timestamp is irrelevant to other versions). From 66eb50c6860c031abc96db117f728ee41cf8ae02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 22 Feb 2025 12:38:50 +0100 Subject: [PATCH 52/53] DB -> database Co-authored-by: Victor Stinner --- Doc/library/uuid.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 1f35b49e380543..b30e811bcf009b 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -226,7 +226,7 @@ The :mod:`uuid` module defines the following functions: Generate a UUID from a sequence number and the current time according to :rfc:`9562`. - This is an alternative to :func:`uuid1` to improve DB locality. + This is an alternative to :func:`uuid1` to improve database locality. When *node* is not specified, :func:`getnode` is used to obtain the hardware address as a 48-bit positive integer. When a sequence number *clock_seq* is From 70872bff7a0cabe11a0378137c8ed7cf79301f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:02:55 +0100 Subject: [PATCH 53/53] remove `.. index::` directive in prevision of gh-130526 --- Doc/library/uuid.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index b30e811bcf009b..ed1126eda5aece 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -237,8 +237,6 @@ The :mod:`uuid` module defines the following functions: .. versionadded:: next -.. index:: single: uuid6 - .. function:: uuid8(a=None, b=None, c=None)