Update uuid from v3.14.2-288-g06f9c8ca1c

This commit is contained in:
CPython Developers
2026-02-01 19:59:12 -05:00
committed by Jeong, YunWon
parent babc3c634f
commit 15efc4a808
2 changed files with 762 additions and 66 deletions

487
Lib/test/test_uuid.py vendored
View File

@@ -1,7 +1,3 @@
import unittest
from test import support
from test.support import import_helper
from test.support.script_helper import assert_python_ok
import builtins
import contextlib
import copy
@@ -9,10 +5,17 @@ import enum
import io
import os
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
from test.support.script_helper import assert_python_ok
py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid'])
c_uuid = import_helper.import_fresh_module('uuid', fresh=['_uuid'])
@@ -33,6 +36,47 @@ def mock_get_command_stdout(data):
class BaseTestUUID:
uuid = None
def test_nil_uuid(self):
nil_uuid = self.uuid.NIL
s = '00000000-0000-0000-0000-000000000000'
i = 0
self.assertEqual(nil_uuid, self.uuid.UUID(s))
self.assertEqual(nil_uuid, self.uuid.UUID(int=i))
self.assertEqual(nil_uuid.int, i)
self.assertEqual(str(nil_uuid), s)
# The Nil UUID falls within the range of the Apollo NCS variant as per
# RFC 9562.
# See https://www.rfc-editor.org/rfc/rfc9562.html#section-5.9-4
self.assertEqual(nil_uuid.variant, self.uuid.RESERVED_NCS)
# A version field of all zeros is "Unused" in RFC 9562, but the version
# field also only applies to the 10xx variant, i.e. the variant
# specified in RFC 9562. As such, because the Nil UUID falls under a
# different variant, its version is considered undefined.
# See https://www.rfc-editor.org/rfc/rfc9562.html#table2
self.assertIsNone(nil_uuid.version)
def test_max_uuid(self):
max_uuid = self.uuid.MAX
s = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
i = (1 << 128) - 1
self.assertEqual(max_uuid, self.uuid.UUID(s))
self.assertEqual(max_uuid, self.uuid.UUID(int=i))
self.assertEqual(max_uuid.int, i)
self.assertEqual(str(max_uuid), s)
# The Max UUID falls within the range of the "yet-to-be defined" future
# UUID variant as per RFC 9562.
# See https://www.rfc-editor.org/rfc/rfc9562.html#section-5.10-4
self.assertEqual(max_uuid.variant, self.uuid.RESERVED_FUTURE)
# A version field of all ones is "Reserved for future definition" in
# RFC 9562, but the version field also only applies to the 10xx
# variant, i.e. the variant specified in RFC 9562. As such, because the
# Max UUID falls under a different variant, its version is considered
# undefined.
# See https://www.rfc-editor.org/rfc/rfc9562.html#table2
self.assertIsNone(max_uuid.version)
def test_safe_uuid_enum(self):
class CheckedSafeUUID(enum.Enum):
safe = 0
@@ -268,7 +312,7 @@ class BaseTestUUID:
# 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))
@@ -682,6 +726,392 @@ class BaseTestUUID:
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 = 0x1571_20a1_de1a_c533
fake_node_value = 0x54e1_acf6_da7f
fake_clock_seq = 0x14c5
with (
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)
# 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_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)
versions = {u.version for u in uuids}
self.assertSetEqual(versions, {6})
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 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.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):
# 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 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)
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):
# 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)
self.assertEqual(u.node, node & 0xffff_ffff_ffff)
def test_uuid6_clock_seq(self):
# Make sure the supplied clock sequence appears in the UUID.
#
# 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)
# 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):
# clock_seq with > 14 bits is truncated
for b in [7, 14, 28]:
node = random.getrandbits(48)
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)
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)
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 = (3 << 12) | 0x3c8
with (
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)
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
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_uuid7(self):
equal = self.assertEqual
u = self.uuid.uuid7()
equal(u.variant, self.uuid.RFC_4122)
equal(u.version, 7)
# 1 Jan 2023 12:34:56.123_456_789
timestamp_ns = 1672533296_123_456_789 # ns precision
timestamp_ms, _ = divmod(timestamp_ns, 1_000_000)
for _ in range(100):
counter_hi = random.getrandbits(11)
counter_lo = random.getrandbits(30)
counter = (counter_hi << 30) | counter_lo
tail = random.getrandbits(32)
# effective number of bits is 32 + 30 + 11 = 73
random_bits = counter << 32 | tail
# set all remaining MSB of fake random bits to 1 to ensure that
# the implementation correctly removes them
random_bits = (((1 << 7) - 1) << 73) | random_bits
random_data = random_bits.to_bytes(10)
with (
mock.patch.multiple(
self.uuid,
_last_timestamp_v7=None,
_last_counter_v7=0,
),
mock.patch('time.time_ns', return_value=timestamp_ns),
mock.patch('os.urandom', return_value=random_data) as urand
):
u = self.uuid.uuid7()
urand.assert_called_once_with(10)
equal(u.variant, self.uuid.RFC_4122)
equal(u.version, 7)
equal(self.uuid._last_timestamp_v7, timestamp_ms)
equal(self.uuid._last_counter_v7, counter)
unix_ts_ms = timestamp_ms & 0xffff_ffff_ffff
equal(u.time, unix_ts_ms)
equal((u.int >> 80) & 0xffff_ffff_ffff, unix_ts_ms)
equal((u.int >> 75) & 1, 0) # check that the MSB is 0
equal((u.int >> 64) & 0xfff, counter_hi)
equal((u.int >> 32) & 0x3fff_ffff, counter_lo)
equal(u.int & 0xffff_ffff, tail)
def test_uuid7_uniqueness(self):
# Test that UUIDv7-generated values are unique.
#
# While UUIDv8 has an entropy of 122 bits, those 122 bits may not
# necessarily be sampled from a PRNG. On the other hand, UUIDv7
# uses os.urandom() as a PRNG which features better randomness.
N = 1000
uuids = {self.uuid.uuid7() for _ in range(N)}
self.assertEqual(len(uuids), N)
versions = {u.version for u in uuids}
self.assertSetEqual(versions, {7})
def test_uuid7_monotonicity(self):
equal = self.assertEqual
us = [self.uuid.uuid7() for _ in range(10_000)]
equal(us, sorted(us))
with mock.patch.multiple(
self.uuid,
_last_timestamp_v7=0,
_last_counter_v7=0,
):
# 1 Jan 2023 12:34:56.123_456_789
timestamp_ns = 1672533296_123_456_789 # ns precision
timestamp_ms, _ = divmod(timestamp_ns, 1_000_000)
# counter_{hi,lo} are chosen so that "counter + 1" does not overflow
counter_hi = random.getrandbits(11)
counter_lo = random.getrandbits(29)
counter = (counter_hi << 30) | counter_lo
self.assertLess(counter + 1, 0x3ff_ffff_ffff)
tail = random.getrandbits(32)
random_bits = counter << 32 | tail
random_data = random_bits.to_bytes(10)
with (
mock.patch('time.time_ns', return_value=timestamp_ns),
mock.patch('os.urandom', return_value=random_data) as urand
):
u1 = self.uuid.uuid7()
urand.assert_called_once_with(10)
equal(self.uuid._last_timestamp_v7, timestamp_ms)
equal(self.uuid._last_counter_v7, counter)
equal(u1.time, timestamp_ms)
equal((u1.int >> 64) & 0xfff, counter_hi)
equal((u1.int >> 32) & 0x3fff_ffff, counter_lo)
equal(u1.int & 0xffff_ffff, tail)
# 1 Jan 2023 12:34:56.123_457_032 (same millisecond but not same ns)
next_timestamp_ns = 1672533296_123_457_032
next_timestamp_ms, _ = divmod(timestamp_ns, 1_000_000)
equal(timestamp_ms, next_timestamp_ms)
next_tail_bytes = os.urandom(4)
next_fail = int.from_bytes(next_tail_bytes)
with (
mock.patch('time.time_ns', return_value=next_timestamp_ns),
mock.patch('os.urandom', return_value=next_tail_bytes) as urand
):
u2 = self.uuid.uuid7()
urand.assert_called_once_with(4)
# same milli-second
equal(self.uuid._last_timestamp_v7, timestamp_ms)
# 42-bit counter advanced by 1
equal(self.uuid._last_counter_v7, counter + 1)
equal(u2.time, timestamp_ms)
equal((u2.int >> 64) & 0xfff, counter_hi)
equal((u2.int >> 32) & 0x3fff_ffff, counter_lo + 1)
equal(u2.int & 0xffff_ffff, next_fail)
self.assertLess(u1, u2)
def test_uuid7_timestamp_backwards(self):
equal = self.assertEqual
# 1 Jan 2023 12:34:56.123_456_789
timestamp_ns = 1672533296_123_456_789 # ns precision
timestamp_ms, _ = divmod(timestamp_ns, 1_000_000)
fake_last_timestamp_v7 = timestamp_ms + 1
# counter_{hi,lo} are chosen so that "counter + 1" does not overflow
counter_hi = random.getrandbits(11)
counter_lo = random.getrandbits(29)
counter = (counter_hi << 30) | counter_lo
self.assertLess(counter + 1, 0x3ff_ffff_ffff)
tail_bytes = os.urandom(4)
tail = int.from_bytes(tail_bytes)
with (
mock.patch.multiple(
self.uuid,
_last_timestamp_v7=fake_last_timestamp_v7,
_last_counter_v7=counter,
),
mock.patch('time.time_ns', return_value=timestamp_ns),
mock.patch('os.urandom', return_value=tail_bytes) as urand
):
u = self.uuid.uuid7()
urand.assert_called_once_with(4)
equal(u.variant, self.uuid.RFC_4122)
equal(u.version, 7)
equal(self.uuid._last_timestamp_v7, fake_last_timestamp_v7 + 1)
unix_ts_ms = (fake_last_timestamp_v7 + 1) & 0xffff_ffff_ffff
equal(u.time, unix_ts_ms)
equal((u.int >> 80) & 0xffff_ffff_ffff, unix_ts_ms)
# 42-bit counter advanced by 1
equal(self.uuid._last_counter_v7, counter + 1)
equal((u.int >> 64) & 0xfff, counter_hi)
# 42-bit counter advanced by 1 (counter_hi is untouched)
equal((u.int >> 32) & 0x3fff_ffff, counter_lo + 1)
equal(u.int & 0xffff_ffff, tail)
def test_uuid7_overflow_counter(self):
equal = self.assertEqual
# 1 Jan 2023 12:34:56.123_456_789
timestamp_ns = 1672533296_123_456_789 # ns precision
timestamp_ms, _ = divmod(timestamp_ns, 1_000_000)
new_counter_hi = random.getrandbits(11)
new_counter_lo = random.getrandbits(30)
new_counter = (new_counter_hi << 30) | new_counter_lo
tail = random.getrandbits(32)
random_bits = (new_counter << 32) | tail
random_data = random_bits.to_bytes(10)
with (
mock.patch.multiple(
self.uuid,
_last_timestamp_v7=timestamp_ms,
# same timestamp, but force an overflow on the counter
_last_counter_v7=0x3ff_ffff_ffff,
),
mock.patch('time.time_ns', return_value=timestamp_ns),
mock.patch('os.urandom', return_value=random_data) as urand
):
u = self.uuid.uuid7()
urand.assert_called_with(10)
equal(u.variant, self.uuid.RFC_4122)
equal(u.version, 7)
# timestamp advanced due to overflow
equal(self.uuid._last_timestamp_v7, timestamp_ms + 1)
unix_ts_ms = (timestamp_ms + 1) & 0xffff_ffff_ffff
equal(u.time, unix_ts_ms)
equal((u.int >> 80) & 0xffff_ffff_ffff, unix_ts_ms)
# counter overflowed, so we picked a new one
equal(self.uuid._last_counter_v7, new_counter)
equal((u.int >> 64) & 0xfff, new_counter_hi)
equal((u.int >> 32) & 0x3fff_ffff, new_counter_lo)
equal(u.int & 0xffff_ffff, tail)
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). There are 122 bits of entropy and assuming
# that the underlying mt-19937-based random generator is sufficiently
# good, it is unlikely to have a collision of two UUIDs.
N = 1000
uuids = {self.uuid.uuid8() for _ in range(N)}
self.assertEqual(len(uuids), N)
versions = {u.version for u in uuids}
self.assertSetEqual(versions, {8})
@support.requires_fork()
def testIssue8621(self):
# On at least some versions of OSX self.uuid.uuid4 generates
@@ -710,6 +1140,23 @@ class BaseTestUUID:
weak = weakref.ref(strong)
self.assertIs(strong, weak())
class CommandLineTestCases:
uuid = None # to be defined in subclasses
def do_test_standalone_uuid(self, version):
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
self.uuid.main()
output = stdout.getvalue().strip()
u = self.uuid.UUID(output)
self.assertEqual(output, str(u))
self.assertEqual(u.version, version)
@mock.patch.object(sys, "argv", ["", "-u", "uuid1"])
def test_cli_uuid1(self):
self.do_test_standalone_uuid(1)
@mock.patch.object(sys, "argv", ["", "-u", "uuid3", "-n", "@dns"])
@mock.patch('sys.stderr', new_callable=io.StringIO)
def test_cli_namespace_required_for_uuid3(self, mock_err):
@@ -742,6 +1189,20 @@ class BaseTestUUID:
self.assertEqual(output, str(uuid_output))
self.assertEqual(uuid_output.version, 4)
@mock.patch.object(sys, "argv", ["", "-C", "3"])
def test_cli_uuid4_outputted_with_count(self):
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
self.uuid.main()
output = stdout.getvalue().strip().splitlines()
# Check that 3 UUIDs in the format of uuid4 have been generated
self.assertEqual(len(output), 3)
for o in output:
uuid_output = self.uuid.UUID(o)
self.assertEqual(uuid_output.version, 4)
@mock.patch.object(sys, "argv",
["", "-u", "uuid3", "-n", "@dns", "-N", "python.org"])
def test_cli_uuid3_ouputted_with_valid_namespace_and_name(self):
@@ -770,13 +1231,25 @@ class BaseTestUUID:
self.assertEqual(output, str(uuid_output))
self.assertEqual(uuid_output.version, 5)
@mock.patch.object(sys, "argv", ["", "-u", "uuid6"])
def test_cli_uuid6(self):
self.do_test_standalone_uuid(6)
class TestUUIDWithoutExtModule(BaseTestUUID, unittest.TestCase):
@mock.patch.object(sys, "argv", ["", "-u", "uuid7"])
def test_cli_uuid7(self):
self.do_test_standalone_uuid(7)
@mock.patch.object(sys, "argv", ["", "-u", "uuid8"])
def test_cli_uuid8(self):
self.do_test_standalone_uuid(8)
class TestUUIDWithoutExtModule(CommandLineTestCases, BaseTestUUID, unittest.TestCase):
uuid = py_uuid
@unittest.skipUnless(c_uuid, 'requires the C _uuid module')
class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase):
class TestUUIDWithExtModule(CommandLineTestCases, BaseTestUUID, unittest.TestCase):
uuid = c_uuid
def check_has_stable_libuuid_extractable_node(self):

