CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-python-ulid

Universally unique lexicographically sortable identifier implementation for Python

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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}")
Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/python-ulid@3.1.x
Publish Source
CLI
Badge
tessl/pypi-python-ulid badge