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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

uplid-1.2.0.tar.gz (9.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

uplid-1.2.0-py3-none-any.whl (10.4 kB view details)

Uploaded Python 3

File details

Details for the file uplid-1.2.0.tar.gz.

File metadata

  • Download URL: uplid-1.2.0.tar.gz
  • Upload date:
  • Size: 9.3 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for uplid-1.2.0.tar.gz
Algorithm Hash digest
SHA256 e5cb34b661f10fbe393a8a8873ac28b4028c3a49b8a8ad8d119c6f8eeb035e74
MD5 98f7863507a9f09ce5302f7297d69bea
BLAKE2b-256 e89b5eaaee81da85625150e2379469139002e97b7c2f54dcd01b6b7522c8680a

See more details on using hashes here.

File details

Details for the file uplid-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: uplid-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 10.4 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for uplid-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2df980717a7a3a83a2f80b659df50dd9bce9374fe2850432b0c4b27deab9ab32
MD5 293f4d091300a4af9c55d69942477acc
BLAKE2b-256 cb52ecda7a88cc06a71c57f241ac2cbdebf20c2abf0ab87e01907e426c83c9d7

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 files

1.1.4

2 files

1.1.3

2 files

1.1.2

2 files

1.1.1

2 files

1.1.0

2 files

1.0.1

2 files

1.0.0

2 files

0.0.4

2 files

0.0.2

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page