New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

typeid-python

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

typeid-python - pypi Package Compare versions

Comparing version
0.3.4
to
0.3.5
+62
typeid/_uuid_backend.py
import os
import uuid as std_uuid
from dataclasses import dataclass
from typing import Callable, Literal, Type
BackendName = Literal["uuid-utils", "uuid6"]
@dataclass(frozen=True)
class UUIDBackend:
name: BackendName
uuid7: Callable[[], std_uuid.UUID]
UUID: Type[std_uuid.UUID]
def _load_uuid_utils() -> UUIDBackend:
import uuid_utils as uuid # type: ignore
return UUIDBackend(name="uuid-utils", uuid7=uuid.uuid7, UUID=uuid.UUID) # type: ignore
def _load_uuid6() -> UUIDBackend:
import uuid6 # type: ignore
return UUIDBackend(name="uuid6", uuid7=uuid6.uuid7, UUID=uuid6.UUID) # type: ignore
def get_uuid_backend() -> UUIDBackend:
"""
Select UUIDv7 backend.
Selection order:
1) If TYPEID_UUID_BACKEND is set, force that backend (or fail with a clear error).
2) Otherwise prefer uuid-utils if installed, else fallback to uuid6.
Allowed values:
TYPEID_UUID_BACKEND=uuid-utils|uuid6
"""
forced = os.getenv("TYPEID_UUID_BACKEND")
if forced:
forced = forced.strip()
if forced not in ("uuid-utils", "uuid6"):
raise RuntimeError(f"Invalid TYPEID_UUID_BACKEND={forced!r}. " "Allowed values: 'uuid-utils' or 'uuid6'.")
try:
return _load_uuid_utils() if forced == "uuid-utils" else _load_uuid6()
except Exception as e:
raise RuntimeError(
f"TYPEID_UUID_BACKEND is set to {forced!r}, but that backend "
"is not available. Install the required dependency."
) from e
# Auto mode
try:
return _load_uuid_utils()
except Exception:
pass
try:
return _load_uuid6()
except Exception as e:
raise RuntimeError("No UUIDv7 backend available. Install one of: uuid-utils (recommended) or uuid6.") from e
+123
-218
Metadata-Version: 2.4
Name: typeid-python
Version: 0.3.4
Version: 0.3.5
Summary: Python implementation of TypeIDs: type-safe, K-sortable, and globally unique identifiers inspired by Stripe IDs

@@ -24,2 +24,4 @@ Project-URL: Homepage, https://github.com/akhundMurad/typeid-python

Requires-Dist: click; extra == 'cli'
Provides-Extra: rust
Requires-Dist: uuid-utils>=0.12.0; extra == 'rust'
Provides-Extra: yaml

@@ -31,174 +33,161 @@ Requires-Dist: pyyaml; extra == 'yaml'

