|
| 1 | +""" |
| 2 | +Generate time-sortable UUIDs (version 7) |
| 3 | +
|
| 4 | +Provides a `uuid7` function that's either directly imported from Python's `uuid` module |
| 5 | +(if it's available) or an implementation based on a pull request to add it to CPython. |
| 6 | +""" |
| 7 | + |
| 8 | +import uuid |
| 9 | + |
| 10 | +if hasattr(uuid, "uuid7"): |
| 11 | + # Future Python versions will (hopefully) have this function built-in |
| 12 | + # Issue: https://github.com/python/cpython/issues/89083 |
| 13 | + uuid7 = uuid.uuid7 |
| 14 | +else: |
| 15 | + # Taken from the CPython pull request: |
| 16 | + # * https://github.com/python/cpython/pull/120650 |
| 17 | + # * Commit (2024-06-19T07:39:18Z) |
| 18 | + # * https://github.com/python/cpython/blob/295d82d513717adb96d9faab7e255dbedb237d88/Lib/uuid.py#L770-L790 |
| 19 | + # Modifications: |
| 20 | + # * Added noqa comments to supress ruff warnings |
| 21 | + # * Manually set the variant and version bits. |
| 22 | + |
| 23 | + _last_timestamp_v7 = None |
| 24 | + |
| 25 | + def uuid7(): |
| 26 | + """Generate a UUID from a Unix timestamp in milliseconds and random bits.""" |
| 27 | + global _last_timestamp_v7 # noqa: PLW0603 (global-statement) |
| 28 | + import os # noqa: PLC0415 (import-outside-toplevel) |
| 29 | + import time # noqa: PLC0415 (import-outside-toplevel) |
| 30 | + |
| 31 | + nanoseconds = time.time_ns() |
| 32 | + timestamp_ms = nanoseconds // 10**6 # may be improved |
| 33 | + if _last_timestamp_v7 is not None and timestamp_ms <= _last_timestamp_v7: |
| 34 | + timestamp_ms = _last_timestamp_v7 + 1 |
| 35 | + _last_timestamp_v7 = timestamp_ms |
| 36 | + int_uuid_7 = (timestamp_ms & 0xFFFFFFFFFFFF) << 80 |
| 37 | + # Ideally, we would have 'rand_a' = first 12 bits of 'rand' |
| 38 | + # and 'rand_b' = lowest 62 bits, but it is easier to test |
| 39 | + # when we pick 'rand_a' from the lowest bits of 'rand' and |
| 40 | + # 'rand_b' from the next 62 bits, ignoring the 6 first bits |
| 41 | + # of 'rand'. |
| 42 | + rand = int.from_bytes(os.urandom(10)) # 80 random bits (ignore 6 first) |
| 43 | + int_uuid_7 |= (rand & 0x0FFF) << 64 # rand_a |
| 44 | + int_uuid_7 |= (rand >> 12) & 0x3FFFFFFFFFFFFFFF # rand_b |
| 45 | + |
| 46 | + # Manually set the variant and version bits, to avoid a |
| 47 | + # `ValueError('illegal version number')` when calling the UUID constructor |
| 48 | + # with `version` set to 7. |
| 49 | + # Copied from UUID.__init__, hardcoded version: |
| 50 | + # https://github.com/python/cpython/blob/a86e6255c371e14cab8680dee979a7393b339ce5/Lib/uuid.py#L219-L224 |
| 51 | + |
| 52 | + # Set the variant to RFC 4122. |
| 53 | + int_uuid_7 &= ~(0xC000 << 48) |
| 54 | + int_uuid_7 |= 0x8000 << 48 |
| 55 | + # Set the version number to 7. |
| 56 | + int_uuid_7 &= ~(0xF000 << 64) |
| 57 | + int_uuid_7 |= 7 << 76 |
| 58 | + |
| 59 | + return uuid.UUID(int=int_uuid_7) |
0 commit comments