Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: oittaa/uuid6-python
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2022.01.31
Choose a base ref
...
head repository: oittaa/uuid6-python
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Loading
Showing with 447 additions and 182 deletions.
  1. +10 −9 .github/workflows/main.yml
  2. +5 −5 .github/workflows/publish-to-test-pypi.yml
  3. +111 −72 README.md
  4. +7 −5 bench.sh
  5. +4 −4 requirements-dev.txt
  6. +10 −4 setup.py
  7. +83 −54 src/uuid6/__init__.py
  8. 0 src/uuid6/py.typed
  9. +73 −29 test/test_uuid6.py
  10. +144 −0 test/test_vectors.py
19 changes: 10 additions & 9 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -10,21 +10,23 @@ on:

jobs:
lint:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- uses: psf/black@stable

build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -49,7 +51,6 @@ jobs:
- name: Benchmark
run: ./bench.sh
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2.1.0
if: matrix.python-version == '3.10'
uses: codecov/codecov-action@v5
with:
flags: unittests
10 changes: 5 additions & 5 deletions .github/workflows/publish-to-test-pypi.yml
Original file line number Diff line number Diff line change
@@ -7,11 +7,11 @@ jobs:
build-n-publish:
if: startsWith(github.ref, 'refs/tags')
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v4
- name: Set up Python 3.x
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install pypa/build
@@ -29,11 +29,11 @@ jobs:
--outdir dist/
.
- name: Publish distribution 📦 to Test PyPI
uses: pypa/gh-action-pypi-publish@v1.5.0
uses: pypa/gh-action-pypi-publish@v1.12.2
with:
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@v1.5.0
uses: pypa/gh-action-pypi-publish@v1.12.2
with:
password: ${{ secrets.PYPI_API_TOKEN }}
183 changes: 111 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ New time-based UUID formats which are suited for use as a database key.
[![Python versions supported](https://img.shields.io/pypi/pyversions/uuid6.svg?logo=python)](https://pypi.org/project/uuid6/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

This module extends immutable UUID objects (the UUID class) with the functions `uuid6()` and `uuid7()` from [the IETF draft][ietf draft].
This module extends immutable UUID objects (the UUID class) with the functions `uuid6()`, `uuid7()`, and `uuid8()` from the proposed [IETF RFC 9562][rfc9562].

## Install

@@ -18,109 +18,148 @@ pip install uuid6
## Usage

```python
from uuid6 import uuid6, uuid7
import uuid6

my_uuid = uuid6()
my_uuid = uuid6.uuid6()
print(my_uuid)
assert my_uuid < uuid6()
assert my_uuid < uuid6.uuid6()

my_uuid = uuid7()
my_uuid = uuid6.uuid7()
print(my_uuid)
assert my_uuid < uuid7()
assert my_uuid < uuid6.uuid7()

my_uuid = uuid6.uuid8()
print(my_uuid)
assert my_uuid < uuid6.uuid8()

import uuid

my_uuid = uuid.UUID(hex="C232AB00-9414-11EC-B3C8-9E6BDECED846")
assert uuid6.uuid1_to_uuid6(my_uuid) == uuid.UUID(hex="1EC9414C-232A-6B00-B3C8-9E6BDECED846")
```

## UUIDv6 Field and Bit Layout
## Which UUID version should I use?

> Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
UUID version 7 features a time-ordered value field derived from the widely implemented and well known Unix Epoch timestamp source, the number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds excluded. As well as improved entropy characteristics over versions 1 or 6.

If your use case requires greater granularity than UUID version 7 can provide, you might consider UUID version 8. UUID version 8 doesn't provide as good entropy characteristics as UUID version 7, but it utilizes timestamp with nanosecond level of precision.

## Functions

### uuid6.uuid1_to_uuid6(*uuid1*)

Generate a UUID version 6 object from a UUID version 1 object.

### uuid6.uuid6(*node=None*, *clock_seq=None*)

Generate a UUID from a host ID, sequence number, and the current time. If *node* is not given, a random 48-bit number is chosen. If *clock_seq* is given, it is used as the sequence number; otherwise a random 14-bit sequence number is chosen. This function is *not* thread-safe.

### uuid6.uuid7()

Generate a UUID from a random number, and the current time. This function is *not* thread-safe.

### uuid6.uuid8()

Generate a UUID from a random number, and the current time. This function is *not* thread-safe.

## UUID Version 6

UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality. It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs. Systems that do not involve legacy UUIDv1 **SHOULD** use UUIDv7 instead.

### UUIDv6 Field and Bit Layout

```
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_mid | time_low_and_version |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|clk_seq_hi_res | clk_seq_low | node (0-1) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| node (2-5) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_mid | ver | time_low |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| clock_seq | node |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| node |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

## UUIDv7 Field and Bit Layout
The `time_high`, `time_mid`, and `time_low` fields guarantee the order of UUIDs generated within the same timestamp by monotonically incrementing the timer.

### [Draft 02][draft 02]
## UUID Version 7

UUID version 7 features a time-ordered value field derived from the widely implemented and well known Unix Epoch timestamp source, the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded. UUID version 7 also has improved entropy characteristics over versions 1 or 6.

### UUIDv7 Field and Bit Layout

```
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unixts |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|unixts | subsec_a | ver | subsec_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| subsec_seq_node |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| subsec_seq_node |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | rand_a |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

### This implementation
The `unix_ts_ms` field guarantees the order of UUIDs generated within the same millisecond by monotonically incrementing the timer.

## UUID Version 8

UUID version 8 provides an RFC-compatible format for experimental or vendor-specific use cases.

This implementation of `uuid8()` sacrifices some entropy for granularity compared to `uuid7()`, while being otherwise compatible.

### UUIDv8 Field and Bit Layout

```
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unixts |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|unixts | subsec_a | ver | subsec_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| subsec_c | rand |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | subsec_a |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| subsec_b | rand |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

- `unixts`: 36 bit big-endian unsigned Unix Timestamp value
- `unix_ts_ms`: 48 bit big-endian unsigned number of Unix epoch timestamp with millisecond level of precision
- `ver`: The 4 bit UUIDv8 version (1000)
- `subsec_a`: 12 bits allocated to sub-second precision values
- `ver`: The 4 bit UUIDv7 version (0111)
- `subsec_b`: 12 bits allocated to sub-second precision values
- `var`: 2 bit UUID variant (10)
- `subsec_c`: 6 bits allocated to sub-second precision values
- `rand`: The remaining 56 bits are filled with pseudo-random data

30 bits dedicated to sub-second precision provide nanosecond resolution. The `unixts` and `subsec` fields guarantee the order of UUIDs generated within the same nanosecond by monotonically incrementing the timer.
- `subsec_b`: 8 bits allocated to sub-second precision values
- `rand`: The remaining 54 bits are filled with [cryptographically strong random data][python randbits]

This implementation does not include a clock sequence counter as defined in the draft RFC.
20 extra bits dedicated to sub-second precision provide nanosecond resolution. The `unix_ts_ms`, `subsec_a`, and `subsec_b` fields guarantee the order of UUIDs generated within the same nanosecond by monotonically incrementing the timer.

## Performance

MacBook Air
```
Python 3.9.7 (default, Oct 12 2021, 22:38:23)
[Clang 13.0.0 (clang-1300.0.29.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> timeit.timeit('uuid1()', number=100000, setup="from uuid import uuid1")
0.14462158300011652
>>> timeit.timeit('uuid6()', number=100000, setup="from uuid6 import uuid6")
0.2687861250000019
>>> timeit.timeit('uuid7()', number=100000, setup="from uuid6 import uuid7")
0.22819437500000106
```
Run the shell script [bench.sh][bench] to test on your own machine.

### Results

Google [Cloud Shell][cloud shell] VM
MacBook Air 2020
```
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> timeit.timeit('uuid1()', number=100000, setup="from uuid import uuid1")
1.2075679750000745
>>> timeit.timeit('uuid6()', number=100000, setup="from uuid6 import uuid6")
0.6328954440000416
>>> timeit.timeit('uuid7()', number=100000, setup="from uuid6 import uuid7")
0.4709622599998511
Python 3.12.4
Mean +- std dev: 899 ns +- 8 ns
Mean +- std dev: 1.22 us +- 0.01 us
Mean +- std dev: 2.18 us +- 0.02 us
Mean +- std dev: 1.54 us +- 0.01 us
Mean +- std dev: 1.73 us +- 0.01 us
+-----------+--------+-----------------------+-----------------------+-----------------------+-----------------------+
| Benchmark | uuid1 | uuid4 | uuid6 | uuid7 | uuid8 |
+===========+========+=======================+=======================+=======================+=======================+
| timeit | 899 ns | 1.22 us: 1.36x slower | 2.18 us: 2.43x slower | 1.54 us: 1.71x slower | 1.73 us: 1.92x slower |
+-----------+--------+-----------------------+-----------------------+-----------------------+-----------------------+
```

[ietf draft]: https://github.com/uuid6/uuid6-ietf-draft
[draft 02]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02#section-4.4
[cloud shell]: https://cloud.google.com/shell/docs
[rfc9562]: https://datatracker.ietf.org/doc/rfc9562/
[python randbits]: https://docs.python.org/3/library/secrets.html#secrets.randbits
[bench]: https://github.com/oittaa/uuid6-python/blob/main/bench.sh
12 changes: 7 additions & 5 deletions bench.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#!/bin/bash
set -eu
TESTDIR=$(mktemp -d)
python -m pyperf timeit -o "${TESTDIR}/uuid1.json" -s "import uuid" "uuid.uuid1()"
python -m pyperf timeit -o "${TESTDIR}/uuid4.json" -s "import uuid" "uuid.uuid4()"
python -m pyperf timeit -o "${TESTDIR}/uuid6.json" -s "import uuid6" "uuid6.uuid6()"
python -m pyperf timeit -o "${TESTDIR}/uuid7.json" -s "import uuid6" "uuid6.uuid7()"
python -m pyperf compare_to --table "${TESTDIR}/uuid1.json" "${TESTDIR}/uuid4.json" "${TESTDIR}/uuid6.json" "${TESTDIR}/uuid7.json"
python --version
python -m pyperf timeit -q -o "${TESTDIR}/uuid1.json" -s "import uuid" "uuid.uuid1()"
python -m pyperf timeit -q -o "${TESTDIR}/uuid4.json" -s "import uuid" "uuid.uuid4()"
python -m pyperf timeit -q -o "${TESTDIR}/uuid6.json" -s "import uuid6" "uuid6.uuid6()"
python -m pyperf timeit -q -o "${TESTDIR}/uuid7.json" -s "import uuid6" "uuid6.uuid7()"
python -m pyperf timeit -q -o "${TESTDIR}/uuid8.json" -s "import uuid6" "uuid6.uuid8()"
python -m pyperf compare_to --table "${TESTDIR}/uuid1.json" "${TESTDIR}/uuid4.json" "${TESTDIR}/uuid6.json" "${TESTDIR}/uuid7.json" "${TESTDIR}/uuid8.json"
rm -rf -- "${TESTDIR}"
8 changes: 4 additions & 4 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
coverage[toml]==6.3
flake8==4.0.1
mypy==0.931
pyperf==2.3.0
coverage[toml]==7.6.7
flake8==7.1.1
mypy==1.13.0
pyperf==2.8.1
14 changes: 10 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
@@ -33,25 +33,31 @@
"uuid",
"uuid6",
"uuid7",
"uuid8",
"uuidv6",
"uuidv7",
"uuidv8",
],
classifiers=[
"Development Status :: 3 - Alpha",
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
],
setup_requires=[
"wheel",
],
package_dir={"": "src"},
package_data={
"": ["py.typed"],
},
packages=find_packages(where="src"),
python_requires=">=3.7",
python_requires=">=3.9",
)
Loading