Skip to content

Commit 03924b5

Browse files
picnixzhugovk
andauthored
gh-89083: add support for UUID version 8 (RFC 9562) (#123224)
Co-authored-by: Hugo van Kemenade <[email protected]>
1 parent a83472f commit 03924b5

File tree

5 files changed

+109
-19
lines changed

5 files changed

+109
-19
lines changed

Doc/library/uuid.rst

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
:mod:`!uuid` --- UUID objects according to :rfc:`4122`
1+
:mod:`!uuid` --- UUID objects according to :rfc:`9562`
22
======================================================
33

44
.. module:: uuid
5-
:synopsis: UUID objects (universally unique identifiers) according to RFC 4122
5+
:synopsis: UUID objects (universally unique identifiers) according to RFC 9562
66
.. moduleauthor:: Ka-Ping Yee <[email protected]>
77
.. sectionauthor:: George Yoshida <[email protected]>
88

@@ -12,7 +12,8 @@
1212

1313
This module provides immutable :class:`UUID` objects (the :class:`UUID` class)
1414
and the functions :func:`uuid1`, :func:`uuid3`, :func:`uuid4`, :func:`uuid5` for
15-
generating version 1, 3, 4, and 5 UUIDs as specified in :rfc:`4122`.
15+
generating version 1, 3, 4, 5, and 8 UUIDs as specified in :rfc:`9562` (which
16+
supersedes :rfc:`4122`).
1617

1718
If all you want is a unique ID, you should probably call :func:`uuid1` or
1819
: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:
6566

6667
Exactly one of *hex*, *bytes*, *bytes_le*, *fields*, or *int* must be given.
6768
The *version* argument is optional; if given, the resulting UUID will have its
68-
variant and version number set according to :rfc:`4122`, overriding bits in the
69+
variant and version number set according to :rfc:`9562`, overriding bits in the
6970
given *hex*, *bytes*, *bytes_le*, *fields*, or *int*.
7071

7172
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:
137138

138139
.. attribute:: UUID.urn
139140

140-
The UUID as a URN as specified in :rfc:`4122`.
141+
The UUID as a URN as specified in :rfc:`9562`.
141142

142143

143144
.. attribute:: UUID.variant
@@ -149,9 +150,13 @@ which relays any information about the UUID's safety, using this enumeration:
149150

150151
.. attribute:: UUID.version
151152

152-
The UUID version number (1 through 5, meaningful only when the variant is
153+
The UUID version number (1 through 8, meaningful only when the variant is
153154
:const:`RFC_4122`).
154155

156+
.. versionchanged:: next
157+
Added UUID version 8.
158+
159+
155160
.. attribute:: UUID.is_safe
156161

157162
An enumeration of :class:`SafeUUID` which indicates whether the platform
@@ -216,6 +221,23 @@ The :mod:`uuid` module defines the following functions:
216221

217222
.. index:: single: uuid5
218223

224+
225+
.. function:: uuid8(a=None, b=None, c=None)
226+
227+
Generate a pseudo-random UUID according to
228+
:rfc:`RFC 9562, §5.8 <9562#section-5.8>`.
229+
230+
When specified, the parameters *a*, *b* and *c* are expected to be
231+
positive integers of 48, 12 and 62 bits respectively. If they exceed
232+
their expected bit count, only their least significant bits are kept;
233+
non-specified arguments are substituted for a pseudo-random integer of
234+
appropriate size.
235+
236+
.. versionadded:: next
237+
238+
.. index:: single: uuid8
239+
240+
219241
The :mod:`uuid` module defines the following namespace identifiers for use with
220242
:func:`uuid3` or :func:`uuid5`.
221243

@@ -252,7 +274,9 @@ of the :attr:`~UUID.variant` attribute:
252274

253275
.. data:: RFC_4122
254276

255-
Specifies the UUID layout given in :rfc:`4122`.
277+
Specifies the UUID layout given in :rfc:`4122`. This constant is kept
278+
for backward compatibility even though :rfc:`4122` has been superseded
279+
by :rfc:`9562`.
256280

257281

258282
.. data:: RESERVED_MICROSOFT
@@ -267,7 +291,7 @@ of the :attr:`~UUID.variant` attribute:
267291

268292
.. seealso::
269293

270-
:rfc:`4122` - A Universally Unique IDentifier (UUID) URN Namespace
294+
:rfc:`9562` - A Universally Unique IDentifier (UUID) URN Namespace
271295
This specification defines a Uniform Resource Name namespace for UUIDs, the
272296
internal format of UUIDs, and methods of generating UUIDs.
273297

@@ -283,7 +307,7 @@ The :mod:`uuid` module can be executed as a script from the command line.
283307

284308
.. code-block:: sh
285309
286-
python -m uuid [-h] [-u {uuid1,uuid3,uuid4,uuid5}] [-n NAMESPACE] [-N NAME]
310+
python -m uuid [-h] [-u {uuid1,uuid3,uuid4,uuid5,uuid8}] [-n NAMESPACE] [-N NAME]
287311
288312
The following options are accepted:
289313

Doc/whatsnew/3.14.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,14 @@ unittest
517517
(Contributed by Jacob Walls in :gh:`80958`.)
518518

519519

520+
uuid
521+
----
522+
523+
* Add support for UUID version 8 via :func:`uuid.uuid8` as specified
524+
in :rfc:`9562`.
525+
(Contributed by Bénédikt Tran in :gh:`89083`.)
526+
527+
520528
.. Add improved modules above alphabetically, not here at the end.
521529
522530
Optimizations

Lib/test/test_uuid.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
import io
99
import os
1010
import pickle
11+
import random
1112
import sys
1213
import weakref
14+
from itertools import product
1315
from unittest import mock
1416

1517
py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid'])
@@ -267,7 +269,7 @@ def test_exceptions(self):
267269

268270
# Version number out of range.
269271
badvalue(lambda: self.uuid.UUID('00'*16, version=0))
270-
badvalue(lambda: self.uuid.UUID('00'*16, version=6))
272+
badvalue(lambda: self.uuid.UUID('00'*16, version=42))
271273

272274
# Integer value out of range.
273275
badvalue(lambda: self.uuid.UUID(int=-1))
@@ -681,6 +683,37 @@ def test_uuid5(self):
681683
equal(u, self.uuid.UUID(v))
682684
equal(str(u), v)
683685

686+
def test_uuid8(self):
687+
equal = self.assertEqual
688+
u = self.uuid.uuid8()
689+
690+
equal(u.variant, self.uuid.RFC_4122)
691+
equal(u.version, 8)
692+
693+
for (_, hi, mid, lo) in product(
694+
range(10), # repeat 10 times
695+
[None, 0, random.getrandbits(48)],
696+
[None, 0, random.getrandbits(12)],
697+
[None, 0, random.getrandbits(62)],
698+
):
699+
u = self.uuid.uuid8(hi, mid, lo)
700+
equal(u.variant, self.uuid.RFC_4122)
701+
equal(u.version, 8)
702+
if hi is not None:
703+
equal((u.int >> 80) & 0xffffffffffff, hi)
704+
if mid is not None:
705+
equal((u.int >> 64) & 0xfff, mid)
706+
if lo is not None:
707+
equal(u.int & 0x3fffffffffffffff, lo)
708+
709+
def test_uuid8_uniqueness(self):
710+
# Test that UUIDv8-generated values are unique
711+
# (up to a negligible probability of failure).
712+
u1 = self.uuid.uuid8()
713+
u2 = self.uuid.uuid8()
714+
self.assertNotEqual(u1.int, u2.int)
715+
self.assertEqual(u1.version, u2.version)
716+
684717
@support.requires_fork()
685718
def testIssue8621(self):
686719
# On at least some versions of OSX self.uuid.uuid4 generates

Lib/uuid.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
r"""UUID objects (universally unique identifiers) according to RFC 4122.
1+
r"""UUID objects (universally unique identifiers) according to RFC 4122/9562.
22
33
This module provides immutable UUID objects (class UUID) and the functions
4-
uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5
5-
UUIDs as specified in RFC 4122.
4+
uuid1(), uuid3(), uuid4(), uuid5(), and uuid8() for generating version 1, 3,
5+
4, 5, and 8 UUIDs as specified in RFC 4122/9562.
66
77
If all you want is a unique ID, you should probably call uuid1() or uuid4().
88
Note that uuid1() may compromise privacy since it creates a UUID containing
@@ -124,12 +124,12 @@ class UUID:
124124
125125
int the UUID as a 128-bit integer
126126
127-
urn the UUID as a URN as specified in RFC 4122
127+
urn the UUID as a URN as specified in RFC 4122/9562
128128
129129
variant the UUID variant (one of the constants RESERVED_NCS,
130130
RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE)
131131
132-
version the UUID version number (1 through 5, meaningful only
132+
version the UUID version number (1 through 8, meaningful only
133133
when the variant is RFC_4122)
134134
135135
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,
214214
if not 0 <= int < 1<<128:
215215
raise ValueError('int is out of range (need a 128-bit value)')
216216
if version is not None:
217-
if not 1 <= version <= 5:
217+
if not 1 <= version <= 8:
218218
raise ValueError('illegal version number')
219-
# Set the variant to RFC 4122.
219+
# Set the variant to RFC 4122/9562.
220220
int &= ~(0xc000 << 48)
221221
int |= 0x8000 << 48
222222
# Set the version number.
@@ -355,7 +355,7 @@ def variant(self):
355355

356356
@property
357357
def version(self):
358-
# The version bits are only meaningful for RFC 4122 UUIDs.
358+
# The version bits are only meaningful for RFC 4122/9562 UUIDs.
359359
if self.variant == RFC_4122:
360360
return int((self.int >> 76) & 0xf)
361361

@@ -719,14 +719,37 @@ def uuid5(namespace, name):
719719
hash = sha1(namespace.bytes + name).digest()
720720
return UUID(bytes=hash[:16], version=5)
721721

722+
def uuid8(a=None, b=None, c=None):
723+
"""Generate a UUID from three custom blocks.
724+
725+
* 'a' is the first 48-bit chunk of the UUID (octets 0-5);
726+
* 'b' is the mid 12-bit chunk (octets 6-7);
727+
* 'c' is the last 62-bit chunk (octets 8-15).
728+
729+
When a value is not specified, a pseudo-random value is generated.
730+
"""
731+
if a is None:
732+
import random
733+
a = random.getrandbits(48)
734+
if b is None:
735+
import random
736+
b = random.getrandbits(12)
737+
if c is None:
738+
import random
739+
c = random.getrandbits(62)
740+
int_uuid_8 = (a & 0xffff_ffff_ffff) << 80
741+
int_uuid_8 |= (b & 0xfff) << 64
742+
int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff
743+
return UUID(int=int_uuid_8, version=8)
722744

723745
def main():
724746
"""Run the uuid command line interface."""
725747
uuid_funcs = {
726748
"uuid1": uuid1,
727749
"uuid3": uuid3,
728750
"uuid4": uuid4,
729-
"uuid5": uuid5
751+
"uuid5": uuid5,
752+
"uuid8": uuid8,
730753
}
731754
uuid_namespace_funcs = ("uuid3", "uuid5")
732755
namespaces = {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :func:`uuid.uuid8` for generating UUIDv8 objects as specified in
2+
:rfc:`9562`. Patch by Bénédikt Tran

0 commit comments

Comments
 (0)