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

Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
This commit is contained in:
Bénédikt Tran 2024-11-12 19:08:49 +01:00 committed by GitHub
parent a83472f49b
commit 03924b5dee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 109 additions and 19 deletions

View File

@ -1,8 +1,8 @@
:mod:`!uuid` --- UUID objects according to :rfc:`4122` :mod:`!uuid` --- UUID objects according to :rfc:`9562`
====================================================== ======================================================
.. module:: uuid .. 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 <ping@zesty.ca> .. moduleauthor:: Ka-Ping Yee <ping@zesty.ca>
.. sectionauthor:: George Yoshida <quiver@users.sourceforge.net> .. sectionauthor:: George Yoshida <quiver@users.sourceforge.net>
@ -12,7 +12,8 @@
This module provides immutable :class:`UUID` objects (the :class:`UUID` class) This module provides immutable :class:`UUID` objects (the :class:`UUID` class)
and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5` for 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 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 :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. 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 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*. given *hex*, *bytes*, *bytes_le*, *fields*, or *int*.
Comparison of UUID objects are made by way of comparing their 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 .. 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 .. attribute:: UUID.variant
@ -149,9 +150,13 @@ which relays any information about the UUID's safety, using this enumeration:
.. attribute:: UUID.version .. 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`). :const:`RFC_4122`).
.. versionchanged:: next
Added UUID version 8.
.. attribute:: UUID.is_safe .. attribute:: UUID.is_safe
An enumeration of :class:`SafeUUID` which indicates whether the platform 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 .. 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 The :mod:`uuid` module defines the following namespace identifiers for use with
:func:`uuid3` or :func:`uuid5`. :func:`uuid3` or :func:`uuid5`.
@ -252,7 +274,9 @@ of the :attr:`~UUID.variant` attribute:
.. data:: RFC_4122 .. 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 .. data:: RESERVED_MICROSOFT
@ -267,7 +291,7 @@ of the :attr:`~UUID.variant` attribute:
.. seealso:: .. 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 This specification defines a Uniform Resource Name namespace for UUIDs, the
internal format of UUIDs, and methods of generating UUIDs. 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 .. 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: The following options are accepted:

View File

@ -517,6 +517,14 @@ unittest
(Contributed by Jacob Walls in :gh:`80958`.) (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. .. Add improved modules above alphabetically, not here at the end.
Optimizations Optimizations

View File

@ -8,8 +8,10 @@ import enum
import io import io
import os import os
import pickle import pickle
import random
import sys import sys
import weakref import weakref
from itertools import product
from unittest import mock from unittest import mock
py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid']) py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid'])
@ -267,7 +269,7 @@ class BaseTestUUID:
# Version number out of range. # Version number out of range.
badvalue(lambda: self.uuid.UUID('00'*16, version=0)) 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. # Integer value out of range.
badvalue(lambda: self.uuid.UUID(int=-1)) badvalue(lambda: self.uuid.UUID(int=-1))
@ -681,6 +683,37 @@ class BaseTestUUID:
equal(u, self.uuid.UUID(v)) equal(u, self.uuid.UUID(v))
equal(str(u), 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() @support.requires_fork()
def testIssue8621(self): def testIssue8621(self):
# On at least some versions of OSX self.uuid.uuid4 generates # On at least some versions of OSX self.uuid.uuid4 generates

View File

@ -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 This module provides immutable UUID objects (class UUID) and the functions
uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5 uuid1(), uuid3(), uuid4(), uuid5(), and uuid8() for generating version 1, 3,
UUIDs as specified in RFC 4122. 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(). 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 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 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, variant the UUID variant (one of the constants RESERVED_NCS,
RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE) 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) when the variant is RFC_4122)
is_safe An enum indicating whether the UUID has been generated in is_safe An enum indicating whether the UUID has been generated in
@ -214,9 +214,9 @@ class UUID:
if not 0 <= int < 1<<128: if not 0 <= int < 1<<128:
raise ValueError('int is out of range (need a 128-bit value)') raise ValueError('int is out of range (need a 128-bit value)')
if version is not None: if version is not None:
if not 1 <= version <= 5: if not 1 <= version <= 8:
raise ValueError('illegal version number') raise ValueError('illegal version number')
# Set the variant to RFC 4122. # Set the variant to RFC 4122/9562.
int &= ~(0xc000 << 48) int &= ~(0xc000 << 48)
int |= 0x8000 << 48 int |= 0x8000 << 48
# Set the version number. # Set the version number.
@ -355,7 +355,7 @@ class UUID:
@property @property
def version(self): 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: if self.variant == RFC_4122:
return int((self.int >> 76) & 0xf) return int((self.int >> 76) & 0xf)
@ -719,6 +719,28 @@ def uuid5(namespace, name):
hash = sha1(namespace.bytes + name).digest() hash = sha1(namespace.bytes + name).digest()
return UUID(bytes=hash[:16], version=5) 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(): def main():
"""Run the uuid command line interface.""" """Run the uuid command line interface."""
@ -726,7 +748,8 @@ def main():
"uuid1": uuid1, "uuid1": uuid1,
"uuid3": uuid3, "uuid3": uuid3,
"uuid4": uuid4, "uuid4": uuid4,
"uuid5": uuid5 "uuid5": uuid5,
"uuid8": uuid8,
} }
uuid_namespace_funcs = ("uuid3", "uuid5") uuid_namespace_funcs = ("uuid3", "uuid5")
namespaces = { namespaces = {

View File

@ -0,0 +1,2 @@
Add :func:`uuid.uuid8` for generating UUIDv8 objects as specified in
:rfc:`9562`. Patch by Bénédikt Tran