https://github.com/python/cpython/commit/03924b5deeb766fabd53ced28ba707e4dd08fb60
commit: 03924b5deeb766fabd53ced28ba707e4dd08fb60
branch: main
author: Bénédikt Tran <[email protected]>
committer: vstinner <[email protected]>
date: 2024-11-12T19:08:49+01:00
summary:

gh-89083: add support for UUID version 8 (RFC 9562) (#123224)

Co-authored-by: Hugo van Kemenade <[email protected]>

files:
A Misc/NEWS.d/next/Library/2024-08-22-12-12-35.gh-issue-89083.b6zFh0.rst
M Doc/library/uuid.rst
M Doc/whatsnew/3.14.rst
M Lib/test/test_uuid.py
M Lib/uuid.py

diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst
index 0f2d7820cb25c8..6166c22caedf81 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 <[email protected]>
 .. sectionauthor:: George Yoshida <[email protected]>
 
@@ -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, and 5 UUIDs as specified in :rfc:`4122`.
+generating version 1, 3, 4, 5, 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
@@ -65,7 +66,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 +138,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
@@ -149,9 +150,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:: next
+      Added UUID version 8.
+
+
 .. attribute:: UUID.is_safe
 
    An enumeration of :class:`SafeUUID` which indicates whether the platform
@@ -216,6 +221,23 @@ The :mod:`uuid` module defines the following functions:
 
 .. index:: single: uuid5
 
+
+.. function:: uuid8(a=None, b=None, c=None)
+
+   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:: next
+
+.. index:: single: uuid8
+
+
 The :mod:`uuid` module defines the following namespace identifiers for use with
 :func:`uuid3` or :func:`uuid5`.
 
@@ -252,7 +274,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 superseded
+   by :rfc:`9562`.
 
 
 .. data:: RESERVED_MICROSOFT
@@ -267,7 +291,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.
 
@@ -283,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:
 
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 20bd3aa5221454..c2cf46902fd7fe 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -517,6 +517,14 @@ unittest
   (Contributed by Jacob Walls in :gh:`80958`.)
 
 
+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
diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py
index e177464c00f7a6..7bd26a8ca34b62 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,37 @@ 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)
+
+    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
diff --git a/Lib/uuid.py b/Lib/uuid.py
index 4d4f06cfc9ebbe..9c6ad9643cf6d5 100644
--- a/Lib/uuid.py
+++ b/Lib/uuid.py
@@ -1,8 +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() 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/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
@@ -124,12 +124,12 @@ 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)
 
-        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 +214,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 4122/9562.
             int &= ~(0xc000 << 48)
             int |= 0x8000 << 48
             # Set the version number.
@@ -355,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)
 
@@ -719,6 +719,28 @@ 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 pseudo-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 & 0xffff_ffff_ffff) << 80
+    int_uuid_8 |= (b & 0xfff) << 64
+    int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff
+    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 = {
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

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to