Skip to main content

UPLID

██╗   ██╗██████╗ ██╗     ██╗██████╗
██║   ██║██╔══██╗██║     ██║██╔══██╗
██║   ██║██████╔╝██║     ██║██║  ██║
██║   ██║██╔═══╝ ██║     ██║██║  ██║
╚██████╔╝██║     ███████╗██║██████╔╝
 ╚═════╝ ╚═╝     ╚══════╝╚═╝╚═════╝

Stripe-style IDs for Python.

# Before: WTF is this?
"550e8400-e29b-41d4-a716-446655440000"

# After: It's a user.
"usr_0M3xL9kQ7vR2nP5wY1jZ4c"

CI PyPI Python Coverage

About

Install

pip install uplid

Requires Python 3.14+ and Pydantic 2.10+.

Quick Start

>>> from uplid import UPLID
>>> UPLID.generate("usr")
usr_0M3xL9kQ7vR2nP5wY1jZ4c
>>> UPLID.generate("ord")
ord_7x9KmNpQrStUvWxYz012Ab

Why UPLID?

Debuggable - See usr_ in your logs and instantly know it's a user, not an order, not a session, not a mystery.

# Your logs now:
"User usr_0M3xL9kQ7vR2nP5wY1jZ4c created order ord_1a2B3c4D5e6F7g..."

# vs the nightmare:
"User 550e8400-e29b-41d4... created order 7c9e6679-7425-40de..."

Type-safe - Your type checker catches user_id = order_id mistakes before they hit production.

UserId = UPLID[Literal["usr"]]
OrgId = UPLID[Literal["org"]]

def get_user(user_id: UserId) -> User: ...

get_user(org_id)  # Type error! Caught by mypy/pyright/ty

Time-sortable - Built on UUIDv7. Sort by ID = sort by creation time. No extra column needed.

URL-safe - 26 characters, no special characters, no encoding. usr_0M3xL9kQ7vR2nP5wY1jZ4c

Minimal dependencies - Just Pydantic. UUID generation uses Python 3.14's stdlib uuid7().

Inspired by Stripe's prefixed IDs (sk_live_..., cus_..., pi_...) - the same pattern trusted by millions of API calls daily.

Pydantic Integration

from typing import Literal
from pydantic import BaseModel, Field
from uplid import UPLID, factory

UserId = UPLID[Literal["usr"]]

class User(BaseModel):
    id: UserId = Field(default_factory=factory(UserId))
    name: str

user = User(name="Alice")
user.model_dump()  # {"id": "usr_0M3xL9kQ7vR2nP5wY1jZ4c", "name": "Alice"}

User(id="org_xxx...", name="Bad")  # ValidationError: wrong prefix

FastAPI Integration

from typing import Annotated, Literal
from fastapi import Depends, FastAPI, HTTPException
from uplid import UPLID, UPLIDError, parse

UserId = UPLID[Literal["usr"]]
parse_user_id = parse(UserId)

def validate_user_id(user_id: str) -> UserId:
    try:
        return parse_user_id(user_id)
    except UPLIDError as e:
        raise HTTPException(422, str(e)) from None

@app.get("/users/{user_id}")
def get_user(user_id: Annotated[UserId, Depends(validate_user_id)]) -> User:
    ...  # user_id is validated and typed

SQLAlchemy Integration

from uplid import UPLID, factory
from uplid.sqlalchemy import uplid_column

UserId = UPLID[Literal["usr"]]

class User(Base):
    __tablename__ = "users"
    id: Mapped[UserId] = uplid_column(UserId, primary_key=True)
    name: Mapped[str]

# Stores as TEXT, returns as UPLID objects
user = session.execute(select(User)).scalar_one()
user.id.prefix    # "usr"
user.id.datetime  # When the ID was created

SQLModel Integration

from uplid import UPLID, factory
from uplid.sqlalchemy import uplid_field

UserId = UPLID[Literal["usr"]]

class User(SQLModel, table=True):
    id: UserId = uplid_field(UserId, default_factory=factory(UserId), primary_key=True)
    name: str

user.model_dump()  # {"id": "usr_...", "name": "Alice"} - Pydantic just works

Prefix Rules

Prefixes must be letters and single underscores, cannot start/end with underscore, max 64 characters.

Examples: usr, api_key, org_member, sk_live, USR, ApiKey

API Reference

UPLID[PREFIX]

uid = UPLID.generate("usr")                              # Generate new
uid = UPLID.from_string("usr_0M3xL9kQ7vR2nP5wY1jZ4c", "usr")  # Parse

uid.prefix      # "usr"
uid.uid         # UUID object
uid.base62_uid  # "0M3xL9kQ7vR2nP5wY1jZ4c"
uid.datetime    # When created (from UUIDv7)
uid.timestamp   # Unix timestamp

factory(UPLIDType) / parse(UPLIDType)

UserId = UPLID[Literal["usr"]]
UserIdFactory = factory(UserId)  # For Pydantic default_factory
parse_user_id = parse(UserId)    # For manual parsing, raises UPLIDError

UPLIDType

Protocol for functions accepting any UPLID:

def log_entity(id: UPLIDType) -> None:
    print(f"{id.prefix} created at {id.datetime}")

uplid_column / uplid_field

from uplid.sqlalchemy import uplid_column, uplid_field

# SQLAlchemy
id: Mapped[UserId] = uplid_column(UserId, primary_key=True)

# SQLModel
id: UserId = uplid_field(UserId, default_factory=factory(UserId), primary_key=True)

Credits

Created by ZVS

License

MIT

Metadata

Release files for uplid 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for uplid 1.2.0
File Size Uploaded
uplid-1.2.0.tar.gz 9.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for uplid 1.2.0
File Interpreter ABI Platform
uplid-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 19.6 kB

Release files / uplid-1.2.0.tar.gz

Download URL uplid-1.2.0.tar.gz
Size 9.3 kB
Tags Source
SHA-256 checksum
How to use checksums
e5cb34b661f10fbe393a8a8873ac28b4028c3a49b8a8ad8d119c6f8eeb035e74
BLAKE2b-256 checksum
How to use checksums
e89b5eaaee81da85625150e2379469139002e97b7c2f54dcd01b6b7522c8680a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / uplid-1.2.0-py3-none-any.whl

Download URL uplid-1.2.0-py3-none-any.whl
Size 10.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2df980717a7a3a83a2f80b659df50dd9bce9374fe2850432b0c4b27deab9ab32
BLAKE2b-256 checksum
How to use checksums
cb52ecda7a88cc06a71c57f241ac2cbdebf20c2abf0ab87e01907e426c83c9d7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.0.4

2 release files

0.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page