or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/pypi-python-ulid

Universally unique lexicographically sortable identifier implementation for Python

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/python-ulid@3.1.x

To install, run

npx @tessl/cli install tessl/pypi-python-ulid@3.1.0

index.mddocs/

Python ULID

A 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.

Package Information

  • Package Name: python-ulid
  • Package Type: pypi
  • Language: Python
  • Installation: pip install python-ulid

Core Imports

from ulid import ULID

Import version information and other module components:

from ulid import __version__, ValueProvider, validate_type

Basic Usage

from 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 UUIDv4

Architecture

The python-ulid package is built around these core components:

  • ULID Class: Main identifier class providing creation, conversion, and comparison methods
  • ValueProvider: Thread-safe timestamp and randomness generation with monotonic guarantees
  • Base32 Encoding: Crockford's base32 implementation for efficient string representation
  • Constants: Timing, length, and boundary constants for ULID validation
  • CLI Interface: Command-line tool for ULID generation and inspection
  • Pydantic Integration: Built-in support for Pydantic v2 data validation

Capabilities

ULID Creation

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
        """

ULID Properties and Conversion

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.
        """

ULID Comparison and Hashing

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."""

Pydantic Integration

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")

Command Line Interface

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 -

Base32 Encoding Utilities

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 characters

Constants and Configuration

Package 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 length

Types

from 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: ...

Error Handling

The package raises standard Python exceptions for error conditions:

  • ValueError: Invalid input values, format errors, overflow conditions, exhausted randomness
  • TypeError: Type validation failures, unsupported conversion types
  • PydanticCustomError: Pydantic validation errors (when using Pydantic integration)

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}")