Created
February 4, 2025 20:10
-
-
Save rayansostenes/254d9e23f51282a2a5703f22898f80bb to your computer and use it in GitHub Desktop.
Python UUIDv7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import datetime | |
import functools | |
import itertools | |
import operator | |
import os | |
import time | |
import uuid | |
import rich | |
class _GlobalCounter: | |
def __init__(self): | |
self._incs = itertools.count(1) | |
self._last_value = 0 | |
def next(self): | |
if self._last_value > 2**14 - 2: | |
self._incs = itertools.count(1) | |
value = next(self._incs) | |
self._last_value = value | |
return value | |
_global_counter = _GlobalCounter() | |
def _print_bytes(*numbers: int): | |
console = rich.get_console() | |
breaks = [(0, 48), (48, 52), (52, 64), (64, 66), (66, 114), (114, 128)] | |
colors = ["magenta", "cyan", "green", "yellow", "blue", "red"] | |
for n, (start, end), color in zip(numbers, breaks, colors, strict=False): | |
# format the slice using color leave the rest as dim | |
string = f"{n:0128b}" | |
prefix = string[:start] | |
colored = string[start:end] | |
suffix = string[end:] | |
final_string = f"[dim]{prefix}[/][{color}]{colored}[/][dim]{suffix}[/]" | |
if prefix: | |
assert prefix == "0" * len(prefix) | |
if suffix: | |
assert suffix == "0" * len(suffix) | |
console.print(final_string, highlight=False) | |
final_number = functools.reduce(operator.or_, numbers) | |
final_string = f"{final_number:0128b}" | |
for (start, end), color in zip(breaks, colors, strict=False): | |
console.print(final_string[start:end], style=color, highlight=False, end="") | |
console.print() | |
def uuid7(unix_ts_ms: int | datetime.datetime | None = None) -> uuid.UUID: | |
""" | |
UUID v7, following the proposed extension to RFC4122 described in | |
https://www.ietf.org/id/draft-peabody-dispatch-new-uuid-format-02.html. | |
All representations (string, byte array, int) sort chronologically, | |
with a potential time resolution of 50ns (if the system clock | |
supports this). | |
Parameters | |
---------- | |
unix_ts_ms | |
Optional integer with the whole number of milliseconds since Unix epoch, | |
or a datetime object. If not given, the current system time is used. | |
Implementation notes | |
-------------------- | |
RFC Draft: https://datatracker.ietf.org/doc/rfc9562/ | |
0 1 2 3 | |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| unix_ts_ms | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| unix_ts_ms | ver | micro_seconds | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
|var| rand | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| | counter | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
unix_ts_ms: | |
48-bit big-endian unsigned number of the Unix Epoch timestamp in | |
milliseconds as per Section 6.1. Occupies bits 0 through 47 | |
ver: | |
The 4-bit version field as defined by Section 4.2, set to 0b0111 | |
(7). Occupies bits 48 through 51. | |
sub_ms: | |
12 bits big-endian unsigned number representing the sub-millisecond | |
portion of the timestamp. Occupies bits 52 through 63. | |
var: | |
The 2-bit variant field as defined by Section 4.1, set to 0b10. | |
Occupies bits 64 and 65. | |
rand: | |
48 bits of pseudorandom data to provide uniqueness as per Section 6.9. | |
Occupies bits 66 through 113. | |
counter: | |
14 bits of a monotonically increasing counter to provide uniqueness | |
Occupies bits 114 through 127. | |
""" | |
if unix_ts_ms is None: | |
unix_ts_ms, _rest = divmod(time.time_ns(), 1_000_000) | |
micro_seconds = _rest // 1_000 | |
elif isinstance(unix_ts_ms, int): | |
micro_seconds = 0 | |
elif isinstance(unix_ts_ms, datetime.datetime): | |
ms, _rest = divmod(unix_ts_ms.timestamp() * 1_000, 1) | |
unix_ts_ms, micro_seconds = int(ms), int(_rest * 1_000) | |
else: | |
msg = "Invalid type for unix_ts_ms" | |
raise AssertionError(msg) | |
uuid_int = ( | |
unix_ts_ms << 80 # 48 bits for unix_ts_ms | |
| 0b0111 << 76 # 4 bits for version (7) | |
| micro_seconds << 64 # 12 bits for sub_ms | |
| 0b10 << 62 # 2 bits for variant | |
| int.from_bytes(os.urandom(6), "big") << 14 # 48 random bits | |
| _global_counter.next() # 16 bits for monotonic counter | |
) | |
return uuid.UUID(int=uuid_int) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment