Python 3.13, released in October 2024, added uuid.uuid7() and uuid.uuid8() to the standard library’s uuid module. For projects on Python 3.13+, UUID v7 generation now requires no third-party dependency — it is a single import and a function call.
The new functions
import uuid
# UUID v7 — time-ordered, RFC 9562-compliant
u7 = uuid.uuid7()
print(u7)
# 019236a7-b4f2-7000-8d3e-9c1a2b3d4e5f
# UUID v8 — custom/application-defined layout
# Takes a 128-bit integer; you control all non-fixed bits
u8 = uuid.uuid8(hi=0x0123456789ab, mid=0xcdef, lo=0x0123456789abcdef)
print(u8)
Both return a uuid.UUID object — the same type returned by uuid.uuid4(). All existing code that works with UUID objects (comparisons, string conversion, JSON serialisation via str(u)) works unchanged.
uuid.uuid7() implementation details
The Python 3.13 implementation follows RFC 9562 §5.7 exactly:
- The first 48 bits are the current Unix timestamp in milliseconds from
time.time_ns() // 1_000_000 - Bits 48–51 are the version field (
0111→7) - Bits 52–63 are a 12-bit sub-millisecond sequence counter (Method 1 from RFC 9562 §6.2)
- Bits 64–65 are the variant field (
10) - Bits 66–127 are drawn from
os.urandom()
The monotonic counter ensures that two calls to uuid.uuid7() within the same millisecond produce values that sort in the order they were generated. The counter resets when the millisecond advances and is not shared across processes.
Upgrading from uuid6
If your project uses the uuid6 third-party package for Python 3.9–3.12 compatibility, the migration to the standard library is straightforward:
# Before (uuid6 package)
import uuid6
user_id = uuid6.uuid7()
# After (Python 3.13+ standard library)
import uuid
user_id = uuid.uuid7()
The return type is identical — both return a uuid.UUID object with the same interface. String output format is the same. No other code changes are needed.
For packages that need to support Python 3.9–3.13, use a compatibility shim:
import sys
if sys.version_info >= (3, 13):
from uuid import uuid7
else:
from uuid6 import uuid7 # pip install uuid6
Django integration
Django’s UUIDField accepts any uuid.UUID value. To use v7 as a primary key default:
import uuid
from django.db import models
class Event(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid7, # Pass the callable, not the result
editable=False,
)
name = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
Django calls the default callable on each new instance. The UUIDField stores the 128-bit value as uuid in PostgreSQL and as CHAR(32) (hex, no hyphens) in SQLite and MySQL.
SQLAlchemy integration
import uuid
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped
from sqlalchemy.dialects.postgresql import UUID
class Base(DeclarativeBase):
pass
class Order(Base):
__tablename__ = "orders"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid7,
)
Using UUID(as_uuid=True) stores the value natively in PostgreSQL’s uuid column type and returns it as a Python uuid.UUID object rather than a string.
FastAPI and Pydantic
Pydantic v2 handles uuid.UUID natively. FastAPI path parameters and response models work without any change:
import uuid
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
id: uuid.UUID
name: str
@app.post("/items/", response_model=Item)
async def create_item(name: str):
return Item(id=uuid.uuid7(), name=name)
Pydantic serialises uuid.UUID to a string in the canonical 8-4-4-4-12 format. Both v4 and v7 values pass validation as uuid.UUID — the type does not check the version digit.
What about uuid.uuid8()?
uuid.uuid8() takes a single 128-bit integer argument and sets the version and variant bits automatically. The remaining 122 bits are whatever you provide. It is intended for application-defined UUID formats that need to signal “this is a UUID-shaped thing” without following any standard layout.
In practice most applications should not use v8. It exists for specialised cases: embedding application-specific metadata in an identifier, implementing a custom time-ordered format, or encoding a foreign key relationship in the UUID itself. Unless you have a specific reason to control the bit layout, use v7 for time-ordered keys and v4 for random keys.
Version support summary
| Python version | uuid.uuid7() | Package required |
|---|---|---|
| 3.13+ | Built-in | None |
| 3.9–3.12 | Not built-in | pip install uuid6 |
| < 3.9 | Not available | — |
Further reading
- UUID in Python — full guide to the uuid module, Django, SQLAlchemy, FastAPI, and Pydantic patterns
- UUID v4 vs UUID v7 — when the timestamp in v7 is an asset and when it is a liability
- UUID in Databases — storage types and index performance for PostgreSQL, MySQL, and SQLite
Frequently asked questions
How do I generate UUID v7 in Python?
In Python 3.13+, use uuid.uuid7() from the standard library. For Python 3.9–3.12, install the uuid6 package (pip install uuid6) and use uuid6.uuid7(). Both return a uuid.UUID object with the same interface as uuid.uuid4().
Does Python support UUID v7?
Yes, from Python 3.13 via uuid.uuid7() in the standard library. For Python 3.9–3.12, install the uuid6 package which provides the same interface.
What column type should I use to store a UUID in a database?
Use the native type where one exists — uuid in PostgreSQL, uniqueidentifier in SQL Server. In MySQL use BINARY(16) and store raw bytes. Avoid VARCHAR(36): it costs 36 bytes per row versus 16 for binary and makes every comparison a string operation.
What Python version do I need for uuid.uuid7()?
Python 3.13 or later. For Python 3.9–3.12, install the uuid6 package (pip install uuid6) which provides uuid6.uuid7() with the same interface.
Is uuid.uuid7() in Python 3.13 RFC 9562-compliant?
Yes. The implementation follows RFC 9562 §5.7 and includes a sub-millisecond counter to maintain sort order for UUIDs generated within the same millisecond, matching the Method 1 monotonic behaviour described in §6.2.