commit 94f31c7d0b48097984a91b7492289d938e6d4878 Author: Leo Vasanko Date: Thu Jul 3 13:27:12 2025 -0600 Publish package, v1.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a69a207 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +dist/ +.* +!.gitignore +*.lock \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f16ca38 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# UUIDv7 for Python + +A simple module for generating UUIDv7 that contain creation +timestamps. Another function for extracting the time of an UUID. + +Note: As of writing, Python has no UUIDv7 support. There's an abandoned package `uuid7` that uses a draft RFC with incorrect timestamps (some two centuries off). These modules conflict, uninstall the other one. + +- **Standard compliant**: Follows the final UUIDv7 [specification](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7). +- **Pythonic**: Uses stdlib `datetime` and `UUID` facilities rather than milliseconds or bare strings. + +## Installation + +```sh +pip install uuid7-standard +``` + +Or for your project using [uv](https://docs.astral.sh/uv/): +```sh +uv add uuid7-standard +``` + +## Usage + +```python +import uuid7 + +# Create a random UUIDv7 with current timestamp +u = uuid7.create() +print(str(u), u.bytes) + +# Create with specific timestamp +from datetime import datetime, UTC + +when = datetime(1970, 1, 1, tzinfo=UTC) +u = uuid7.create(when) + +# Extract timestamp +from uuid import UUID + +u = UUID('00000000-0000-7dac-b3e3-ecb571bb3e2f') +timestamp = uuid7.time(u) # 1970-01-01 UTC +``` + +### `create(when: datetime?) -> UUID` + +Create a UUIDv7 with timestamp-based ordering. + +The current time is used, unless `when` is passed as datetime (local time or timezone-aware) This is useful e.g. for creating a bunch of UUIDv7 with precisely the same timestamp. + +### `time(u: UUID|str) -> datetime` + +Extract the timestamp from a UUIDv7. Raises ValueError if the UUID is not a UUIDv7. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bf20797 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,31 @@ +[project] +name = "uuid7-standard" +version = "1.0.0" +description = "UUIDv7 with the final standard. Not to be confused with the uuid7 package on pypi, based on a draft version that was very different." +readme = "README.md" +requires-python = ">=3.10" +dependencies = [] +authors = [ + {name = "Leo Vasanko"}, +] +keywords = ["uuid", "uuid7", "timestamp", "ordering", "database", "primary-key"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "License :: Public Domain", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities", +] + +[project.urls] +Homepage = "https://git.zi.fi/leo/uuid7-python" +Repository = "https://github.com/LeoVasanko/uuid7-python" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["uuid7.py"] diff --git a/uuid7.py b/uuid7.py new file mode 100644 index 0000000..149682d --- /dev/null +++ b/uuid7.py @@ -0,0 +1,34 @@ +from datetime import datetime +from datetime import timezone as _tz +from secrets import token_bytes as _token_bytes +from uuid import UUID + +__all__ = ["create", "time"] + + +def create(when: datetime | None = None) -> UUID: + """Create a UUIDv7 with timestamp-based ordering. + + Args: + when: Timestamp to use. Defaults to current time. + """ + if when is None: + when = datetime.now() + ts = int(when.timestamp() * 1000).to_bytes(6, "big") + rand = bytearray(_token_bytes(10)) + rand[0] = (rand[0] & 0x0F) | 0x70 + rand[2] = (rand[2] & 0x3F) | 0x80 + return UUID(bytes=ts + rand) + + +def time(u: UUID | str) -> datetime: + """Extract the timestamp from a UUIDv7. + + Raises ValueError if u is not a UUID version 7. + """ + if not isinstance(u, UUID): + u = UUID(u) + if u.version != 7: + raise ValueError("Not a UUIDv7") + ts = int.from_bytes(u.bytes[:6], "big") + return datetime.fromtimestamp(ts / 1000, tz=_tz.utc)