Give new orders uuid7 ids
Python · Python · intermediate · modification
Moves new orders from uuid1 to uuid7 ids now that we are on Python 3.14 and `uuid.uuid7()` is in the standard library. uuid1 puts the low bits of the timestamp first and can embed a MAC-derived node identifier in the ids we hand to customers; uuid7 starts with the creation time in milliseconds, so new ids sort by creation time (to the millisecond) and inserts land at the end of the primary key index instead of all over it. Old orders keep their uuid1 ids, and `id_timestamp` now accepts both versions, so `placed_at` and the sales report keep reading the order time from the id for old and new orders alike. Nothing in the database changes.
Order service on CPython 3.14 (`requires-python = ">=3.14"`; production image and CI moved to 3.14 last month). Order ids are stored in a PostgreSQL `uuid` primary key column. `new_order_id` is called once per order by `OrderService.create`, which runs in Gunicorn sync workers (one thread per process). `orders/ids.py` is the only place that generates order ids or reads a time out of them; `GET /orders/{id}` serialises `placed_at` with `isoformat()`, and the nightly sales report groups orders by the UTC date of `id_timestamp(order.id)`.
Requirements
- New orders get their id from `new_order_id()`, which now returns `uuid.uuid7()` (standard library since Python 3.14, RFC 9562 version 7) instead of `uuid.uuid1()`. Existing orders keep their uuid1 ids; no stored id is rewritten.
- `id_timestamp(order_id)` returns the creation time embedded in an order id, as an aware UTC `datetime`: for a uuid1 id, the RFC 9562 version 1 timestamp (100-nanosecond intervals since 1582-10-15 00:00 UTC, truncated to the microsecond); for a uuid7 id, its 48-bit Unix timestamp in milliseconds. It must be right for both kinds of id, old orders and new.
- `id_timestamp` raises `ValueError` for any other UUID version. Callers only pass ids read from the `orders` table, which holds uuid1 and uuid7 ids generated by this service since 2019.
- `placed_at` in `GET /orders/{id}` and the order dates in the daily sales report both come from `id_timestamp` (existing callers, unchanged).
Files touched
- orders/ids.py
- tests/test_ids.py
--- orders/ids.py
from datetime import UTC, datetime, timedelta
-_UUID1_EPOCH = datetime(1582, 10, 15, tzinfo=UTC)
+# Orders created before this change carry uuid1 ids, newer ones uuid7.
+# Both are time-based, so the creation time can be read from the id.
+_TIME_BASED_VERSIONS = (1, 7)
+_GREGORIAN_EPOCH = datetime(1582, 10, 15, tzinfo=UTC)
def new_order_id() -> uuid.UUID:
- """A fresh id for a new order."""
- return uuid.uuid1()
+ """A fresh id for a new order.
+
+ uuid7 starts with its creation time in milliseconds, so ids sort by
+ creation time (to the millisecond) and new rows go to the end of the
+ primary key index. Unlike uuid1, it has no MAC-derived node field.
+ """
+ return uuid.uuid7()
def id_timestamp(order_id: uuid.UUID) -> datetime:
"""When the order id was generated, as an aware UTC datetime."""
- if order_id.version != 1:
+ if order_id.version not in _TIME_BASED_VERSIONS:
raise ValueError(f"not a time-based order id: {order_id}")
- return _UUID1_EPOCH + timedelta(microseconds=order_id.time // 10)
+ return _GREGORIAN_EPOCH + timedelta(microseconds=order_id.time // 10)