Universally unique lexicographically sortable identifier implementation for Python
npx @tessl/cli install tessl/pypi-python-ulid@3.1.0A Python implementation of ULID (Universally Unique Lexicographically Sortable Identifier). ULIDs are 128-bit identifiers that combine the benefits of UUIDs with lexicographic sorting capabilities. They contain a 48-bit timestamp component and 80 bits of randomness, encoded as 26-character strings using Crockford's base32 for efficiency and readability.
pip install python-ulidfrom ulid import ULIDImport version information and other module components:
from ulid import __version__, ValueProvider, validate_typefrom ulid import ULID
from datetime import datetime
import time
# Generate a new ULID with current timestamp
ulid = ULID()
print(str(ulid)) # '01ARZ3NDEKTSV4RRFFQ69G5FAV'
# Create ULID from various input types
ulid_from_str = ULID.from_str('01ARZ3NDEKTSV4RRFFQ69G5FAV')
ulid_from_timestamp = ULID.from_timestamp(time.time())
ulid_from_datetime = ULID.from_datetime(datetime.now())
# Convert ULID to different formats
print(ulid.hex) # '0158a4c7bb6f5b48bc9b8b8a0b4d5e8f'
print(int(ulid)) # 1827391827391827391827391827391827391
print(ulid.timestamp) # 1588257207.56
print(ulid.datetime) # datetime.datetime(2020, 4, 30, 14, 33, 27, 560000, tzinfo=timezone.utc)
# Convert to UUID
uuid_obj = ulid.to_uuid()
uuid4_obj = ulid.to_uuid4() # RFC 4122 compliant UUIDv4The python-ulid package is built around these core components:
Create ULID instances from various input types and sources.
class ULID:
def __init__(self, value: bytes | None = None):
"""
Create a ULID instance.
Args:
value: 16-byte sequence representing an encoded ULID, or None to generate new
Raises:
ValueError: If value is not exactly 16 bytes
"""
@classmethod
def from_datetime(cls, value: datetime) -> Self:
"""
Create ULID from datetime object.
Args:
value: datetime object for timestamp component
Returns:
New ULID instance with timestamp from datetime, random component
"""
@classmethod
def from_timestamp(cls, value: float) -> Self:
"""
Create ULID from timestamp.
Args:
value: Timestamp as float (seconds) or int (milliseconds)
Returns:
New ULID instance with given timestamp, random component
"""
@classmethod
def from_uuid(cls, value: uuid.UUID) -> Self:
"""
Create ULID from UUID (timestamp becomes random).
Args:
value: UUID object to convert
Returns:
New ULID instance with UUID bytes, random timestamp
"""
@classmethod
def from_bytes(cls, bytes_: bytes) -> Self:
"""
Create ULID from 16-byte sequence.
Args:
bytes_: 16-byte sequence
Returns:
New ULID instance
"""
@classmethod
def from_hex(cls, value: str) -> Self:
"""
Create ULID from 32-character hex string.
Args:
value: 32-character hex string
Returns:
New ULID instance
"""
@classmethod
def from_str(cls, string: str) -> Self:
"""
Create ULID from 26-character base32 string.
Args:
string: 26-character base32 encoded ULID string
Returns:
New ULID instance
"""
@classmethod
def from_int(cls, value: int) -> Self:
"""
Create ULID from integer.
Args:
value: Integer representation of ULID
Returns:
New ULID instance
"""
@classmethod
def parse(cls, value: Any) -> Self:
"""
Create ULID from various input types with auto-detection.
Args:
value: Input value (ULID, UUID, str, int, float, datetime, bytes)
Returns:
New ULID instance
Raises:
ValueError: If string length is invalid
TypeError: If type cannot be parsed
"""Access timestamp information and convert ULIDs to different formats.
class ULID:
@functools.cached_property
def milliseconds(self) -> int:
"""Timestamp component as epoch milliseconds."""
@functools.cached_property
def timestamp(self) -> float:
"""Timestamp component as epoch seconds."""
@functools.cached_property
def datetime(self) -> datetime:
"""Timestamp as timezone-aware UTC datetime."""
@functools.cached_property
def hex(self) -> str:
"""32-character hex representation."""
bytes: bytes # Raw 16-byte representation
def __str__(self) -> str:
"""26-character base32 encoded string."""
def __int__(self) -> int:
"""Integer representation."""
def __bytes__(self) -> bytes:
"""Raw bytes representation."""
def to_uuid(self) -> uuid.UUID:
"""Convert to UUID object."""
def to_uuid4(self) -> uuid.UUID:
"""
Convert to RFC 4122 compliant UUIDv4.
Note: This is a destructive conversion - the resulting UUID
cannot be converted back to the same ULID.
"""Compare and hash ULID instances with various types.
class ULID:
def __lt__(self, other: Any) -> bool:
"""
Less-than comparison.
Args:
other: ULID, int, bytes, or str to compare against
Returns:
True if this ULID is less than other
"""
def __eq__(self, other: object) -> bool:
"""
Equality comparison.
Args:
other: Object to compare against
Returns:
True if equal (supports ULID, int, bytes, str)
"""
def __hash__(self) -> int:
"""Hash based on bytes representation."""Built-in support for Pydantic v2 data validation and serialization.
class ULID:
@classmethod
def __get_pydantic_core_schema__(
cls,
source: Any,
handler: GetCoreSchemaHandler
) -> CoreSchema:
"""Generate Pydantic v2 core schema for ULID validation."""
@classmethod
def _pydantic_validate(
cls,
value: Any,
handler: ValidatorFunctionWrapHandler
) -> Any:
"""Pydantic validation handler for ULID instances."""Usage with Pydantic:
from pydantic import BaseModel
from ulid import ULID
class User(BaseModel):
id: ULID
name: str
# Automatically validates and converts various ULID formats
user = User(id="01ARZ3NDEKTSV4RRFFQ69G5FAV", name="Alice")
user = User(id=ULID(), name="Bob")Generate and inspect ULIDs from the command line using the ulid command.
# Show version information
ulid --version # or ulid -V
# Generate new ULID
ulid build
# Generate ULID from different sources
ulid build --from-timestamp 1588257207.56
ulid build --from-datetime "2020-04-30T14:33:27.560000+00:00"
ulid build --from-hex "0158a4c7bb6f5b48bc9b8b8a0b4d5e8f"
ulid build --from-str "01ARZ3NDEKTSV4RRFFQ69G5FAV"
ulid build --from-int 1827391827391827391827391827391827391
ulid build --from-uuid "01234567-89ab-cdef-0123-456789abcdef"
# Inspect ULID properties
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --timestamp # or --ts
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --datetime # or --dt
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --hex
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --int
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --uuid
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --uuid4
# Read ULID from stdin
echo "01ARZ3NDEKTSV4RRFFQ69G5FAV" | ulid show -Low-level base32 encoding and decoding functions using Crockford's base32 alphabet.
from ulid.base32 import (
encode, decode, encode_timestamp, encode_randomness,
decode_timestamp, decode_randomness, ENCODE, DECODE
)
def encode(binary: bytes) -> str:
"""
Encode 16-byte ULID to 26-character string.
Args:
binary: 16-byte ULID representation
Returns:
26-character base32 encoded string
Raises:
ValueError: If input is not exactly 16 bytes
"""
def decode(encoded: str) -> bytes:
"""
Decode 26-character string to 16-byte ULID.
Args:
encoded: 26-character base32 encoded string
Returns:
16-byte ULID representation
Raises:
ValueError: If string is wrong length or contains invalid characters
"""
def encode_timestamp(binary: bytes) -> str:
"""
Encode 6-byte timestamp to 10-character string.
Args:
binary: 6-byte timestamp representation
Returns:
10-character base32 encoded timestamp string
Raises:
ValueError: If input is not exactly 6 bytes
"""
def encode_randomness(binary: bytes) -> str:
"""
Encode 10-byte randomness to 16-character string.
Args:
binary: 10-byte randomness representation
Returns:
16-character base32 encoded randomness string
Raises:
ValueError: If input is not exactly 10 bytes
"""
def decode_timestamp(encoded: str) -> bytes:
"""
Decode 10-character timestamp string to 6 bytes.
Args:
encoded: 10-character base32 encoded timestamp string
Returns:
6-byte timestamp representation
Raises:
ValueError: If string is wrong length or timestamp value would overflow
"""
def decode_randomness(encoded: str) -> bytes:
"""
Decode 16-character randomness string to 10 bytes.
Args:
encoded: 16-character base32 encoded randomness string
Returns:
10-byte randomness representation
Raises:
ValueError: If string is wrong length
"""
ENCODE: str # "0123456789ABCDEFGHJKMNPQRSTVWXYZ" - Crockford's base32 alphabet
DECODE: Sequence[int] # 256-element lookup table for decoding base32 charactersPackage constants for ULID validation and configuration.
from ulid.constants import (
MILLISECS_IN_SECS, NANOSECS_IN_MILLISECS,
MIN_TIMESTAMP, MAX_TIMESTAMP,
MIN_RANDOMNESS, MAX_RANDOMNESS,
TIMESTAMP_LEN, RANDOMNESS_LEN, BYTES_LEN,
REPR_LEN, HEX_REPR_LEN, UUID_REPR_LEN, INT_REPR_LEN
)
# Time conversion constants
MILLISECS_IN_SECS: int = 1000
NANOSECS_IN_MILLISECS: int = 1000000
# Value boundaries
MIN_TIMESTAMP: int = 0
MAX_TIMESTAMP: int = 281474976710655 # 2**48 - 1
MIN_RANDOMNESS: bytes # 10 zero bytes
MAX_RANDOMNESS: bytes # 10 0xFF bytes
# Length constants
TIMESTAMP_LEN: int = 6 # Timestamp byte length
RANDOMNESS_LEN: int = 10 # Randomness byte length
BYTES_LEN: int = 16 # Total ULID byte length
# String representation lengths
TIMESTAMP_REPR_LEN: int = 10 # Timestamp string representation length
RANDOMNESS_REPR_LEN: int = 16 # Randomness string representation length
REPR_LEN: int = 26 # Base32 string length
HEX_REPR_LEN: int = 32 # Hex string length
UUID_REPR_LEN: int = 36 # UUID string length (with dashes)
INT_REPR_LEN: int = 37 # Integer string lengthfrom typing import Any, Generic, TypeVar
from datetime import datetime
import uuid
T = TypeVar("T", bound=type)
R = TypeVar("R")
class validate_type(Generic[T]):
"""Type validation decorator for methods."""
def __init__(self, *types: T) -> None: ...
def __call__(self, func: Callable[..., R]) -> Callable[..., R]: ...
class ValueProvider:
"""Thread-safe provider for timestamp and randomness values."""
lock: Lock # Threading lock for synchronization
prev_timestamp: int # Previous timestamp value for monotonic guarantees
prev_randomness: bytes # Previous randomness value for incremental generation
def __init__(self) -> None: ...
def timestamp(self, value: float | None = None) -> int: ...
def randomness(self) -> bytes: ...
def increment_bytes(self, value: bytes) -> bytes: ...The package raises standard Python exceptions for error conditions:
Common error scenarios:
from ulid import ULID
# ValueError examples
try:
ULID(b"too_short") # Not 16 bytes
except ValueError as e:
print(f"Invalid length: {e}")
try:
ULID.from_str("INVALID_LENGTH") # Not 26 characters
except ValueError as e:
print(f"Invalid string: {e}")
try:
ULID.from_timestamp(2**48) # Exceeds maximum timestamp
except ValueError as e:
print(f"Timestamp overflow: {e}")
# TypeError examples
try:
ULID.parse(object()) # Unsupported type
except TypeError as e:
print(f"Cannot parse: {e}")