Source code for task_script_utils.uuid
import os
from uuid import UUID as BaseUUID
from uuid import SafeUUID
# This implementation is taken from the proposed implementation for the python standard library:
# https://github.com/python/cpython/blob/99c3c63d2b22359374ecb9645b027b8cd2082249/Lib/uuid.py#L845
_last_timestamp_v7 = None
_last_counter_v7 = 0 # 42-bit counter
_RFC_4122_VERSION_7_FLAGS = (7 << 76) | (0x8000 << 48)
_UINT_128_MAX = (1 << 128) - 1
[docs]
class UUID(BaseUUID): # Extend for backwards compatibility with older python versions
@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 _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.
def get_counter_and_tail():
# Default is byteorder="big" for >=3.11
rand = int.from_bytes(os.urandom(10), byteorder="big")
# 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
global _last_timestamp_v7
global _last_counter_v7
import time
nanoseconds = time.time_ns()
timestamp_ms = nanoseconds // 1_000_000
if _last_timestamp_v7 is None or timestamp_ms > _last_timestamp_v7:
counter, tail = 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:
timestamp_ms += 1 # advance the 48-bit timestamp
counter, tail = get_counter_and_tail()
else:
# 32-bit random data. Default is byteorder="big" for >=3.11
tail = int.from_bytes(os.urandom(4), byteorder="big")
unix_ts_ms = timestamp_ms & 0xFFFF_FFFF_FFFF
counter_msbs = counter >> 30
counter_hi = counter_msbs & 0x0FFF # keep 12 counter's MSBs and clear variant bits
counter_lo = counter & 0x3FFF_FFFF # keep 30 counter's LSBs and clear version bits
# 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 _uuid():
"""Abstraction layer for UUID generation
This provides a fixed target for a fixture to patch, so that the UUID generation
can be controlled in a test environment.
"""
return _uuid7()
[docs]
def uuid():
"""Generate a random UUID
We are using UUID Version 7 for our random UUIDs.
These are time-ordered, meaning that sequentially generated UUIDs are
lexicographically sortable by creation time.
For more details on UUID Version 7, see
https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-7
"""
return _uuid()