typeid-python
Advanced tools
| [console_scripts] | ||
| typeid=typeid.cli:cli |
| MIT License | ||
| Copyright (c) 2023 Murad Akhundov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+238
| Metadata-Version: 2.4 | ||
| Name: typeid-python | ||
| Version: 0.3.6 | ||
| Classifier: Development Status :: 3 - Alpha | ||
| Classifier: License :: OSI Approved :: MIT License | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Classifier: Programming Language :: Python :: 3.12 | ||
| Classifier: Programming Language :: Python :: 3.13 | ||
| Classifier: Programming Language :: Python :: 3.14 | ||
| Classifier: Operating System :: OS Independent | ||
| Requires-Dist: uuid-utils>=0.12.0 | ||
| Requires-Dist: click ; extra == 'cli' | ||
| Requires-Dist: pyyaml ; extra == 'yaml' | ||
| Provides-Extra: cli | ||
| Provides-Extra: yaml | ||
| License-File: LICENSE | ||
| Summary: Python implementation of TypeIDs: type-safe, K-sortable, and globally unique identifiers inspired by Stripe IDs | ||
| Keywords: typeid,uuid,rust,guid,uuid7 | ||
| Author-email: Murad Akhundov <akhundov1murad@gmail.com> | ||
| License-Expression: MIT | ||
| Requires-Python: >=3.10, <4 | ||
| Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM | ||
| Project-URL: Homepage, https://github.com/akhundMurad/typeid-python | ||
| Project-URL: Repository, https://github.com/akhundMurad/typeid-python | ||
| Project-URL: Bug Tracker, https://github.com/akhundMurad/typeid-python/issues | ||
| # TypeID Python | ||
| [](https://github.com/akhundMurad/typeid-python/actions/workflows/test.yml) | ||
| [](https://pepy.tech/projects/typeid-python) | ||
| [](https://pypi.org/project/typeid-python/) | ||
| [](https://pypi.org/project/typeid-python/) | ||
| A **high-performance Python implementation of [TypeIDs](https://github.com/jetpack-io/typeid)** — type-safe, | ||
| sortable identifiers based on **UUIDv7**. | ||
| TypeIDs are designed for modern systems where identifiers should be: | ||
| - globally unique | ||
| - sortable by creation time | ||
| - safe to expose externally | ||
| - easy to reason about in logs, APIs, and databases | ||
| This library provides a Python package with optional Rust acceleration. | ||
| ## Key features | ||
| - ✅ 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 | ||
| ## Performance | ||
| TypeID is optimized for **real-world performance**, not just correctness. | ||
| ### Benchmark summary (mean time) | ||
| | 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** | | ||
| ### Highlights | ||
| * 🚀 **~5× faster generation** | ||
| * ⚡ **~1.6× faster parsing** | ||
| * 🔁 **~2.5× faster end-to-end workflows** | ||
| Benchmarks are: | ||
| * reproducible | ||
| * committed as raw JSON | ||
| * runnable locally via `bench/` | ||
| See [`Docs: Performance`](https://akhundmurad.github.io/typeid-python/performance/) for details. | ||
| ## Installation | ||
| ### Core | ||
| ```console | ||
| $ pip install typeid-python | ||
| ``` | ||
| Included: | ||
| * Rust base32 encode/decode | ||
| * `uuid-utils` for fast UUIDv7 generation | ||
| ### Other optional extras | ||
| ```console | ||
| $ pip install typeid-python[yaml] # YAML schema support | ||
| $ pip install typeid-python[cli] # CLI tools | ||
| ``` | ||
| Extras are **strictly optional**. | ||
| ## Usage | ||
| ### Basic | ||
| ```python | ||
| from typeid import TypeID | ||
| tid = TypeID(prefix="user") | ||
| assert tid.prefix == "user" | ||
| assert isinstance(tid.suffix, str) | ||
| assert str(tid).startswith("user_") | ||
| ``` | ||
| ### From string | ||
| ```python | ||
| from typeid import TypeID | ||
| tid = TypeID.from_string("user_01h45ytscbebyvny4gc8cr8ma2") | ||
| assert tid.prefix == "user" | ||
| ``` | ||
| ### From UUIDv7 | ||
| ```python | ||
| from typeid import TypeID | ||
| from uuid_utils import uuid7 | ||
| u = uuid7() | ||
| tid = TypeID.from_uuid(prefix="user", suffix=u) | ||
| assert tid.uuid.version == 7 | ||
| ``` | ||
| ### Typed prefixes | ||
| ```python | ||
| from typing import Literal | ||
| from typeid import TypeID, typeid_factory | ||
| UserID = TypeID[Literal["user"]] | ||
| gen_user_id = typeid_factory("user") | ||
| user_id = gen_user_id() | ||
| ``` | ||
| ## CLI | ||
| ```console | ||
| $ pip install typeid-python[cli] | ||
| ``` | ||
| Generate: | ||
| ```console | ||
| $ typeid new -p user | ||
| user_01h2xcejqtf2nbrexx3vqjhp41 | ||
| ``` | ||
| Decode: | ||
| ```console | ||
| $ typeid decode user_01h2xcejqtf2nbrexx3vqjhp41 | ||
| uuid: 0188bac7-4afa-78aa-bc3b-bd1eef28d881 | ||
| ``` | ||
| Encode: | ||
| ```console | ||
| $ typeid encode 0188bac7-4afa-78aa-bc3b-bd1eef28d881 --prefix user | ||
| ``` | ||
| ## ✨ `typeid explain` — understand any ID | ||
| ```console | ||
| $ typeid explain user_01h45ytscbebyvny4gc8cr8ma2 | ||
| ``` | ||
| Outputs: | ||
| ```yaml | ||
| parsed: | ||
| prefix: user | ||
| uuid: 01890bf0-846f-7762-8605-5a3abb40e0e5 | ||
| created_at: 2025-03-12T10:41:23Z | ||
| sortable: true | ||
| ``` | ||
| Works **without schema**, fully offline. | ||
| ## Schema-based explanations | ||
| Define meaning for prefixes using JSON or YAML. | ||
| Example (`typeid.schema.json`): | ||
| ```json | ||
| { | ||
| "schema_version": 1, | ||
| "types": { | ||
| "user": { | ||
| "name": "User", | ||
| "owner_team": "identity-platform", | ||
| "pii": true | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| Then: | ||
| ```console | ||
| $ typeid explain user_01h45ytscbebyvny4gc8cr8ma2 | ||
| ``` | ||
| Read more here: ["Docs: Explain"](https://akhundmurad.github.io/typeid-python/performance/). | ||
| ## Design principles | ||
| * **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 | ||
| > Think of TypeID as | ||
| > **UUIDs + semantics + observability — without sacrificing speed** | ||
| ## License | ||
| MIT | ||
+21
| typeid/__init__.py,sha256=YWaZ6tmmlnlFfNQO2NmrzlEW04yRCp54H8A-pRTdH1E,314 | ||
| typeid/_base32.cpython-310-darwin.so,sha256=g-NS8BbilCnFDhHQie8IQQGhy-hf68XIXDoRXCnyEbY,487872 | ||
| typeid/base32.py,sha256=74oJ1Xm3wHTBY1DqaIk53tVxmL3V6yTnFDuOK4mkbhM,210 | ||
| typeid/cli.py,sha256=luCyGYbt0z1VhiCWcLVWaBspdOH8hWCHT0Kn1oznPk4,4159 | ||
| typeid/constants.py,sha256=ua-JigETdE4pZvOB9ALK_71sDOZKzOYIabHlJQ01yAw,84 | ||
| typeid/errors.py,sha256=2bSRH97Uvq_V1KnLadTmTNHAV5yOXOAHetJwz0YKDzc,225 | ||
| typeid/explain/__init__.py,sha256=2kSFzI_l7F9cXUAN_JTJbu-1ID-TlgLYJBl9hjkRz-A,2141 | ||
| typeid/explain/discovery.py,sha256=RKxebqLUqwQpHQp_nTO5PkK2-z8dejGHWtuw35VmAK8,3681 | ||
| typeid/explain/engine.py,sha256=-cKThbazfdvSNb1YvJ8JZ0t3N9Z2av0ubwMEsW0y_Os,8023 | ||
| typeid/explain/formatters.py,sha256=VAZGNGVKq52YS6ZjWPB28IGr_opiP676RSel95qV3iQ,6819 | ||
| typeid/explain/model.py,sha256=pd-vqA6SAAnPV1l5_ZVbTqwXoRLELd0n_7tbNtg17L8,5086 | ||
| typeid/explain/registry.py,sha256=DUQ6Jmk4M5la7Ye5BMXQMhqmDKjM36ohTUYx6ZRvjDo,6786 | ||
| typeid/factory.py,sha256=UJzWN8djufyYQhR08GmXyGrKfWaGIUvPv1GIKKG5RJo,909 | ||
| typeid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | ||
| typeid/typeid.py,sha256=axWR9dSXJ0QHZclPFHshe-vS1OjWANPBXbFEpAcT-ro,10657 | ||
| typeid/validation.py,sha256=0c2XWTkHro1gLaJmQnqW6XoSD1weSxWpBtEAbU157PI,1313 | ||
| typeid_python-0.3.6.dist-info/METADATA,sha256=9PVkYLwbrT_jpKp-dodrsbD6ZnkqvP00T8YM4bC3m2g,5810 | ||
| typeid_python-0.3.6.dist-info/WHEEL,sha256=IiUSfnZcnKtfg3tUHJ05sdhsIxnb0hSO-4fdRXTiMXo,105 | ||
| typeid_python-0.3.6.dist-info/entry_points.txt,sha256=nnZGQ4ygTxWYd3Wq1MknTNFLN76MIxJ8FFRB54QVhGs,40 | ||
| typeid_python-0.3.6.dist-info/licenses/LICENSE,sha256=f98oZ9FId4i3835UJYGw066BU8PSc57L1utGWS1ypcs,1070 | ||
| typeid_python-0.3.6.dist-info/RECORD,, |
Sorry, the diff of this file is not supported yet
+4
| Wheel-Version: 1.0 | ||
| Generator: maturin (1.10.2) | ||
| Root-Is-Purelib: false | ||
| Tag: cp310-cp310-macosx_11_0_arm64 |
+3
-231
@@ -1,237 +0,9 @@ | ||
| try: | ||
| from typeid_base32 import encode as _encode_rust, decode as _decode_rust # type: ignore | ||
| 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 | ||
| ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz" | ||
| # TABLE maps ASCII byte -> 0..31 or 0xFF if invalid | ||
| TABLE = [ | ||
| 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, | ||
| 0x00, | ||
| 0x01, | ||
| 0x02, | ||
| 0x03, | ||
| 0x04, | ||
| 0x05, | ||
| 0x06, | ||
| 0x07, | ||
| 0x08, | ||
| 0x09, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0x0A, | ||
| 0x0B, | ||
| 0x0C, | ||
| 0x0D, | ||
| 0x0E, | ||
| 0x0F, | ||
| 0x10, | ||
| 0x11, | ||
| 0xFF, | ||
| 0x12, | ||
| 0x13, | ||
| 0xFF, | ||
| 0x14, | ||
| 0x15, | ||
| 0xFF, | ||
| 0x16, | ||
| 0x17, | ||
| 0x18, | ||
| 0x19, | ||
| 0x1A, | ||
| 0xFF, | ||
| 0x1B, | ||
| 0x1C, | ||
| 0x1D, | ||
| 0x1E, | ||
| 0x1F, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0x0A, | ||
| 0x0B, | ||
| 0x0C, | ||
| 0x0D, | ||
| 0x0E, | ||
| 0x0F, | ||
| 0x10, | ||
| 0x11, | ||
| 0xFF, | ||
| 0x12, | ||
| 0x13, | ||
| 0xFF, | ||
| 0x14, | ||
| 0x15, | ||
| 0xFF, | ||
| 0x16, | ||
| 0x17, | ||
| 0x18, | ||
| 0x19, | ||
| 0x1A, | ||
| 0xFF, | ||
| 0x1B, | ||
| 0x1C, | ||
| 0x1D, | ||
| 0x1E, | ||
| 0x1F, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| 0xFF, | ||
| ] + [0xFF] * (256 - 128) | ||
| BytesLike = Union[bytes, bytearray, memoryview] | ||
| def _encode_py(src: BytesLike) -> str: | ||
| mv = memoryview(src) | ||
| if mv.nbytes != 16: | ||
| raise RuntimeError("Invalid length.") | ||
| # Pre-allocate output chars | ||
| dst = [""] * SUFFIX_LEN | ||
| # 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_py(s: str) -> bytes: | ||
| if len(s) != SUFFIX_LEN: | ||
| raise RuntimeError("Invalid length.") | ||
| v = s.encode("utf-8") | ||
| tbl = TABLE | ||
| for b in v: | ||
| if tbl[b] == 0xFF: | ||
| raise RuntimeError("Invalid base32 character") | ||
| out = bytearray(16) | ||
| # 6 bytes timestamp (48 bits) | ||
| 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 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 bytes(out) | ||
| def encode(src: bytes) -> str: | ||
| if _HAS_RUST: | ||
| return _encode_rust(src) | ||
| return _encode_py(src) | ||
| return _encode_rust(src) | ||
| def decode(s: str) -> bytes: | ||
| if _HAS_RUST: | ||
| return _decode_rust(s) | ||
| return _decode_py(s) | ||
| return _decode_rust(s) |
+1
-1
@@ -5,3 +5,3 @@ from pathlib import Path | ||
| import click | ||
| from uuid6 import UUID | ||
| from uuid_utils import UUID | ||
@@ -8,0 +8,0 @@ from typeid import TypeID, base32, from_uuid, get_prefix_and_suffix |
| SUFFIX_LEN = 26 | ||
| PREFIX_MAX_LEN = 63 | ||
| ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz" |
@@ -71,3 +71,3 @@ """ | ||
| schema = schema_lookup(parsed.prefix) | ||
| except Exception as e: # never let schema backend break explain | ||
| except Exception as e: | ||
| exp.warnings.append(f"Schema lookup failed: {e!s}") | ||
@@ -132,3 +132,3 @@ schema = None | ||
| # Derived facts from the validated TypeID | ||
| uuid_obj = tid.uuid # library returns a UUID object (uuid6.UUID) | ||
| uuid_obj = tid.uuid # library returns a UUID object | ||
| uuid_str = str(uuid_obj) | ||
@@ -178,3 +178,2 @@ | ||
| try: | ||
| # uuid_obj is likely uuid6.UUID, but supports .int like uuid.UUID | ||
| u_int = int(uuid_obj.int) | ||
@@ -257,5 +256,4 @@ unix_ms = u_int >> 80 | ||
| try: | ||
| # uuid.UUID and uuid6.UUID both usually expose .version | ||
| return int(u.version) | ||
| except Exception: | ||
| return None |
@@ -53,3 +53,3 @@ """ | ||
| # Derived (best-effort) | ||
| uuid: Optional[str] = None # keep as string to avoid uuid/uuid6 typing bleed | ||
| uuid: Optional[str] = None | ||
| created_at: Optional[datetime] = None | ||
@@ -56,0 +56,0 @@ sortable: Optional[bool] = None # TypeIDs w/ UUIDv7 are typically sortable |
+10
-23
| from datetime import datetime, timezone | ||
| import warnings | ||
| import uuid_utils | ||
| from typing import Generic, Optional, TypeVar | ||
| import uuid as std_uuid | ||
| from typeid import base32 | ||
| from typeid.errors import InvalidTypeIDStringException | ||
| 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: | ||
| def _uuid_from_bytes_v7(uuid_bytes: bytes) -> uuid_utils.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 uuid_utils.UUID(int=uuid_int) | ||
| 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]): | ||
@@ -78,3 +68,3 @@ """ | ||
| self._str: Optional[str] = None | ||
| self._uuid: Optional[std_uuid.UUID] = None | ||
| self._uuid: Optional[uuid_utils.UUID] = None | ||
| self._uuid_bytes: Optional[bytes] = None | ||
@@ -84,3 +74,3 @@ | ||
| # generate uuid (fast path) | ||
| u = _backend.uuid7() | ||
| u = uuid_utils.uuid7() | ||
| uuid_bytes = u.bytes | ||
@@ -122,3 +112,3 @@ suffix = base32.encode(uuid_bytes) | ||
| @classmethod | ||
| def from_uuid(cls, suffix: std_uuid.UUID, prefix: Optional[PrefixT] = None) -> "TypeID": | ||
| def from_uuid(cls, suffix: uuid_utils.UUID, prefix: Optional[PrefixT] = None) -> "TypeID": | ||
| """ | ||
@@ -148,3 +138,3 @@ Construct a TypeID from an existing UUID. | ||
| obj._uuid_bytes = uuid_bytes | ||
| obj._uuid = suffix # keep original object type (uuid6/uuid_utils/stdlib) | ||
| obj._uuid = suffix # keep original object type | ||
| obj._str = None | ||
@@ -179,3 +169,3 @@ return obj | ||
| @property | ||
| def uuid(self) -> std_uuid.UUID: | ||
| def uuid(self) -> uuid_utils.UUID: | ||
| """ | ||
@@ -202,5 +192,2 @@ The UUID represented by this TypeID. | ||
| This property is backend-agnostic and independent of the concrete | ||
| UUID implementation used internally. | ||
| Returns: | ||
@@ -316,3 +303,3 @@ A 16-byte ``bytes`` object representing the UUID. | ||
| def from_uuid(suffix: std_uuid.UUID, prefix: Optional[str] = None) -> TypeID: | ||
| def from_uuid(suffix: uuid_utils.UUID, prefix: Optional[str] = None) -> TypeID: | ||
| warnings.warn("Consider TypeID.from_uuid instead.", DeprecationWarning) | ||
@@ -319,0 +306,0 @@ return TypeID.from_uuid(suffix=suffix, prefix=prefix) |
| import re | ||
| from typeid import base32 | ||
| from typeid.constants import SUFFIX_LEN | ||
| from typeid.constants import SUFFIX_LEN, ALPHABET | ||
| from typeid.errors import PrefixValidationException, SuffixValidationException | ||
@@ -26,3 +26,3 @@ | ||
| or (not suffix.isdigit() and not suffix.islower()) | ||
| or any([symbol not in base32.ALPHABET for symbol in suffix]) | ||
| or any([symbol not in ALPHABET for symbol in suffix]) | ||
| or suffix[0] > "7" | ||
@@ -29,0 +29,0 @@ ): |
-314
| # Created by https://www.toptal.com/developers/gitignore/api/venv,python,visualstudiocode,pycharm | ||
| # Edit at https://www.toptal.com/developers/gitignore?templates=venv,python,visualstudiocode,pycharm | ||
| ### PyCharm ### | ||
| # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider | ||
| # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 | ||
| # User-specific stuff | ||
| .idea/**/workspace.xml | ||
| .idea/**/tasks.xml | ||
| .idea/**/usage.statistics.xml | ||
| .idea/**/dictionaries | ||
| .idea/**/shelf | ||
| # AWS User-specific | ||
| .idea/**/aws.xml | ||
| # Generated files | ||
| .idea/**/contentModel.xml | ||
| # Sensitive or high-churn files | ||
| .idea/**/dataSources/ | ||
| .idea/**/dataSources.ids | ||
| .idea/**/dataSources.local.xml | ||
| .idea/**/sqlDataSources.xml | ||
| .idea/**/dynamic.xml | ||
| .idea/**/uiDesigner.xml | ||
| .idea/**/dbnavigator.xml | ||
| # Gradle | ||
| .idea/**/gradle.xml | ||
| .idea/**/libraries | ||
| # Gradle and Maven with auto-import | ||
| # When using Gradle or Maven with auto-import, you should exclude module files, | ||
| # since they will be recreated, and may cause churn. Uncomment if using | ||
| # auto-import. | ||
| # .idea/artifacts | ||
| # .idea/compiler.xml | ||
| # .idea/jarRepositories.xml | ||
| # .idea/modules.xml | ||
| # .idea/*.iml | ||
| # .idea/modules | ||
| # *.iml | ||
| # *.ipr | ||
| # CMake | ||
| cmake-build-*/ | ||
| # Mongo Explorer plugin | ||
| .idea/**/mongoSettings.xml | ||
| # File-based project format | ||
| *.iws | ||
| # IntelliJ | ||
| out/ | ||
| # mpeltonen/sbt-idea plugin | ||
| .idea_modules/ | ||
| # JIRA plugin | ||
| atlassian-ide-plugin.xml | ||
| # Cursive Clojure plugin | ||
| .idea/replstate.xml | ||
| # SonarLint plugin | ||
| .idea/sonarlint/ | ||
| # Crashlytics plugin (for Android Studio and IntelliJ) | ||
| com_crashlytics_export_strings.xml | ||
| crashlytics.properties | ||
| crashlytics-build.properties | ||
| fabric.properties | ||
| # Editor-based Rest Client | ||
| .idea/httpRequests | ||
| # Android studio 3.1+ serialized cache file | ||
| .idea/caches/build_file_checksums.ser | ||
| ### PyCharm Patch ### | ||
| # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 | ||
| # *.iml | ||
| # modules.xml | ||
| # .idea/misc.xml | ||
| # *.ipr | ||
| # Sonarlint plugin | ||
| # https://plugins.jetbrains.com/plugin/7973-sonarlint | ||
| .idea/**/sonarlint/ | ||
| # SonarQube Plugin | ||
| # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin | ||
| .idea/**/sonarIssues.xml | ||
| # Markdown Navigator plugin | ||
| # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced | ||
| .idea/**/markdown-navigator.xml | ||
| .idea/**/markdown-navigator-enh.xml | ||
| .idea/**/markdown-navigator/ | ||
| # Cache file creation bug | ||
| # See https://youtrack.jetbrains.com/issue/JBR-2257 | ||
| .idea/$CACHE_FILE$ | ||
| # CodeStream plugin | ||
| # https://plugins.jetbrains.com/plugin/12206-codestream | ||
| .idea/codestream.xml | ||
| # Azure Toolkit for IntelliJ plugin | ||
| # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij | ||
| .idea/**/azureSettings.xml | ||
| ### Python ### | ||
| # Byte-compiled / optimized / DLL files | ||
| __pycache__/ | ||
| *.py[cod] | ||
| *$py.class | ||
| # C extensions | ||
| *.so | ||
| # Distribution / packaging | ||
| .Python | ||
| build/ | ||
| develop-eggs/ | ||
| dist/ | ||
| downloads/ | ||
| eggs/ | ||
| .eggs/ | ||
| lib/ | ||
| lib64/ | ||
| parts/ | ||
| sdist/ | ||
| var/ | ||
| wheels/ | ||
| share/python-wheels/ | ||
| *.egg-info/ | ||
| .installed.cfg | ||
| *.egg | ||
| MANIFEST | ||
| # PyInstaller | ||
| # Usually these files are written by a python script from a template | ||
| # before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
| *.manifest | ||
| *.spec | ||
| # Installer logs | ||
| pip-log.txt | ||
| pip-delete-this-directory.txt | ||
| # Unit test / coverage reports | ||
| htmlcov/ | ||
| .tox/ | ||
| .nox/ | ||
| .coverage | ||
| .coverage.* | ||
| .cache | ||
| nosetests.xml | ||
| coverage.xml | ||
| *.cover | ||
| *.py,cover | ||
| .hypothesis/ | ||
| .pytest_cache/ | ||
| cover/ | ||
| # Translations | ||
| *.mo | ||
| *.pot | ||
| # Django stuff: | ||
| *.log | ||
| local_settings.py | ||
| db.sqlite3 | ||
| db.sqlite3-journal | ||
| # Flask stuff: | ||
| instance/ | ||
| .webassets-cache | ||
| # Scrapy stuff: | ||
| .scrapy | ||
| # Sphinx documentation | ||
| docs/_build/ | ||
| # PyBuilder | ||
| .pybuilder/ | ||
| target/ | ||
| # Jupyter Notebook | ||
| .ipynb_checkpoints | ||
| # IPython | ||
| profile_default/ | ||
| ipython_config.py | ||
| # pyenv | ||
| # For a library or package, you might want to ignore these files since the code is | ||
| # intended to run in multiple environments; otherwise, check them in: | ||
| # .python-version | ||
| # pipenv | ||
| # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | ||
| # However, in case of collaboration, if having platform-specific dependencies or dependencies | ||
| # having no cross-platform support, pipenv may install dependencies that don't work, or not | ||
| # install all needed dependencies. | ||
| #Pipfile.lock | ||
| # poetry | ||
| # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. | ||
| # This is especially recommended for binary packages to ensure reproducibility, and is more | ||
| # commonly ignored for libraries. | ||
| # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control | ||
| #poetry.lock | ||
| # pdm | ||
| # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. | ||
| #pdm.lock | ||
| # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it | ||
| # in version control. | ||
| # https://pdm.fming.dev/#use-with-ide | ||
| .pdm.toml | ||
| # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm | ||
| __pypackages__/ | ||
| # Celery stuff | ||
| celerybeat-schedule | ||
| celerybeat.pid | ||
| # SageMath parsed files | ||
| *.sage.py | ||
| # Environments | ||
| .env | ||
| .venv | ||
| env/ | ||
| venv/ | ||
| ENV/ | ||
| env.bak/ | ||
| venv.bak/ | ||
| # Spyder project settings | ||
| .spyderproject | ||
| .spyproject | ||
| # Rope project settings | ||
| .ropeproject | ||
| # mkdocs documentation | ||
| /site | ||
| # mypy | ||
| .mypy_cache/ | ||
| .dmypy.json | ||
| dmypy.json | ||
| # Pyre type checker | ||
| .pyre/ | ||
| # pytype static type analyzer | ||
| .pytype/ | ||
| # Cython debug symbols | ||
| cython_debug/ | ||
| # PyCharm | ||
| # JetBrains specific template is maintained in a separate JetBrains.gitignore that can | ||
| # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore | ||
| # and can be added to the global gitignore or merged into this file. For a more nuclear | ||
| # option (not recommended) you can uncomment the following to ignore the entire idea folder. | ||
| #.idea/ | ||
| ### Python Patch ### | ||
| # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration | ||
| poetry.toml | ||
| # ruff | ||
| .ruff_cache/ | ||
| ### venv ### | ||
| # Virtualenv | ||
| # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ | ||
| [Bb]in | ||
| [Ii]nclude | ||
| [Ll]ib | ||
| [Ll]ib64 | ||
| [Ll]ocal | ||
| [Ss]cripts | ||
| pyvenv.cfg | ||
| pip-selfcheck.json | ||
| ### VisualStudioCode ### | ||
| .vscode/ | ||
| # Local History for Visual Studio Code | ||
| .history/ | ||
| # Built Visual Studio Code Extensions | ||
| *.vsix | ||
| ### VisualStudioCode Patch ### | ||
| # Ignore all local history of files | ||
| .history | ||
| .ionide | ||
| # End of https://www.toptal.com/developers/gitignore/api/venv,python,visualstudiocode,pycharm | ||
| .DS_Store |
-21
| MIT License | ||
| Copyright (c) 2023 Murad Akhundov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
-247
| Metadata-Version: 2.4 | ||
| Name: typeid-python | ||
| Version: 0.3.5 | ||
| Summary: Python implementation of TypeIDs: type-safe, K-sortable, and globally unique identifiers inspired by Stripe IDs | ||
| Project-URL: Homepage, https://github.com/akhundMurad/typeid-python | ||
| Project-URL: Repository, https://github.com/akhundMurad/typeid-python | ||
| Project-URL: Bug Tracker, https://github.com/akhundMurad/typeid-python/issues | ||
| Author-email: Murad Akhundov <akhundov1murad@gmail.com> | ||
| License-Expression: MIT | ||
| License-File: LICENSE | ||
| Keywords: guid,typeid,uuid,uuid6 | ||
| Classifier: Development Status :: 3 - Alpha | ||
| Classifier: License :: OSI Approved :: MIT License | ||
| Classifier: Operating System :: OS Independent | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Classifier: Programming Language :: Python :: 3.12 | ||
| Classifier: Programming Language :: Python :: 3.13 | ||
| Classifier: Programming Language :: Python :: 3.14 | ||
| Requires-Python: <4,>=3.10 | ||
| Requires-Dist: uuid6<2026.0.0,>=2024.7.10 | ||
| Provides-Extra: cli | ||
| Requires-Dist: click; extra == 'cli' | ||
| Provides-Extra: rust | ||
| Requires-Dist: uuid-utils>=0.12.0; extra == 'rust' | ||
| Provides-Extra: yaml | ||
| Requires-Dist: pyyaml; extra == 'yaml' | ||
| Description-Content-Type: text/markdown | ||
| # TypeID Python | ||
| [](https://github.com/akhundMurad/typeid-python/actions/workflows/test.yml) | ||
| [](https://pepy.tech/projects/typeid-python) | ||
| [](https://pypi.org/project/typeid-python/) | ||
| [](https://pypi.org/project/typeid-python/) | ||
| A **high-performance Python implementation of [TypeIDs](https://github.com/jetpack-io/typeid)** — type-safe, | ||
| sortable identifiers based on **UUIDv7**. | ||
| TypeIDs are designed for modern systems where identifiers should be: | ||
| - globally unique | ||
| - sortable by creation time | ||
| - safe to expose externally | ||
| - easy to reason about in logs, APIs, and databases | ||
| This library provides a Python package with optional Rust acceleration. | ||
| ## Key features | ||
| - ✅ 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 | ||
| ## Performance | ||
| TypeID is optimized for **real-world performance**, not just correctness. | ||
| ### Benchmark summary (mean time) | ||
| | 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** | | ||
| ### Highlights | ||
| * 🚀 **~5× faster generation** | ||
| * ⚡ **~1.6× faster parsing** | ||
| * 🔁 **~2.5× faster end-to-end workflows** | ||
| Benchmarks are: | ||
| * reproducible | ||
| * committed as raw JSON | ||
| * runnable locally via `bench/` | ||
| See [`Docs: Performance`](https://akhundmurad.github.io/typeid-python/performance/) for details. | ||
| ## Installation | ||
| ### Core (pure Python) | ||
| ```console | ||
| $ pip install typeid-python | ||
| ``` | ||
| ### With Rust acceleration (recommended) | ||
| ```console | ||
| $ pip install typeid-python[rust] | ||
| ``` | ||
| This enables: | ||
| * Rust base32 encode/decode | ||
| * `uuid-utils` for fast UUIDv7 generation | ||
| If Rust is unavailable, TypeID automatically falls back to the pure-Python implementation. | ||
| ### Other optional extras | ||
| ```console | ||
| $ pip install typeid-python[yaml] # YAML schema support | ||
| $ pip install typeid-python[cli] # CLI tools | ||
| ``` | ||
| Extras are **strictly optional**. | ||
| ## Usage | ||
| ### Basic | ||
| ```python | ||
| from typeid import TypeID | ||
| tid = TypeID(prefix="user") | ||
| assert tid.prefix == "user" | ||
| assert isinstance(tid.suffix, str) | ||
| assert str(tid).startswith("user_") | ||
| ``` | ||
| ### From string | ||
| ```python | ||
| from typeid import TypeID | ||
| tid = TypeID.from_string("user_01h45ytscbebyvny4gc8cr8ma2") | ||
| assert tid.prefix == "user" | ||
| ``` | ||
| ### From UUIDv7 | ||
| ```python | ||
| from typeid import TypeID | ||
| from uuid6 import uuid7 | ||
| u = uuid7() | ||
| tid = TypeID.from_uuid(prefix="user", suffix=u) | ||
| assert tid.uuid.version == 7 | ||
| ``` | ||
| ### Typed prefixes | ||
| ```python | ||
| from typing import Literal | ||
| from typeid import TypeID, typeid_factory | ||
| UserID = TypeID[Literal["user"]] | ||
| gen_user_id = typeid_factory("user") | ||
| user_id = gen_user_id() | ||
| ``` | ||
| ## CLI | ||
| ```console | ||
| $ pip install typeid-python[cli] | ||
| ``` | ||
| Generate: | ||
| ```console | ||
| $ typeid new -p user | ||
| user_01h2xcejqtf2nbrexx3vqjhp41 | ||
| ``` | ||
| Decode: | ||
| ```console | ||
| $ typeid decode user_01h2xcejqtf2nbrexx3vqjhp41 | ||
| uuid: 0188bac7-4afa-78aa-bc3b-bd1eef28d881 | ||
| ``` | ||
| Encode: | ||
| ```console | ||
| $ typeid encode 0188bac7-4afa-78aa-bc3b-bd1eef28d881 --prefix user | ||
| ``` | ||
| ## ✨ `typeid explain` — understand any ID | ||
| ```console | ||
| $ typeid explain user_01h45ytscbebyvny4gc8cr8ma2 | ||
| ``` | ||
| Outputs: | ||
| ```yaml | ||
| parsed: | ||
| prefix: user | ||
| uuid: 01890bf0-846f-7762-8605-5a3abb40e0e5 | ||
| created_at: 2025-03-12T10:41:23Z | ||
| sortable: true | ||
| ``` | ||
| Works **without schema**, fully offline. | ||
| ## Schema-based explanations | ||
| Define meaning for prefixes using JSON or YAML. | ||
| Example (`typeid.schema.json`): | ||
| ```json | ||
| { | ||
| "schema_version": 1, | ||
| "types": { | ||
| "user": { | ||
| "name": "User", | ||
| "owner_team": "identity-platform", | ||
| "pii": true | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| Then: | ||
| ```console | ||
| $ typeid explain user_01h45ytscbebyvny4gc8cr8ma2 | ||
| ``` | ||
| Read more here: ["Docs: Explain"](https://akhundmurad.github.io/typeid-python/performance/). | ||
| ## Design principles | ||
| * **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 | ||
| > Think of TypeID as | ||
| > **UUIDs + semantics + observability — without sacrificing speed** | ||
| ## License | ||
| MIT |
| [project] | ||
| name = "typeid-python" | ||
| version = "0.3.5" | ||
| description = "Python implementation of TypeIDs: type-safe, K-sortable, and globally unique identifiers inspired by Stripe IDs" | ||
| authors = [{ name = "Murad Akhundov", email = "akhundov1murad@gmail.com" }] | ||
| requires-python = ">=3.10,<4" | ||
| readme = "README.md" | ||
| license = "MIT" | ||
| keywords = [ | ||
| "typeid", | ||
| "uuid", | ||
| "uuid6", | ||
| "guid", | ||
| ] | ||
| classifiers = [ | ||
| "Development Status :: 3 - Alpha", | ||
| "License :: OSI Approved :: MIT License", | ||
| "Programming Language :: Python :: 3.10", | ||
| "Programming Language :: Python :: 3.11", | ||
| "Programming Language :: Python :: 3.12", | ||
| "Programming Language :: Python :: 3.13", | ||
| "Programming Language :: Python :: 3.14", | ||
| "Operating System :: OS Independent", | ||
| ] | ||
| dependencies = ["uuid6>=2024.7.10,<2026.0.0"] | ||
| [project.optional-dependencies] | ||
| cli = ["click"] | ||
| yaml = ["PyYAML"] | ||
| rust = ["uuid-utils>=0.12.0"] | ||
| [project.urls] | ||
| Homepage = "https://github.com/akhundMurad/typeid-python" | ||
| Repository = "https://github.com/akhundMurad/typeid-python" | ||
| "Bug Tracker" = "https://github.com/akhundMurad/typeid-python/issues" | ||
| [project.scripts] | ||
| typeid = "typeid.cli:cli" | ||
| [dependency-groups] | ||
| dev = [ | ||
| "pytest>=7.3.2,<8", | ||
| "black>=23.3.0,<24", | ||
| "mypy>=1.3.0,<2", | ||
| "requests>=2.31.0,<3", | ||
| "ruff>=0.14.5,<0.15", | ||
| "twine>=6.2.0,<7", | ||
| "pyyaml>=6.0", | ||
| "mkdocs-material>=9.7.1", | ||
| "mkdocstrings[python]>=1.0.0", | ||
| "mkdocs-git-revision-date-localized-plugin>=1.5.0", | ||
| "mkdocs-gen-files>=0.6.0", | ||
| "mkdocs-literate-nav>=0.6.2", | ||
| "mkdocs-section-index>=0.3.10", | ||
| "pytest-markdown-docs>=0.9.0", | ||
| "pytest-benchmark>=5.0.1", | ||
| "maturin>=1.5; platform_system != 'Windows'" | ||
| ] | ||
| [tool.hatch.build.targets.sdist] | ||
| include = ["typeid"] | ||
| [tool.hatch.build.targets.wheel] | ||
| include = ["typeid"] | ||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" |
-218
| # TypeID Python | ||
| [](https://github.com/akhundMurad/typeid-python/actions/workflows/test.yml) | ||
| [](https://pepy.tech/projects/typeid-python) | ||
| [](https://pypi.org/project/typeid-python/) | ||
| [](https://pypi.org/project/typeid-python/) | ||
| A **high-performance Python implementation of [TypeIDs](https://github.com/jetpack-io/typeid)** — type-safe, | ||
| sortable identifiers based on **UUIDv7**. | ||
| TypeIDs are designed for modern systems where identifiers should be: | ||
| - globally unique | ||
| - sortable by creation time | ||
| - safe to expose externally | ||
| - easy to reason about in logs, APIs, and databases | ||
| This library provides a Python package with optional Rust acceleration. | ||
| ## Key features | ||
| - ✅ 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 | ||
| ## Performance | ||
| TypeID is optimized for **real-world performance**, not just correctness. | ||
| ### Benchmark summary (mean time) | ||
| | 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** | | ||
| ### Highlights | ||
| * 🚀 **~5× faster generation** | ||
| * ⚡ **~1.6× faster parsing** | ||
| * 🔁 **~2.5× faster end-to-end workflows** | ||
| Benchmarks are: | ||
| * reproducible | ||
| * committed as raw JSON | ||
| * runnable locally via `bench/` | ||
| See [`Docs: Performance`](https://akhundmurad.github.io/typeid-python/performance/) for details. | ||
| ## Installation | ||
| ### Core (pure Python) | ||
| ```console | ||
| $ pip install typeid-python | ||
| ``` | ||
| ### With Rust acceleration (recommended) | ||
| ```console | ||
| $ pip install typeid-python[rust] | ||
| ``` | ||
| This enables: | ||
| * Rust base32 encode/decode | ||
| * `uuid-utils` for fast UUIDv7 generation | ||
| If Rust is unavailable, TypeID automatically falls back to the pure-Python implementation. | ||
| ### Other optional extras | ||
| ```console | ||
| $ pip install typeid-python[yaml] # YAML schema support | ||
| $ pip install typeid-python[cli] # CLI tools | ||
| ``` | ||
| Extras are **strictly optional**. | ||
| ## Usage | ||
| ### Basic | ||
| ```python | ||
| from typeid import TypeID | ||
| tid = TypeID(prefix="user") | ||
| assert tid.prefix == "user" | ||
| assert isinstance(tid.suffix, str) | ||
| assert str(tid).startswith("user_") | ||
| ``` | ||
| ### From string | ||
| ```python | ||
| from typeid import TypeID | ||
| tid = TypeID.from_string("user_01h45ytscbebyvny4gc8cr8ma2") | ||
| assert tid.prefix == "user" | ||
| ``` | ||
| ### From UUIDv7 | ||
| ```python | ||
| from typeid import TypeID | ||
| from uuid6 import uuid7 | ||
| u = uuid7() | ||
| tid = TypeID.from_uuid(prefix="user", suffix=u) | ||
| assert tid.uuid.version == 7 | ||
| ``` | ||
| ### Typed prefixes | ||
| ```python | ||
| from typing import Literal | ||
| from typeid import TypeID, typeid_factory | ||
| UserID = TypeID[Literal["user"]] | ||
| gen_user_id = typeid_factory("user") | ||
| user_id = gen_user_id() | ||
| ``` | ||
| ## CLI | ||
| ```console | ||
| $ pip install typeid-python[cli] | ||
| ``` | ||
| Generate: | ||
| ```console | ||
| $ typeid new -p user | ||
| user_01h2xcejqtf2nbrexx3vqjhp41 | ||
| ``` | ||
| Decode: | ||
| ```console | ||
| $ typeid decode user_01h2xcejqtf2nbrexx3vqjhp41 | ||
| uuid: 0188bac7-4afa-78aa-bc3b-bd1eef28d881 | ||
| ``` | ||
| Encode: | ||
| ```console | ||
| $ typeid encode 0188bac7-4afa-78aa-bc3b-bd1eef28d881 --prefix user | ||
| ``` | ||
| ## ✨ `typeid explain` — understand any ID | ||
| ```console | ||
| $ typeid explain user_01h45ytscbebyvny4gc8cr8ma2 | ||
| ``` | ||
| Outputs: | ||
| ```yaml | ||
| parsed: | ||
| prefix: user | ||
| uuid: 01890bf0-846f-7762-8605-5a3abb40e0e5 | ||
| created_at: 2025-03-12T10:41:23Z | ||
| sortable: true | ||
| ``` | ||
| Works **without schema**, fully offline. | ||
| ## Schema-based explanations | ||
| Define meaning for prefixes using JSON or YAML. | ||
| Example (`typeid.schema.json`): | ||
| ```json | ||
| { | ||
| "schema_version": 1, | ||
| "types": { | ||
| "user": { | ||
| "name": "User", | ||
| "owner_team": "identity-platform", | ||
| "pii": true | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| Then: | ||
| ```console | ||
| $ typeid explain user_01h45ytscbebyvny4gc8cr8ma2 | ||
| ``` | ||
| Read more here: ["Docs: Explain"](https://akhundmurad.github.io/typeid-python/performance/). | ||
| ## Design principles | ||
| * **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 | ||
| > Think of TypeID as | ||
| > **UUIDs + semantics + observability — without sacrificing speed** | ||
| ## License | ||
| MIT |
| 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 |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
0
-100%0
-100%0
-100%