341
Lib/uuid.py vendored
View File

@@ -1,8 +1,12 @@
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.
This module provides immutable UUID objects (class UUID) and functions for
generating UUIDs corresponding to a specific UUID version as specified in
RFC 4122/9562, e.g., uuid1() for UUID version 1, uuid3() for UUID version 3,
and so on.
Note that UUID version 2 is deliberately omitted as it is outside the scope
of the RFC.
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
@@ -42,10 +46,19 @@ Typical usage:
# make a UUID from a 16-byte string
>>> uuid.UUID(bytes=x.bytes)
UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')
# get the Nil UUID
>>> uuid.NIL
UUID('00000000-0000-0000-0000-000000000000')
# get the Max UUID
>>> uuid.MAX
UUID('ffffffff-ffff-ffff-ffff-ffffffffffff')
"""
import os
import sys
import time
from enum import Enum, _simple_enum
@@ -85,6 +98,19 @@ class SafeUUID:
unknown = None
_UINT_128_MAX = (1 << 128) - 1
# 128-bit mask to clear the variant and version bits of a UUID integral value
_RFC_4122_CLEARFLAGS_MASK = ~((0xf000 << 64) | (0xc000 << 48))
# RFC 4122 variant bits and version bits to activate on a UUID integral value.
_RFC_4122_VERSION_1_FLAGS = ((1 << 76) | (0x8000 << 48))
_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_7_FLAGS = ((7 << 76) | (0x8000 << 48))
_RFC_4122_VERSION_8_FLAGS = ((8 << 76) | (0x8000 << 48))
class UUID:
"""Instances of the UUID class represent UUIDs as specified in RFC 4122.
UUID objects are immutable, hashable, and usable as dictionary keys.
@@ -108,7 +134,16 @@ 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. Those attributes are not
always relevant to all UUID versions:
The 'time_*' attributes are only relevant to version 1.
The 'clock_seq*' and 'node' attributes are only relevant
to versions 1 and 6.
The 'time' attribute is only relevant to versions 1, 6
and 7.
time_low the first 32 bits of the UUID
time_mid the next 16 bits of the UUID
@@ -117,19 +152,20 @@ class UUID:
clock_seq_low the next 8 bits of the UUID
node the last 48 bits of the UUID
time the 60-bit timestamp
time the 60-bit timestamp for UUIDv1/v6,
or the 48-bit timestamp for UUIDv7
clock_seq the 14-bit sequence number
hex the UUID as a 32-character hexadecimal string
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
@@ -174,57 +210,69 @@ class UUID:
if [hex, bytes, bytes_le, fields, int].count(None) != 4:
raise TypeError('one of the hex, bytes, bytes_le, fields, '
'or int arguments must be given')
if hex is not None:
if int is not None:
pass
elif hex is not None:
hex = hex.replace('urn:', '').replace('uuid:', '')
hex = hex.strip('{}').replace('-', '')
if len(hex) != 32:
raise ValueError('badly formed hexadecimal UUID string')
int = int_(hex, 16)
if bytes_le is not None:
elif bytes_le is not None:
if len(bytes_le) != 16:
raise ValueError('bytes_le is not a 16-char string')
assert isinstance(bytes_le, bytes_), repr(bytes_le)
bytes = (bytes_le[4-1::-1] + bytes_le[6-1:4-1:-1] +
bytes_le[8-1:6-1:-1] + bytes_le[8:])
if bytes is not None:
int = int_.from_bytes(bytes) # big endian
elif bytes is not None:
if len(bytes) != 16:
raise ValueError('bytes is not a 16-char string')
assert isinstance(bytes, bytes_), repr(bytes)
int = int_.from_bytes(bytes) # big endian
if fields is not None:
elif fields is not None:
if len(fields) != 6:
raise ValueError('fields is not a 6-tuple')
(time_low, time_mid, time_hi_version,
clock_seq_hi_variant, clock_seq_low, node) = fields
if not 0 <= time_low < 1<<32:
if not 0 <= time_low < (1 << 32):
raise ValueError('field 1 out of range (need a 32-bit value)')
if not 0 <= time_mid < 1<<16:
if not 0 <= time_mid < (1 << 16):
raise ValueError('field 2 out of range (need a 16-bit value)')
if not 0 <= time_hi_version < 1<<16:
if not 0 <= time_hi_version < (1 << 16):
raise ValueError('field 3 out of range (need a 16-bit value)')
if not 0 <= clock_seq_hi_variant < 1<<8:
if not 0 <= clock_seq_hi_variant < (1 << 8):
raise ValueError('field 4 out of range (need an 8-bit value)')
if not 0 <= clock_seq_low < 1<<8:
if not 0 <= clock_seq_low < (1 << 8):
raise ValueError('field 5 out of range (need an 8-bit value)')
if not 0 <= node < 1<<48:
if not 0 <= node < (1 << 48):
raise ValueError('field 6 out of range (need a 48-bit value)')
clock_seq = (clock_seq_hi_variant << 8) | clock_seq_low
int = ((time_low << 96) | (time_mid << 80) |
(time_hi_version << 64) | (clock_seq << 48) | node)
if int is not None:
if not 0 <= int < 1<<128:
raise ValueError('int is out of range (need a 128-bit value)')
if not 0 <= int <= _UINT_128_MAX:
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)
int |= 0x8000 << 48
# clear the variant and the version number bits
int &= _RFC_4122_CLEARFLAGS_MASK
# Set the variant to RFC 4122/9562.
int |= 0x8000_0000_0000_0000 # (0x8000 << 48)
# Set the version number.
int &= ~(0xf000 << 64)
int |= version << 76
object.__setattr__(self, 'int', int)
object.__setattr__(self, 'is_safe', is_safe)
@classmethod
def _from_int(cls, value):
"""Create a UUID from an integer *value*. Internal use only."""
assert 0 <= value <= _UINT_128_MAX, repr(value)
self = object.__new__(cls)
object.__setattr__(self, 'int', value)
object.__setattr__(self, 'is_safe', SafeUUID.unknown)
return self
def __getstate__(self):
d = {'int': self.int}
if self.is_safe != SafeUUID.unknown:
@@ -281,9 +329,8 @@ class UUID:
raise TypeError('UUID objects are immutable')
def __str__(self):
hex = '%032x' % self.int
return '%s-%s-%s-%s-%s' % (
hex[:8], hex[8:12], hex[12:16], hex[16:20], hex[20:])
x = self.hex
return f'{x[:8]}-{x[8:12]}-{x[12:16]}-{x[16:20]}-{x[20:]}'
@property
def bytes(self):
@@ -322,8 +369,22 @@ class UUID:
@property
def time(self):
return (((self.time_hi_version & 0x0fff) << 48) |
(self.time_mid << 32) | self.time_low)
if self.version == 6:
# time_hi (32) | time_mid (16) | ver (4) | time_lo (12) | ... (64)
time_hi = self.int >> 96
time_lo = (self.int >> 64) & 0x0fff
return time_hi << 28 | (self.time_mid << 12) | time_lo
elif self.version == 7:
# unix_ts_ms (48) | ... (80)
return self.int >> 80
else:
# 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).
time_hi = (self.int >> 64) & 0x0fff
time_lo = self.int >> 96
return time_hi << 48 | (self.time_mid << 32) | time_lo
@property
def clock_seq(self):
@@ -336,7 +397,7 @@ class UUID:
@property
def hex(self):
return '%032x' % self.int
return self.bytes.hex()
@property
def urn(self):
@@ -355,7 +416,7 @@ class UUID:
@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)
@@ -374,7 +435,7 @@ def _get_command_stdout(command, *args):
# for are actually localized, but in theory some system could do so.)
env = dict(os.environ)
env['LC_ALL'] = 'C'
# Empty strings will be quoted by popen so we should just ommit it
# Empty strings will be quoted by popen so we should just omit it
if args != ('',):
command = (executable, *args)
else:
@@ -572,7 +633,7 @@ def _netstat_getnode():
try:
import _uuid
_generate_time_safe = getattr(_uuid, "generate_time_safe", None)
_has_stable_extractable_node = getattr(_uuid, "has_stable_extractable_node", False)
_has_stable_extractable_node = _uuid.has_stable_extractable_node
_UuidCreate = getattr(_uuid, "UuidCreate", None)
except ImportError:
_uuid = None
@@ -679,7 +740,6 @@ def uuid1(node=None, clock_seq=None):
return UUID(bytes=uuid_time, is_safe=is_safe)
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.
@@ -704,24 +764,171 @@ def uuid3(namespace, name):
"""Generate a UUID from the MD5 hash of a namespace UUID and a name."""
if isinstance(name, str):
name = bytes(name, "utf-8")
from hashlib import md5
digest = md5(
namespace.bytes + name,
usedforsecurity=False
).digest()
return UUID(bytes=digest[:16], version=3)
import hashlib
h = hashlib.md5(namespace.bytes + name, usedforsecurity=False)
int_uuid_3 = int.from_bytes(h.digest())
int_uuid_3 &= _RFC_4122_CLEARFLAGS_MASK
int_uuid_3 |= _RFC_4122_VERSION_3_FLAGS
return UUID._from_int(int_uuid_3)
def uuid4():
"""Generate a random UUID."""
return UUID(bytes=os.urandom(16), version=4)
int_uuid_4 = int.from_bytes(os.urandom(16))
int_uuid_4 &= _RFC_4122_CLEARFLAGS_MASK
int_uuid_4 |= _RFC_4122_VERSION_4_FLAGS
return UUID._from_int(int_uuid_4)
def uuid5(namespace, name):
"""Generate a UUID from the SHA-1 hash of a namespace UUID and a name."""
if isinstance(name, str):
name = bytes(name, "utf-8")
from hashlib import sha1
hash = sha1(namespace.bytes + name).digest()
return UUID(bytes=hash[:16], version=5)
import hashlib
h = hashlib.sha1(namespace.bytes + name, usedforsecurity=False)
int_uuid_5 = int.from_bytes(h.digest()[:16])
int_uuid_5 &= _RFC_4122_CLEARFLAGS_MASK
int_uuid_5 |= _RFC_4122_VERSION_5_FLAGS
return UUID._from_int(int_uuid_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) & 0xffff_ffff_ffff
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_lo << 64
int_uuid_6 |= clock_s << 48
int_uuid_6 |= node & 0xffff_ffff_ffff
# 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)
_last_timestamp_v7 = None
_last_counter_v7 = 0 # 42-bit counter
def _uuid7_get_counter_and_tail():
rand = int.from_bytes(os.urandom(10))
# 42-bit counter with MSB set to 0
counter = (rand >> 32) & 0x1ff_ffff_ffff
# 32-bit random data
tail = rand & 0xffff_ffff
return counter, tail
def uuid7():
"""Generate a UUID from a Unix timestamp in milliseconds and random bits.
UUIDv7 objects feature monotonicity within a millisecond.
"""
# --- 48 --- -- 4 -- --- 12 --- -- 2 -- --- 30 --- - 32 -
# unix_ts_ms | version | counter_hi | variant | counter_lo | random
#
# 'counter = counter_hi | counter_lo' is a 42-bit counter constructed
# with Method 1 of RFC 9562, §6.2, and its MSB is set to 0.
#
# 'random' is a 32-bit random value regenerated for every new UUID.
#
# If multiple UUIDs are generated within the same millisecond, the LSB
# of 'counter' is incremented by 1. When overflowing, the timestamp is
# advanced and the counter is reset to a random 42-bit integer with MSB
# set to 0.
global _last_timestamp_v7
global _last_counter_v7
nanoseconds = time.time_ns()
timestamp_ms = nanoseconds // 1_000_000
if _last_timestamp_v7 is None or timestamp_ms > _last_timestamp_v7:
counter, tail = _uuid7_get_counter_and_tail()
else:
if timestamp_ms < _last_timestamp_v7:
timestamp_ms = _last_timestamp_v7 + 1
# advance the 42-bit counter
counter = _last_counter_v7 + 1
if counter > 0x3ff_ffff_ffff:
# advance the 48-bit timestamp
timestamp_ms += 1
counter, tail = _uuid7_get_counter_and_tail()
else:
# 32-bit random data
tail = int.from_bytes(os.urandom(4))
unix_ts_ms = timestamp_ms & 0xffff_ffff_ffff
counter_msbs = counter >> 30
# keep 12 counter's MSBs and clear variant bits
counter_hi = counter_msbs & 0x0fff
# keep 30 counter's LSBs and clear version bits
counter_lo = counter & 0x3fff_ffff
# ensure that the tail is always a 32-bit integer (by construction,
# it is already the case, but future interfaces may allow the user
# to specify the random tail)
tail &= 0xffff_ffff
int_uuid_7 = unix_ts_ms << 80
int_uuid_7 |= counter_hi << 64
int_uuid_7 |= counter_lo << 32
int_uuid_7 |= tail
# by construction, the variant and version bits are already cleared
int_uuid_7 |= _RFC_4122_VERSION_7_FLAGS
res = UUID._from_int(int_uuid_7)
# defer global update until all computations are done
_last_timestamp_v7 = timestamp_ms
_last_counter_v7 = counter
return res
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
# by construction, the variant and version bits are already cleared
int_uuid_8 |= _RFC_4122_VERSION_8_FLAGS
return UUID._from_int(int_uuid_8)
def main():
@@ -730,7 +937,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 = {
@@ -742,18 +952,24 @@ def main():
import argparse
parser = argparse.ArgumentParser(
description="Generates a uuid using the selected uuid function.")
parser.add_argument("-u", "--uuid", choices=uuid_funcs.keys(), default="uuid4",
help="The function to use to generate the uuid. "
"By default uuid4 function is used.")
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Generate a UUID using the selected UUID function.",
color=True,
)
parser.add_argument("-u", "--uuid",
choices=uuid_funcs.keys(),
default="uuid4",
help="function to generate the UUID")
parser.add_argument("-n", "--namespace",
help="The namespace is a UUID, or '@ns' where 'ns' is a "
"well-known predefined UUID addressed by namespace name. "
"Such as @dns, @url, @oid, and @x500. "
"Only required for uuid3/uuid5 functions.")
choices=["any UUID", *namespaces.keys()],
help="uuid3/uuid5 only: "
"a UUID, or a well-known predefined UUID addressed "
"by namespace name")
parser.add_argument("-N", "--name",
help="The name used as part of generating the uuid. "
"Only required for uuid3/uuid5 functions.")
help="uuid3/uuid5 only: "
"name used as part of generating the UUID")
parser.add_argument("-C", "--count", metavar="NUM", type=int, default=1,
help="generate NUM fresh UUIDs")
args = parser.parse_args()
uuid_func = uuid_funcs[args.uuid]
@@ -768,9 +984,11 @@ def main():
"Run 'python -m uuid -h' for more information."
)
namespace = namespaces[namespace] if namespace in namespaces else UUID(namespace)
print(uuid_func(namespace, name))
for _ in range(args.count):
print(uuid_func(namespace, name))
else:
print(uuid_func())
for _ in range(args.count):
print(uuid_func())
# The following standard UUIDs are for use with uuid3() or uuid5().
@@ -780,5 +998,10 @@ NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8')
NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8')
NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8')
# RFC 9562 Sections 5.9 and 5.10 define the special Nil and Max UUID formats.
NIL = UUID('00000000-0000-0000-0000-000000000000')
MAX = UUID('ffffffff-ffff-ffff-ffff-ffffffffffff')
if __name__ == "__main__":
main()