<a href="https://github.com/akhundMurad/typeid-python/actions?query=setup%3ACI%2FCD+event%3Apush+branch%3Amain" target="_blank">
<img src="https://github.com/akhundMurad/typeid-python/actions/workflows/setup.yml/badge.svg?event=push&branch=main" alt="Test">
</a>
<a href="https://pepy.tech/project/typeid-python" target="_blank">
<img src="https://static.pepy.tech/personalized-badge/typeid-python?period=total&units=international_system&left_color=black&right_color=red&left_text=downloads" alt="Downloads">
</a>
<a href="https://pypi.org/project/typeid-python" target="_blank">
<img src="https://img.shields.io/pypi/v/typeid-python?color=red&labelColor=black" alt="Package version">
</a>
<a href="https://pypi.org/project/typeid-python" target="_blank">
<img src="https://img.shields.io/pypi/pyversions/typeid-python.svg?color=red&labelColor=black" alt="Supported Python versions">
</a>
[![Run Tests](https://github.com/akhundMurad/typeid-python/actions/workflows/test.yml/badge.svg)](https://github.com/akhundMurad/typeid-python/actions/workflows/test.yml)
[![PyPI Downloads](https://static.pepy.tech/personalized-badge/typeid-python?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/typeid-python)
[![PyPI - Version](https://img.shields.io/pypi/v/typeid-python?color=green)](https://pypi.org/project/typeid-python/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/typeid-python?color=green)](https://pypi.org/project/typeid-python/)
## A Python implementation of [TypeIDs](https://github.com/jetpack-io/typeid) using Python
TypeIDs are a modern, **type-safe**, globally unique identifier based on the upcoming
UUIDv7 standard. They provide a ton of nice properties that make them a great choice
as the primary identifiers for your data in a database, APIs, and distributed systems.
Read more about TypeIDs in their [spec](https://github.com/jetpack-io/typeid).
A **high-performance Python implementation of [TypeIDs](https://github.com/jetpack-io/typeid)** — type-safe,
sortable identifiers based on **UUIDv7**.
This particular implementation provides an pip package that can be used by any Python project.
TypeIDs are designed for modern systems where identifiers should be:
## Installation
- globally unique
- sortable by creation time
- safe to expose externally
- easy to reason about in logs, APIs, and databases
- Pip:
This library provides a Python package with optional Rust acceleration.
```console
pip install typeid-python
```
## Key features
- Uv:
- ✅ UUIDv7-based, time-sortable identifiers
- ✅ Type-safe prefixes (`user_`, `order_`, …)
- ✅ Human-readable and URL-safe
- ✅ Fast generation & parsing (Rust-accelerated)
- ✅ CLI tools (`new`, `encode`, `decode`, `explain`)
- ✅ Schema-based ID explanations (JSON / YAML)
- ✅ Fully offline, no external services
```console
uv add typeid-python
```
## Performance
- Poetry:
TypeID is optimized for **real-world performance**, not just correctness.
```console
poetry add typeid-python
```
### Benchmark summary (mean time)
### Optional dependencies
| Operation | Before Rust | Rust + optimizations |
| --------- | ----------- | -------------------- |
| Generate | 3.47 µs | **0.70 µs** |
| Parse | 2.08 µs | **1.30 µs** |
| Workflow | 5.52 µs | **2.25 µs** |
TypeID supports schema-based ID explanations using JSON (always available) and
YAML (optional).
### Highlights
To enable YAML support:
* 🚀 **~5× faster generation**
* ⚡ **~1.6× faster parsing**
* 🔁 **~2.5× faster end-to-end workflows**
```console
pip install typeid-python[yaml]
```
Benchmarks are:
If the extra is not installed, JSON schemas will still work.
* reproducible
* committed as raw JSON
* runnable locally via `bench/`
## Usage
See [`Docs: Performance`](https://akhundmurad.github.io/typeid-python/performance/) for details.
### Basic
## Installation
- Create TypeID Instance:
### Core (pure Python)
```python
from typeid import TypeID
```console
$ pip install typeid-python
```
# Default TypeID (no prefix)
typeid = TypeID()
### With Rust acceleration (recommended)
assert typeid.prefix == ""
assert isinstance(typeid.suffix, str)
assert len(typeid.suffix) > 0 # encoded UUIDv7
```console
$ pip install typeid-python[rust]
```
# TypeID with prefix
typeid = TypeID(prefix="user")
This enables:
assert typeid.prefix == "user"
assert str(typeid).startswith("user_")
```
* Rust base32 encode/decode
* `uuid-utils` for fast UUIDv7 generation
- Create TypeID from string:
If Rust is unavailable, TypeID automatically falls back to the pure-Python implementation.
```python
from typeid import TypeID
### Other optional extras
value = "user_01h45ytscbebyvny4gc8cr8ma2"
typeid = TypeID.from_string(value)
```console
$ pip install typeid-python[yaml] # YAML schema support
$ pip install typeid-python[cli] # CLI tools
```
assert str(typeid) == value
assert typeid.prefix == "user"
```
Extras are **strictly optional**.
- Create TypeID from uuid7:
## Usage
```python
from typeid import TypeID
from uuid6 import uuid7
### Basic
uuid = uuid7()
prefix = "user"
```python
from typeid import TypeID
typeid = TypeID.from_uuid(prefix=prefix, suffix=uuid)
tid = TypeID(prefix="user")
assert typeid.prefix == prefix
assert str(typeid).startswith(f"{prefix}_")
assert tid.prefix == "user"
assert isinstance(tid.suffix, str)
assert str(tid).startswith("user_")
```
```
### From string
- Use pre-defined prefix:
```python
from typeid import TypeID
```python
from dataclasses import dataclass, field
from typing import Literal
from typeid import TypeID, typeid_factory
tid = TypeID.from_string("user_01h45ytscbebyvny4gc8cr8ma2")
assert tid.prefix == "user"
```
UserID = TypeID[Literal["user"]]
gen_user_id = typeid_factory("user")
### From UUIDv7
```python
from typeid import TypeID
from uuid6 import uuid7
@dataclass
class UserDTO:
user_id: UserID = field(default_factory=gen_user_id)
full_name: str = "A J"
age: int = 18
u = uuid7()
tid = TypeID.from_uuid(prefix="user", suffix=u)
assert tid.uuid.version == 7
```
user = UserDTO()
### Typed prefixes
assert str(user.user_id).startswith("user_")
```
```python
from typing import Literal
from typeid import TypeID, typeid_factory
### CLI-tool
UserID = TypeID[Literal["user"]]
gen_user_id = typeid_factory("user")
- Install dependencies:
user_id = gen_user_id()
```
```console
pip install typeid-python[cli]
```
## CLI
- To generate a new TypeID, run:
```console
$ pip install typeid-python[cli]
```
```console
$ typeid new -p prefix
prefix_01h2xcejqtf2nbrexx3vqjhp41
```
Generate:
- To decode an existing TypeID into a UUID run:
```console
$ typeid new -p user
user_01h2xcejqtf2nbrexx3vqjhp41
```
```console
$ typeid decode prefix_01h2xcejqtf2nbrexx3vqjhp41
type: prefix
uuid: 0188bac7-4afa-78aa-bc3b-bd1eef28d881
```
Decode:
- And to encode an existing UUID into a TypeID run:
```console
$ typeid decode user_01h2xcejqtf2nbrexx3vqjhp41
uuid: 0188bac7-4afa-78aa-bc3b-bd1eef28d881
```
```console
$ typeid encode 0188bac7-4afa-78aa-bc3b-bd1eef28d881 --prefix prefix
prefix_01h2xcejqtf2nbrexx3vqjhp41
```
Encode:
## ✨ NEW: `typeid explain` — “What is this ID?”
```console
$ typeid encode 0188bac7-4afa-78aa-bc3b-bd1eef28d881 --prefix user
```
TypeID can now **explain a TypeID** in a human-readable way.
## ✨ `typeid explain` — understand any ID
This is useful when:
* debugging logs
* inspecting database records
* reviewing production incidents
* understanding IDs shared via Slack, tickets, or dashboards
### Basic usage (no schema required)
```console

@@ -208,32 +197,19 @@ $ typeid explain user_01h45ytscbebyvny4gc8cr8ma2

Example output:
Outputs:
```yaml
id: user_01h45ytscbebyvny4gc8cr8ma2
valid: true
parsed:
prefix: user
suffix: 01h45ytscbebyvny4gc8cr8ma2
uuid: 01890bf0-846f-7762-8605-5a3abb40e0e5
created_at: 2025-03-12T10:41:23Z
sortable: true
schema:
found: false
```
Even without configuration, `typeid explain` can:
Works **without schema**, fully offline.
* validate the ID
* extract the UUID
* derive creation time (UUIDv7)
* determine sortability
## Schema-based explanations
To make explanations richer, you can define a **TypeID schema** describing what each
prefix represents.
Define meaning for prefixes using JSON or YAML.
### Example schema (`typeid.schema.json`)
Example (`typeid.schema.json`):

@@ -246,10 +222,4 @@ ```json

"name": "User",
"description": "End-user account",
"owner_team": "identity-platform",
"pii": true,
"retention": "7y",
"links": {
"logs": "https://logs.company/search?q={id}",
"trace": "https://traces.company/?id={id}"
}
"pii": true
}

@@ -260,3 +230,3 @@ }

### Explain using schema
Then:

@@ -267,82 +237,17 @@ ```console

Output (excerpt):
Read more here: ["Docs: Explain"](https://akhundmurad.github.io/typeid-python/performance/).
```yaml
schema:
found: true
name: User
owner_team: identity-platform
pii: true
retention: 7y
links:
logs: https://logs.company/search?q=user_01h45ytscbebyvny4gc8cr8ma2
```
## Schema discovery rules
If `--schema` is not provided, TypeID looks for a schema in the following order:
1. Environment variable:
```console
TYPEID_SCHEMA=/path/to/schema.json
```
2. Current directory:
* `typeid.schema.json`
* `typeid.schema.yaml`
3. User config directory:
* `~/.config/typeid/schema.json`
* `~/.config/typeid/schema.yaml`
If no schema is found, the command still works with derived information only.
## YAML schemas (optional)
YAML schemas are supported if the optional dependency is installed:
```console
pip install typeid-python[yaml]
```
Example (`typeid.schema.yaml`):
```yaml
schema_version: 1
types:
user:
name: User
owner_team: identity-platform
links:
logs: "https://logs.company/search?q={id}"
```
## JSON output (machine-readable)
```console
$ typeid explain user_01h45ytscbebyvny4gc8cr8ma2 --json
```
Useful for:
* scripts
* CI pipelines
* IDE integrations
## Design principles
* **Non-breaking**: existing APIs and CLI commands remain unchanged
* **Schema-optional**: works fully offline
* **Read-only**: no side effects or external mutations
* **Declarative**: meaning is defined by users, not inferred by the tool
* **Non-breaking**: stable APIs
* **Optional acceleration**: Rust is opt-in
* **Lazy evaluation**: work is done only when needed
* **Explainability**: identifiers carry meaning
* **Transparency**: performance claims are backed by data
You can think of `typeid explain` as:
> Think of TypeID as
> **UUIDs + semantics + observability — without sacrificing speed**
> **OpenAPI — but for identifiers instead of HTTP endpoints**
## License
MIT
[project]
name = "typeid-python"
version = "0.3.4"
version = "0.3.5"
description = "Python implementation of TypeIDs: type-safe, K-sortable, and globally unique identifiers inspired by Stripe IDs"

@@ -30,2 +30,3 @@ authors = [{ name = "Murad Akhundov", email = "akhundov1murad@gmail.com" }]

yaml = ["PyYAML"]
rust = ["uuid-utils>=0.12.0"]

@@ -56,2 +57,4 @@ [project.urls]

"pytest-markdown-docs>=0.9.0",
"pytest-benchmark>=5.0.1",
"maturin>=1.5; platform_system != 'Windows'"
]

@@ -58,0 +61,0 @@

+120
-217
# TypeID Python
<a href="https://github.com/akhundMurad/typeid-python/actions?query=setup%3ACI%2FCD+event%3Apush+branch%3Amain" target="_blank">
<img src="https://github.com/akhundMurad/typeid-python/actions/workflows/setup.yml/badge.svg?event=push&branch=main" alt="Test">
</a>
<a href="https://pepy.tech/project/typeid-python" target="_blank">
<img src="https://static.pepy.tech/personalized-badge/typeid-python?period=total&units=international_system&left_color=black&right_color=red&left_text=downloads" alt="Downloads">
</a>
<a href="https://pypi.org/project/typeid-python" target="_blank">
<img src="https://img.shields.io/pypi/v/typeid-python?color=red&labelColor=black" alt="Package version">
</a>
<a href="https://pypi.org/project/typeid-python" target="_blank">
<img src="https://img.shields.io/pypi/pyversions/typeid-python.svg?color=red&labelColor=black" alt="Supported Python versions">
</a>
[![Run Tests](https://github.com/akhundMurad/typeid-python/actions/workflows/test.yml/badge.svg)](https://github.com/akhundMurad/typeid-python/actions/workflows/test.yml)
[![PyPI Downloads](https://static.pepy.tech/personalized-badge/typeid-python?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/typeid-python)
[![PyPI - Version](https://img.shields.io/pypi/v/typeid-python?color=green)](https://pypi.org/project/typeid-python/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/typeid-python?color=green)](https://pypi.org/project/typeid-python/)
## A Python implementation of [TypeIDs](https://github.com/jetpack-io/typeid) using Python
TypeIDs are a modern, **type-safe**, globally unique identifier based on the upcoming
UUIDv7 standard. They provide a ton of nice properties that make them a great choice
as the primary identifiers for your data in a database, APIs, and distributed systems.
Read more about TypeIDs in their [spec](https://github.com/jetpack-io/typeid).
A **high-performance Python implementation of [TypeIDs](https://github.com/jetpack-io/typeid)** — type-safe,
sortable identifiers based on **UUIDv7**.
This particular implementation provides an pip package that can be used by any Python project.
TypeIDs are designed for modern systems where identifiers should be:
## Installation
- globally unique
- sortable by creation time
- safe to expose externally
- easy to reason about in logs, APIs, and databases
- Pip:
This library provides a Python package with optional Rust acceleration.
```console
pip install typeid-python
```
## Key features
- Uv:
- ✅ UUIDv7-based, time-sortable identifiers
- ✅ Type-safe prefixes (`user_`, `order_`, …)
- ✅ Human-readable and URL-safe
- ✅ Fast generation & parsing (Rust-accelerated)
- ✅ CLI tools (`new`, `encode`, `decode`, `explain`)
- ✅ Schema-based ID explanations (JSON / YAML)
- ✅ Fully offline, no external services
```console
uv add typeid-python
```
## Performance
- Poetry:
TypeID is optimized for **real-world performance**, not just correctness.
```console
poetry add typeid-python
```
### Benchmark summary (mean time)
### Optional dependencies
| Operation | Before Rust | Rust + optimizations |
| --------- | ----------- | -------------------- |
| Generate | 3.47 µs | **0.70 µs** |
| Parse | 2.08 µs | **1.30 µs** |
| Workflow | 5.52 µs | **2.25 µs** |
TypeID supports schema-based ID explanations using JSON (always available) and
YAML (optional).
### Highlights
To enable YAML support:
* 🚀 **~5× faster generation**
* ⚡ **~1.6× faster parsing**
* 🔁 **~2.5× faster end-to-end workflows**
```console
pip install typeid-python[yaml]
```
Benchmarks are:
If the extra is not installed, JSON schemas will still work.
* reproducible
* committed as raw JSON
* runnable locally via `bench/`
## Usage
See [`Docs: Performance`](https://akhundmurad.github.io/typeid-python/performance/) for details.
### Basic
## Installation
- Create TypeID Instance:
### Core (pure Python)
```python
from typeid import TypeID
```console
$ pip install typeid-python
```
# Default TypeID (no prefix)
typeid = TypeID()
### With Rust acceleration (recommended)
assert typeid.prefix == ""
assert isinstance(typeid.suffix, str)
assert len(typeid.suffix) > 0 # encoded UUIDv7
```console
$ pip install typeid-python[rust]
```
# TypeID with prefix
typeid = TypeID(prefix="user")
This enables:
assert typeid.prefix == "user"
assert str(typeid).startswith("user_")
```
* Rust base32 encode/decode
* `uuid-utils` for fast UUIDv7 generation
- Create TypeID from string:
If Rust is unavailable, TypeID automatically falls back to the pure-Python implementation.
```python
from typeid import TypeID
### Other optional extras
value = "user_01h45ytscbebyvny4gc8cr8ma2"
typeid = TypeID.from_string(value)
```console
$ pip install typeid-python[yaml] # YAML schema support
$ pip install typeid-python[cli] # CLI tools
```
assert str(typeid) == value
assert typeid.prefix == "user"
```
Extras are **strictly optional**.
- Create TypeID from uuid7:
## Usage
```python
from typeid import TypeID
from uuid6 import uuid7
### Basic
uuid = uuid7()
prefix = "user"
```python
from typeid import TypeID
typeid = TypeID.from_uuid(prefix=prefix, suffix=uuid)
tid = TypeID(prefix="user")
assert typeid.prefix == prefix
assert str(typeid).startswith(f"{prefix}_")
assert tid.prefix == "user"
assert isinstance(tid.suffix, str)
assert str(tid).startswith("user_")
```
```
### From string
- Use pre-defined prefix:
```python
from typeid import TypeID
```python
from dataclasses import dataclass, field
from typing import Literal
from typeid import TypeID, typeid_factory
tid = TypeID.from_string("user_01h45ytscbebyvny4gc8cr8ma2")
assert tid.prefix == "user"
```
UserID = TypeID[Literal["user"]]
gen_user_id = typeid_factory("user")
### From UUIDv7
```python
from typeid import TypeID
from uuid6 import uuid7
@dataclass
class UserDTO:
user_id: UserID = field(default_factory=gen_user_id)
full_name: str = "A J"
age: int = 18
u = uuid7()
tid = TypeID.from_uuid(prefix="user", suffix=u)
assert tid.uuid.version == 7
```
user = UserDTO()
### Typed prefixes
assert str(user.user_id).startswith("user_")
```
```python
from typing import Literal
from typeid import TypeID, typeid_factory
### CLI-tool
UserID = TypeID[Literal["user"]]
gen_user_id = typeid_factory("user")
- Install dependencies:
user_id = gen_user_id()
```
```console
pip install typeid-python[cli]
```
## CLI
- To generate a new TypeID, run:
```console
$ pip install typeid-python[cli]
```
```console
$ typeid new -p prefix
prefix_01h2xcejqtf2nbrexx3vqjhp41
```
Generate:
- To decode an existing TypeID into a UUID run:
```console
$ typeid new -p user
user_01h2xcejqtf2nbrexx3vqjhp41
```
```console
$ typeid decode prefix_01h2xcejqtf2nbrexx3vqjhp41
type: prefix
uuid: 0188bac7-4afa-78aa-bc3b-bd1eef28d881
```
Decode:
- And to encode an existing UUID into a TypeID run:
```console
$ typeid decode user_01h2xcejqtf2nbrexx3vqjhp41
uuid: 0188bac7-4afa-78aa-bc3b-bd1eef28d881
```
```console
$ typeid encode 0188bac7-4afa-78aa-bc3b-bd1eef28d881 --prefix prefix
prefix_01h2xcejqtf2nbrexx3vqjhp41
```
Encode:
## ✨ NEW: `typeid explain` — “What is this ID?”
```console
$ typeid encode 0188bac7-4afa-78aa-bc3b-bd1eef28d881 --prefix user
```
TypeID can now **explain a TypeID** in a human-readable way.
## ✨ `typeid explain` — understand any ID
This is useful when:
* debugging logs
* inspecting database records
* reviewing production incidents
* understanding IDs shared via Slack, tickets, or dashboards
### Basic usage (no schema required)
```console

@@ -179,32 +166,19 @@ $ typeid explain user_01h45ytscbebyvny4gc8cr8ma2

Example output:
Outputs:
```yaml
id: user_01h45ytscbebyvny4gc8cr8ma2
valid: true
parsed:
prefix: user
suffix: 01h45ytscbebyvny4gc8cr8ma2
uuid: 01890bf0-846f-7762-8605-5a3abb40e0e5
created_at: 2025-03-12T10:41:23Z
sortable: true
schema:
found: false
```
Even without configuration, `typeid explain` can:
Works **without schema**, fully offline.
* validate the ID
* extract the UUID
* derive creation time (UUIDv7)
* determine sortability
## Schema-based explanations
To make explanations richer, you can define a **TypeID schema** describing what each
prefix represents.
Define meaning for prefixes using JSON or YAML.
### Example schema (`typeid.schema.json`)
Example (`typeid.schema.json`):

@@ -217,10 +191,4 @@ ```json

"name": "User",
"description": "End-user account",
"owner_team": "identity-platform",
"pii": true,
"retention": "7y",
"links": {
"logs": "https://logs.company/search?q={id}",
"trace": "https://traces.company/?id={id}"
}
"pii": true
}

@@ -231,3 +199,3 @@ }

### Explain using schema
Then:

@@ -238,82 +206,17 @@ ```console

Output (excerpt):
Read more here: ["Docs: Explain"](https://akhundmurad.github.io/typeid-python/performance/).
```yaml
schema:
found: true
name: User
owner_team: identity-platform
pii: true
retention: 7y
links:
logs: https://logs.company/search?q=user_01h45ytscbebyvny4gc8cr8ma2
```
## Schema discovery rules
If `--schema` is not provided, TypeID looks for a schema in the following order:
1. Environment variable:
```console
TYPEID_SCHEMA=/path/to/schema.json
```
2. Current directory:
* `typeid.schema.json`
* `typeid.schema.yaml`
3. User config directory:
* `~/.config/typeid/schema.json`
* `~/.config/typeid/schema.yaml`
If no schema is found, the command still works with derived information only.
## YAML schemas (optional)
YAML schemas are supported if the optional dependency is installed:
```console
pip install typeid-python[yaml]
```
Example (`typeid.schema.yaml`):
```yaml
schema_version: 1
types:
user:
name: User
owner_team: identity-platform
links:
logs: "https://logs.company/search?q={id}"
```
## JSON output (machine-readable)
```console
$ typeid explain user_01h45ytscbebyvny4gc8cr8ma2 --json
```
Useful for:
* scripts
* CI pipelines
* IDE integrations
## Design principles
* **Non-breaking**: existing APIs and CLI commands remain unchanged
* **Schema-optional**: works fully offline
* **Read-only**: no side effects or external mutations
* **Declarative**: meaning is defined by users, not inferred by the tool
* **Non-breaking**: stable APIs
* **Optional acceleration**: Rust is opt-in
* **Lazy evaluation**: work is done only when needed
* **Explainability**: identifiers carry meaning
* **Transparency**: performance claims are backed by data
You can think of `typeid explain` as:
> Think of TypeID as
> **UUIDs + semantics + observability — without sacrificing speed**
> **OpenAPI — but for identifiers instead of HTTP endpoints**
## License
MIT

@@ -1,3 +0,11 @@

from typing import List
try:
from typeid_base32 import encode as _encode_rust, decode as _decode_rust # type: ignore
_HAS_RUST = True
except Exception:
_HAS_RUST = False
_encode_rust = None
_decode_rust = None
from typing import Union
from typeid.constants import SUFFIX_LEN

@@ -7,3 +15,3 @@

# TABLE maps ASCII byte -> 0..31 or 0xFF if invalid
TABLE = [

@@ -138,227 +146,94 @@ 0xFF,

0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
]
] + [0xFF] * (256 - 128)
def encode(src: List[int]) -> str:
dst = [""] * SUFFIX_LEN
BytesLike = Union[bytes, bytearray, memoryview]
if len(src) != 16:
def _encode_py(src: BytesLike) -> str:
mv = memoryview(src)
if mv.nbytes != 16:
raise RuntimeError("Invalid length.")
# 10 byte timestamp
dst[0] = ALPHABET[(src[0] & 224) >> 5]
dst[1] = ALPHABET[src[0] & 31]
dst[2] = ALPHABET[(src[1] & 248) >> 3]
dst[3] = ALPHABET[((src[1] & 7) << 2) | ((src[2] & 192) >> 6)]
dst[4] = ALPHABET[(src[2] & 62) >> 1]
dst[5] = ALPHABET[((src[2] & 1) << 4) | ((src[3] & 240) >> 4)]
dst[6] = ALPHABET[((src[3] & 15) << 1) | ((src[4] & 128) >> 7)]
dst[7] = ALPHABET[(src[4] & 124) >> 2]
dst[8] = ALPHABET[((src[4] & 3) << 3) | ((src[5] & 224) >> 5)]
dst[9] = ALPHABET[src[5] & 31]
# Pre-allocate output chars
dst = [""] * SUFFIX_LEN
# 16 bytes of randomness
dst[10] = ALPHABET[(src[6] & 248) >> 3]
dst[11] = ALPHABET[((src[6] & 7) << 2) | ((src[7] & 192) >> 6)]
dst[12] = ALPHABET[(src[7] & 62) >> 1]
dst[13] = ALPHABET[((src[7] & 1) << 4) | ((src[8] & 240) >> 4)]
dst[14] = ALPHABET[((src[8] & 15) << 1) | ((src[9] & 128) >> 7)]
dst[15] = ALPHABET[(src[9] & 124) >> 2]
dst[16] = ALPHABET[((src[9] & 3) << 3) | ((src[10] & 224) >> 5)]
dst[17] = ALPHABET[src[10] & 31]
dst[18] = ALPHABET[(src[11] & 248) >> 3]
dst[19] = ALPHABET[((src[11] & 7) << 2) | ((src[12] & 192) >> 6)]
dst[20] = ALPHABET[(src[12] & 62) >> 1]
dst[21] = ALPHABET[((src[12] & 1) << 4) | ((src[13] & 240) >> 4)]
dst[22] = ALPHABET[((src[13] & 15) << 1) | ((src[14] & 128) >> 7)]
dst[23] = ALPHABET[(src[14] & 124) >> 2]
dst[24] = ALPHABET[((src[14] & 3) << 3) | ((src[15] & 224) >> 5)]
dst[25] = ALPHABET[src[15] & 31]
# Timestamp (6 bytes => 10 chars)
dst[0] = ALPHABET[(mv[0] & 0b11100000) >> 5]
dst[1] = ALPHABET[mv[0] & 0b00011111]
dst[2] = ALPHABET[(mv[1] & 0b11111000) >> 3]
dst[3] = ALPHABET[((mv[1] & 0b00000111) << 2) | ((mv[2] & 0b11000000) >> 6)]
dst[4] = ALPHABET[(mv[2] & 0b00111110) >> 1]
dst[5] = ALPHABET[((mv[2] & 0b00000001) << 4) | ((mv[3] & 0b11110000) >> 4)]
dst[6] = ALPHABET[((mv[3] & 0b00001111) << 1) | ((mv[4] & 0b10000000) >> 7)]
dst[7] = ALPHABET[(mv[4] & 0b01111100) >> 2]
dst[8] = ALPHABET[((mv[4] & 0b00000011) << 3) | ((mv[5] & 0b11100000) >> 5)]
dst[9] = ALPHABET[mv[5] & 0b00011111]
# Entropy (10 bytes => 16 chars)
dst[10] = ALPHABET[(mv[6] & 0b11111000) >> 3]
dst[11] = ALPHABET[((mv[6] & 0b00000111) << 2) | ((mv[7] & 0b11000000) >> 6)]
dst[12] = ALPHABET[(mv[7] & 0b00111110) >> 1]
dst[13] = ALPHABET[((mv[7] & 0b00000001) << 4) | ((mv[8] & 0b11110000) >> 4)]
dst[14] = ALPHABET[((mv[8] & 0b00001111) << 1) | ((mv[9] & 0b10000000) >> 7)]
dst[15] = ALPHABET[(mv[9] & 0b01111100) >> 2]
dst[16] = ALPHABET[((mv[9] & 0b00000011) << 3) | ((mv[10] & 0b11100000) >> 5)]
dst[17] = ALPHABET[mv[10] & 0b00011111]
dst[18] = ALPHABET[(mv[11] & 0b11111000) >> 3]
dst[19] = ALPHABET[((mv[11] & 0b00000111) << 2) | ((mv[12] & 0b11000000) >> 6)]
dst[20] = ALPHABET[(mv[12] & 0b00111110) >> 1]
dst[21] = ALPHABET[((mv[12] & 0b00000001) << 4) | ((mv[13] & 0b11110000) >> 4)]
dst[22] = ALPHABET[((mv[13] & 0b00001111) << 1) | ((mv[14] & 0b10000000) >> 7)]
dst[23] = ALPHABET[(mv[14] & 0b01111100) >> 2]
dst[24] = ALPHABET[((mv[14] & 0b00000011) << 3) | ((mv[15] & 0b11100000) >> 5)]
dst[25] = ALPHABET[mv[15] & 0b00011111]
return "".join(dst)
def decode(s: str) -> List[int]:
v = bytes(s, encoding="utf-8")
def _decode_py(s: str) -> bytes:
if len(s) != SUFFIX_LEN:
raise RuntimeError("Invalid length.")
if (
TABLE[v[0]] == 0xFF
and TABLE[v[1]] == 0xFF
and TABLE[v[2]] == 0xFF
and TABLE[v[3]] == 0xFF
and TABLE[v[4]] == 0xFF
and TABLE[v[5]] == 0xFF
and TABLE[v[6]] == 0xFF
and TABLE[v[7]] == 0xFF
and TABLE[v[8]] == 0xFF
and TABLE[v[9]] == 0xFF
and TABLE[v[10]] == 0xFF
and TABLE[v[11]] == 0xFF
and TABLE[v[12]] == 0xFF
and TABLE[v[13]] == 0xFF
and TABLE[v[14]] == 0xFF
and TABLE[v[15]] == 0xFF
and TABLE[v[16]] == 0xFF
and TABLE[v[17]] == 0xFF
and TABLE[v[18]] == 0xFF
and TABLE[v[19]] == 0xFF
and TABLE[v[20]] == 0xFF
and TABLE[v[21]] == 0xFF
and TABLE[v[22]] == 0xFF
and TABLE[v[23]] == 0xFF
and TABLE[v[24]] == 0xFF
and TABLE[v[25]] == 0xFF
):
raise RuntimeError("Invalid base32 character")
v = s.encode("utf-8")
tbl = TABLE
typeid = [0] * 16
for b in v:
if tbl[b] == 0xFF:
raise RuntimeError("Invalid base32 character")
out = bytearray(16)
# 6 bytes timestamp (48 bits)
typeid[0] = (TABLE[v[0]] << 5) | TABLE[v[1]]
typeid[1] = (TABLE[v[2]] << 3) | (TABLE[v[3]] >> 2)
typeid[2] = ((TABLE[v[3]] & 3) << 6) | (TABLE[v[4]] << 1) | (TABLE[v[5]] >> 4)
typeid[3] = ((TABLE[v[5]] & 15) << 4) | (TABLE[v[6]] >> 1)
typeid[4] = ((TABLE[v[6]] & 1) << 7) | (TABLE[v[7]] << 2) | (TABLE[v[8]] >> 3)
typeid[5] = ((TABLE[v[8]] & 7) << 5) | TABLE[v[9]]
out[0] = (tbl[v[0]] << 5) | tbl[v[1]]
out[1] = (tbl[v[2]] << 3) | (tbl[v[3]] >> 2)
out[2] = ((tbl[v[3]] & 3) << 6) | (tbl[v[4]] << 1) | (tbl[v[5]] >> 4)
out[3] = ((tbl[v[5]] & 15) << 4) | (tbl[v[6]] >> 1)
out[4] = ((tbl[v[6]] & 1) << 7) | (tbl[v[7]] << 2) | (tbl[v[8]] >> 3)
out[5] = ((tbl[v[8]] & 7) << 5) | tbl[v[9]]
# 10 bytes of entropy (80 bits)
typeid[6] = (TABLE[v[10]] << 3) | (TABLE[v[11]] >> 2)
typeid[7] = ((TABLE[v[11]] & 3) << 6) | (TABLE[v[12]] << 1) | (TABLE[v[13]] >> 4)
typeid[8] = ((TABLE[v[13]] & 15) << 4) | (TABLE[v[14]] >> 1)
typeid[9] = ((TABLE[v[14]] & 1) << 7) | (TABLE[v[15]] << 2) | (TABLE[v[16]] >> 3)
typeid[10] = ((TABLE[v[16]] & 7) << 5) | TABLE[v[17]]
typeid[11] = (TABLE[v[18]] << 3) | (TABLE[v[19]] >> 2)
typeid[12] = ((TABLE[v[19]] & 3) << 6) | (TABLE[v[20]] << 1) | (TABLE[v[21]] >> 4)
typeid[13] = ((TABLE[v[21]] & 15) << 4) | (TABLE[v[22]] >> 1)
typeid[14] = ((TABLE[v[22]] & 1) << 7) | (TABLE[v[23]] << 2) | (TABLE[v[24]] >> 3)
typeid[15] = ((TABLE[v[24]] & 7) << 5) | TABLE[v[25]]
# 10 bytes entropy (80 bits)
out[6] = (tbl[v[10]] << 3) | (tbl[v[11]] >> 2)
out[7] = ((tbl[v[11]] & 3) << 6) | (tbl[v[12]] << 1) | (tbl[v[13]] >> 4)
out[8] = ((tbl[v[13]] & 15) << 4) | (tbl[v[14]] >> 1)
out[9] = ((tbl[v[14]] & 1) << 7) | (tbl[v[15]] << 2) | (tbl[v[16]] >> 3)
out[10] = ((tbl[v[16]] & 7) << 5) | tbl[v[17]]
out[11] = (tbl[v[18]] << 3) | (tbl[v[19]] >> 2)
out[12] = ((tbl[v[19]] & 3) << 6) | (tbl[v[20]] << 1) | (tbl[v[21]] >> 4)
out[13] = ((tbl[v[21]] & 15) << 4) | (tbl[v[22]] >> 1)
out[14] = ((tbl[v[22]] & 1) << 7) | (tbl[v[23]] << 2) | (tbl[v[24]] >> 3)
out[15] = ((tbl[v[24]] & 7) << 5) | tbl[v[25]]
return typeid
return bytes(out)
def encode(src: bytes) -> str:
if _HAS_RUST:
return _encode_rust(src)
return _encode_py(src)
def decode(s: str) -> bytes:
if _HAS_RUST:
return _decode_rust(s)
return _decode_py(s)

@@ -134,5 +134,7 @@ """

created_at = _uuid7_created_at(uuid_obj)
sortable = True # UUIDv7 is time-ordered by design
ver = _uuid_version(uuid_obj)
created_at = _uuid7_created_at(uuid_obj) if ver == 7 else None
sortable = True if ver == 7 else False
return ParsedTypeID(

@@ -249,1 +251,9 @@ raw=id_str,

exp.provenance.setdefault("sortable", Provenance.DERIVED_FROM_ID)
def _uuid_version(u: Any) -> Optional[int]:
try:
# uuid.UUID and uuid6.UUID both usually expose .version
return int(u.version)
except Exception:
return None

@@ -1,14 +0,31 @@

import uuid
from datetime import datetime, timezone
import warnings
from typing import Generic, Optional, TypeVar
import uuid6
import uuid as std_uuid
from typeid import base32
from typeid.errors import InvalidTypeIDStringException
from typeid.validation import validate_prefix, validate_suffix
from typeid.validation import validate_prefix, validate_suffix_and_decode
from typeid._uuid_backend import get_uuid_backend
_backend = get_uuid_backend()
PrefixT = TypeVar("PrefixT", bound=str)
def _uuid_from_bytes_v7(uuid_bytes: bytes) -> std_uuid.UUID:
"""
Construct a UUID object from bytes.
Prefer uuid6 (if installed) to preserve UUIDv7 semantics like `.time`.
"""
try:
import uuid6 # type: ignore
uuid_int = int.from_bytes(uuid_bytes, "big")
return uuid6.UUID(int=uuid_int)
except Exception:
return std_uuid.UUID(bytes=uuid_bytes)
class TypeID(Generic[PrefixT]):

@@ -37,2 +54,4 @@ """

__slots__ = ("_prefix", "_suffix", "_uuid_bytes", "_uuid", "_str")
def __init__(self, prefix: Optional[PrefixT] = None, suffix: Optional[str] = None) -> None:

@@ -54,15 +73,25 @@ """

"""
# If no suffix is provided, generate a new UUIDv7 and encode it as Base32.
suffix = _convert_uuid_to_b32(uuid6.uuid7()) if not suffix else suffix
# Validate prefix early (cheap) so failures don't do extra work
if prefix:
validate_prefix(prefix=prefix)
self._prefix: Optional[PrefixT] = prefix
# Ensure the suffix is a valid encoded UUID representation.
validate_suffix(suffix=suffix)
self._str: Optional[str] = None
self._uuid: Optional[std_uuid.UUID] = None
self._uuid_bytes: Optional[bytes] = None
# Prefix is optional; when present it must satisfy the project's prefix rules.
if prefix is not None:
validate_prefix(prefix=prefix)
if not suffix:
# generate uuid (fast path)
u = _backend.uuid7()
uuid_bytes = u.bytes
suffix = base32.encode(uuid_bytes)
# Cache UUID object (keep original type for user expectations)
self._uuid = u
self._uuid_bytes = uuid_bytes
else:
# validate+decode once; don't create UUID object yet
uuid_bytes = validate_suffix_and_decode(suffix)
self._uuid_bytes = uuid_bytes
# Keep prefix as Optional internally. String rendering decides whether to show it.
self._prefix: Optional[PrefixT] = prefix
self._suffix: str = suffix
self._suffix = suffix

@@ -93,3 +122,3 @@ @classmethod

@classmethod
def from_uuid(cls, suffix: uuid.UUID, prefix: Optional[PrefixT] = None) -> "TypeID":
def from_uuid(cls, suffix: std_uuid.UUID, prefix: Optional[PrefixT] = None) -> "TypeID":
"""

@@ -108,6 +137,17 @@ Construct a TypeID from an existing UUID.

"""
# Encode the UUID into the canonical Base32 suffix representation.
suffix_str = _convert_uuid_to_b32(suffix)
return cls(suffix=suffix_str, prefix=prefix)
# Validate prefix (if provided)
if prefix:
validate_prefix(prefix=prefix)
uuid_bytes = suffix.bytes
suffix_str = base32.encode(uuid_bytes)
obj = cls.__new__(cls) # bypass __init__ to avoid decode+validate cycle
obj._prefix = prefix
obj._suffix = suffix_str
obj._uuid_bytes = uuid_bytes
obj._uuid = suffix # keep original object type (uuid6/uuid_utils/stdlib)
obj._str = None
return obj
@property

@@ -139,3 +179,3 @@ def suffix(self) -> str:

@property
def uuid(self) -> uuid6.UUID:
def uuid(self) -> std_uuid.UUID:
"""

@@ -146,9 +186,57 @@ The UUID represented by this TypeID.

The decoded UUID value.
"""
# Lazy materialization
if self._uuid is None:
assert self._uuid_bytes is not None
self._uuid = _uuid_from_bytes_v7(self._uuid_bytes)
return self._uuid
Notes:
- This decodes `self.suffix` each time it is accessed.
- The UUID type here follows `uuid6.UUID` used by the project.
@property
def uuid_bytes(self) -> bytes:
"""
return _convert_b32_to_uuid(self.suffix)
Raw bytes of the underlying UUID.
This returns the canonical 16-byte representation of the UUID encoded
in this TypeID. The value is derived lazily from the suffix and cached
on first access.
This property is backend-agnostic and independent of the concrete
UUID implementation used internally.
Returns:
A 16-byte ``bytes`` object representing the UUID.
"""
if self._uuid_bytes is None:
self._uuid_bytes = base32.decode(self._suffix)
return self._uuid_bytes
@property
def created_at(self) -> Optional[datetime]:
"""
Creation time embedded in the underlying UUID, if available.
TypeID typically uses UUIDv7 for generated IDs. UUIDv7 encodes the Unix
timestamp (milliseconds) in the most significant 48 bits of the 128-bit UUID.
Returns:
A timezone-aware UTC datetime if the underlying UUID is version 7,
otherwise None.
"""
u = self.uuid
# Only UUIDv7 has a defined "created_at" in this sense.
try:
if getattr(u, "version", None) != 7:
return None
except Exception:
return None
try:
# UUID is 128 bits; top 48 bits are unix epoch time in milliseconds.
# So: unix_ms = uuid_int >> (128 - 48) = uuid_int >> 80
unix_ms = int(u.int) >> 80
return datetime.fromtimestamp(unix_ms / 1000.0, tz=timezone.utc)
except Exception:
return None
def __str__(self) -> str:

@@ -161,7 +249,12 @@ """

"""
value = ""
# cache string representation; helps workflow + comparisons
s = self._str
if s is not None:
return s
if self.prefix:
value += f"{self.prefix}_"
value += self.suffix
return value
s = f"{self.prefix}_{self.suffix}"
else:
s = self.suffix
self._str = s
return s

@@ -224,3 +317,3 @@ def __repr__(self):

def from_uuid(suffix: uuid.UUID, prefix: Optional[str] = None) -> TypeID:
def from_uuid(suffix: std_uuid.UUID, prefix: Optional[str] = None) -> TypeID:
warnings.warn("Consider TypeID.from_uuid instead.", DeprecationWarning)

@@ -245,11 +338,1 @@ return TypeID.from_uuid(suffix=suffix, prefix=prefix)

return prefix, suffix
def _convert_uuid_to_b32(uuid_instance: uuid.UUID) -> str:
return base32.encode(list(uuid_instance.bytes))
def _convert_b32_to_uuid(b32: str) -> uuid6.UUID:
uuid_bytes = bytes(base32.decode(b32))
uuid_int = int.from_bytes(uuid_bytes, byteorder="big")
return uuid6.UUID(int=uuid_int, version=7)

@@ -7,10 +7,16 @@ import re

_PREFIX_RE = re.compile(r"^([a-z]([a-z0-9_]{0,61}[a-z0-9])?)?$") # allow digits too (spec-like)
def validate_prefix(prefix: str) -> None:
# See https://github.com/jetify-com/typeid/tree/main/spec
if not re.match("^([a-z]([a-z_]{0,61}[a-z])?)?$", prefix):
# Use fullmatch (anchored) and precompiled regex
if not _PREFIX_RE.fullmatch(prefix or ""):
raise PrefixValidationException(f"Invalid prefix: {prefix}.")
def validate_suffix(suffix: str) -> None:
def validate_suffix_and_decode(suffix: str) -> bytes:
"""
Validate a TypeID suffix and return decoded UUID bytes (16 bytes).
This guarantees: one decode per suffix on the fast path.
"""
if (

@@ -25,5 +31,10 @@ len(suffix) != SUFFIX_LEN

raise SuffixValidationException(f"Invalid suffix: {suffix}.")
try:
base32.decode(suffix)
uuid_bytes = base32.decode(suffix) # rust-backed or py fallback
except Exception as exc:
raise SuffixValidationException(f"Invalid suffix: {suffix}.") from exc
if len(uuid_bytes) != 16:
raise SuffixValidationException(f"Invalid suffix: {suffix}.")
return uuid_bytes