0
# Python ULID
1
2
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.
3
4
## Package Information
5
6
- **Package Name**: python-ulid
7
- **Package Type**: pypi
8
- **Language**: Python
9
- **Installation**: `pip install python-ulid`
10
11
## Core Imports
12
13
```python
14
from ulid import ULID
15
```
16
17
Import version information and other module components:
18
19
```python
20
from ulid import __version__, ValueProvider, validate_type
21
```
22
23
## Basic Usage
24
25
```python
26
from ulid import ULID
27
from datetime import datetime
28
import time
29
30
# Generate a new ULID with current timestamp
31
ulid = ULID()
32
print(str(ulid)) # '01ARZ3NDEKTSV4RRFFQ69G5FAV'
33
34
# Create ULID from various input types
35
ulid_from_str = ULID.from_str('01ARZ3NDEKTSV4RRFFQ69G5FAV')
36
ulid_from_timestamp = ULID.from_timestamp(time.time())
37
ulid_from_datetime = ULID.from_datetime(datetime.now())
38
39
# Convert ULID to different formats
40
print(ulid.hex) # '0158a4c7bb6f5b48bc9b8b8a0b4d5e8f'
41
print(int(ulid)) # 1827391827391827391827391827391827391
42
print(ulid.timestamp) # 1588257207.56
43
print(ulid.datetime) # datetime.datetime(2020, 4, 30, 14, 33, 27, 560000, tzinfo=timezone.utc)
44
45
# Convert to UUID
46
uuid_obj = ulid.to_uuid()
47
uuid4_obj = ulid.to_uuid4() # RFC 4122 compliant UUIDv4
48
```
49
50
## Architecture
51
52
The python-ulid package is built around these core components:
53
54
- **ULID Class**: Main identifier class providing creation, conversion, and comparison methods
55
- **ValueProvider**: Thread-safe timestamp and randomness generation with monotonic guarantees
56
- **Base32 Encoding**: Crockford's base32 implementation for efficient string representation
57
- **Constants**: Timing, length, and boundary constants for ULID validation
58
- **CLI Interface**: Command-line tool for ULID generation and inspection
59
- **Pydantic Integration**: Built-in support for Pydantic v2 data validation
60
61
## Capabilities
62
63
### ULID Creation
64
65
Create ULID instances from various input types and sources.
66
67
```python { .api }
68
class ULID:
69
def __init__(self, value: bytes | None = None):
70
"""
71
Create a ULID instance.
72
73
Args:
74
value: 16-byte sequence representing an encoded ULID, or None to generate new
75
76
Raises:
77
ValueError: If value is not exactly 16 bytes
78
"""
79
80
@classmethod
81
def from_datetime(cls, value: datetime) -> Self:
82
"""
83
Create ULID from datetime object.
84
85
Args:
86
value: datetime object for timestamp component
87
88
Returns:
89
New ULID instance with timestamp from datetime, random component
90
"""
91
92
@classmethod
93
def from_timestamp(cls, value: float) -> Self:
94
"""
95
Create ULID from timestamp.
96
97
Args:
98
value: Timestamp as float (seconds) or int (milliseconds)
99
100
Returns:
101
New ULID instance with given timestamp, random component
102
"""
103
104
@classmethod
105
def from_uuid(cls, value: uuid.UUID) -> Self:
106
"""
107
Create ULID from UUID (timestamp becomes random).
108
109
Args:
110
value: UUID object to convert
111
112
Returns:
113
New ULID instance with UUID bytes, random timestamp
114
"""
115
116
@classmethod
117
def from_bytes(cls, bytes_: bytes) -> Self:
118
"""
119
Create ULID from 16-byte sequence.
120
121
Args:
122
bytes_: 16-byte sequence
123
124
Returns:
125
New ULID instance
126
"""
127
128
@classmethod
129
def from_hex(cls, value: str) -> Self:
130
"""
131
Create ULID from 32-character hex string.
132
133
Args:
134
value: 32-character hex string
135
136
Returns:
137
New ULID instance
138
"""
139
140
@classmethod
141
def from_str(cls, string: str) -> Self:
142
"""
143
Create ULID from 26-character base32 string.
144
145
Args:
146
string: 26-character base32 encoded ULID string
147
148
Returns:
149
New ULID instance
150
"""
151
152
@classmethod
153
def from_int(cls, value: int) -> Self:
154
"""
155
Create ULID from integer.
156
157
Args:
158
value: Integer representation of ULID
159
160
Returns:
161
New ULID instance
162
"""
163
164
@classmethod
165
def parse(cls, value: Any) -> Self:
166
"""
167
Create ULID from various input types with auto-detection.
168
169
Args:
170
value: Input value (ULID, UUID, str, int, float, datetime, bytes)
171
172
Returns:
173
New ULID instance
174
175
Raises:
176
ValueError: If string length is invalid
177
TypeError: If type cannot be parsed
178
"""
179
```
180
181
### ULID Properties and Conversion
182
183
Access timestamp information and convert ULIDs to different formats.
184
185
```python { .api }
186
class ULID:
187
@functools.cached_property
188
def milliseconds(self) -> int:
189
"""Timestamp component as epoch milliseconds."""
190
191
@functools.cached_property
192
def timestamp(self) -> float:
193
"""Timestamp component as epoch seconds."""
194
195
@functools.cached_property
196
def datetime(self) -> datetime:
197
"""Timestamp as timezone-aware UTC datetime."""
198
199
@functools.cached_property
200
def hex(self) -> str:
201
"""32-character hex representation."""
202
203
bytes: bytes # Raw 16-byte representation
204
205
def __str__(self) -> str:
206
"""26-character base32 encoded string."""
207
208
def __int__(self) -> int:
209
"""Integer representation."""
210
211
def __bytes__(self) -> bytes:
212
"""Raw bytes representation."""
213
214
def to_uuid(self) -> uuid.UUID:
215
"""Convert to UUID object."""
216
217
def to_uuid4(self) -> uuid.UUID:
218
"""
219
Convert to RFC 4122 compliant UUIDv4.
220
221
Note: This is a destructive conversion - the resulting UUID
222
cannot be converted back to the same ULID.
223
"""
224
```
225
226
### ULID Comparison and Hashing
227
228
Compare and hash ULID instances with various types.
229
230
```python { .api }
231
class ULID:
232
def __lt__(self, other: Any) -> bool:
233
"""
234
Less-than comparison.
235
236
Args:
237
other: ULID, int, bytes, or str to compare against
238
239
Returns:
240
True if this ULID is less than other
241
"""
242
243
def __eq__(self, other: object) -> bool:
244
"""
245
Equality comparison.
246
247
Args:
248
other: Object to compare against
249
250
Returns:
251
True if equal (supports ULID, int, bytes, str)
252
"""
253
254
def __hash__(self) -> int:
255
"""Hash based on bytes representation."""
256
```
257
258
### Pydantic Integration
259
260
Built-in support for Pydantic v2 data validation and serialization.
261
262
```python { .api }
263
class ULID:
264
@classmethod
265
def __get_pydantic_core_schema__(
266
cls,
267
source: Any,
268
handler: GetCoreSchemaHandler
269
) -> CoreSchema:
270
"""Generate Pydantic v2 core schema for ULID validation."""
271
272
@classmethod
273
def _pydantic_validate(
274
cls,
275
value: Any,
276
handler: ValidatorFunctionWrapHandler
277
) -> Any:
278
"""Pydantic validation handler for ULID instances."""
279
```
280
281
Usage with Pydantic:
282
283
```python
284
from pydantic import BaseModel
285
from ulid import ULID
286
287
class User(BaseModel):
288
id: ULID
289
name: str
290
291
# Automatically validates and converts various ULID formats
292
user = User(id="01ARZ3NDEKTSV4RRFFQ69G5FAV", name="Alice")
293
user = User(id=ULID(), name="Bob")
294
```
295
296
### Command Line Interface
297
298
Generate and inspect ULIDs from the command line using the `ulid` command.
299
300
```bash { .api }
301
# Show version information
302
ulid --version # or ulid -V
303
304
# Generate new ULID
305
ulid build
306
307
# Generate ULID from different sources
308
ulid build --from-timestamp 1588257207.56
309
ulid build --from-datetime "2020-04-30T14:33:27.560000+00:00"
310
ulid build --from-hex "0158a4c7bb6f5b48bc9b8b8a0b4d5e8f"
311
ulid build --from-str "01ARZ3NDEKTSV4RRFFQ69G5FAV"
312
ulid build --from-int 1827391827391827391827391827391827391
313
ulid build --from-uuid "01234567-89ab-cdef-0123-456789abcdef"
314
315
# Inspect ULID properties
316
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV
317
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --timestamp # or --ts
318
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --datetime # or --dt
319
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --hex
320
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --int
321
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --uuid
322
ulid show 01ARZ3NDEKTSV4RRFFQ69G5FAV --uuid4
323
324
# Read ULID from stdin
325
echo "01ARZ3NDEKTSV4RRFFQ69G5FAV" | ulid show -
326
```
327
328
### Base32 Encoding Utilities
329
330
Low-level base32 encoding and decoding functions using Crockford's base32 alphabet.
331
332
```python { .api }
333
from ulid.base32 import (
334
encode, decode, encode_timestamp, encode_randomness,
335
decode_timestamp, decode_randomness, ENCODE, DECODE
336
)
337
338
def encode(binary: bytes) -> str:
339
"""
340
Encode 16-byte ULID to 26-character string.
341
342
Args:
343
binary: 16-byte ULID representation
344
345
Returns:
346
26-character base32 encoded string
347
348
Raises:
349
ValueError: If input is not exactly 16 bytes
350
"""
351
352
def decode(encoded: str) -> bytes:
353
"""
354
Decode 26-character string to 16-byte ULID.
355
356
Args:
357
encoded: 26-character base32 encoded string
358
359
Returns:
360
16-byte ULID representation
361
362
Raises:
363
ValueError: If string is wrong length or contains invalid characters
364
"""
365
366
def encode_timestamp(binary: bytes) -> str:
367
"""
368
Encode 6-byte timestamp to 10-character string.
369
370
Args:
371
binary: 6-byte timestamp representation
372
373
Returns:
374
10-character base32 encoded timestamp string
375
376
Raises:
377
ValueError: If input is not exactly 6 bytes
378
"""
379
380
def encode_randomness(binary: bytes) -> str:
381
"""
382
Encode 10-byte randomness to 16-character string.
383
384
Args:
385
binary: 10-byte randomness representation
386
387
Returns:
388
16-character base32 encoded randomness string
389
390
Raises:
391
ValueError: If input is not exactly 10 bytes
392
"""
393
394
def decode_timestamp(encoded: str) -> bytes:
395
"""
396
Decode 10-character timestamp string to 6 bytes.
397
398
Args:
399
encoded: 10-character base32 encoded timestamp string
400
401
Returns:
402
6-byte timestamp representation
403
404
Raises:
405
ValueError: If string is wrong length or timestamp value would overflow
406
"""
407
408
def decode_randomness(encoded: str) -> bytes:
409
"""
410
Decode 16-character randomness string to 10 bytes.
411
412
Args:
413
encoded: 16-character base32 encoded randomness string
414
415
Returns:
416
10-byte randomness representation
417
418
Raises:
419
ValueError: If string is wrong length
420
"""
421
422
ENCODE: str # "0123456789ABCDEFGHJKMNPQRSTVWXYZ" - Crockford's base32 alphabet
423
DECODE: Sequence[int] # 256-element lookup table for decoding base32 characters
424
```
425
426
### Constants and Configuration
427
428
Package constants for ULID validation and configuration.
429
430
```python { .api }
431
from ulid.constants import (
432
MILLISECS_IN_SECS, NANOSECS_IN_MILLISECS,
433
MIN_TIMESTAMP, MAX_TIMESTAMP,
434
MIN_RANDOMNESS, MAX_RANDOMNESS,
435
TIMESTAMP_LEN, RANDOMNESS_LEN, BYTES_LEN,
436
REPR_LEN, HEX_REPR_LEN, UUID_REPR_LEN, INT_REPR_LEN
437
)
438
439
# Time conversion constants
440
MILLISECS_IN_SECS: int = 1000
441
NANOSECS_IN_MILLISECS: int = 1000000
442
443
# Value boundaries
444
MIN_TIMESTAMP: int = 0
445
MAX_TIMESTAMP: int = 281474976710655 # 2**48 - 1
446
MIN_RANDOMNESS: bytes # 10 zero bytes
447
MAX_RANDOMNESS: bytes # 10 0xFF bytes
448
449
# Length constants
450
TIMESTAMP_LEN: int = 6 # Timestamp byte length
451
RANDOMNESS_LEN: int = 10 # Randomness byte length
452
BYTES_LEN: int = 16 # Total ULID byte length
453
454
# String representation lengths
455
TIMESTAMP_REPR_LEN: int = 10 # Timestamp string representation length
456
RANDOMNESS_REPR_LEN: int = 16 # Randomness string representation length
457
REPR_LEN: int = 26 # Base32 string length
458
HEX_REPR_LEN: int = 32 # Hex string length
459
UUID_REPR_LEN: int = 36 # UUID string length (with dashes)
460
INT_REPR_LEN: int = 37 # Integer string length
461
```
462
463
## Types
464
465
```python { .api }
466
from typing import Any, Generic, TypeVar
467
from datetime import datetime
468
import uuid
469
470
T = TypeVar("T", bound=type)
471
R = TypeVar("R")
472
473
class validate_type(Generic[T]):
474
"""Type validation decorator for methods."""
475
def __init__(self, *types: T) -> None: ...
476
def __call__(self, func: Callable[..., R]) -> Callable[..., R]: ...
477
478
class ValueProvider:
479
"""Thread-safe provider for timestamp and randomness values."""
480
lock: Lock # Threading lock for synchronization
481
prev_timestamp: int # Previous timestamp value for monotonic guarantees
482
prev_randomness: bytes # Previous randomness value for incremental generation
483
484
def __init__(self) -> None: ...
485
def timestamp(self, value: float | None = None) -> int: ...
486
def randomness(self) -> bytes: ...
487
def increment_bytes(self, value: bytes) -> bytes: ...
488
```
489
490
## Error Handling
491
492
The package raises standard Python exceptions for error conditions:
493
494
- **ValueError**: Invalid input values, format errors, overflow conditions, exhausted randomness
495
- **TypeError**: Type validation failures, unsupported conversion types
496
- **PydanticCustomError**: Pydantic validation errors (when using Pydantic integration)
497
498
Common error scenarios:
499
500
```python
501
from ulid import ULID
502
503
# ValueError examples
504
try:
505
ULID(b"too_short") # Not 16 bytes
506
except ValueError as e:
507
print(f"Invalid length: {e}")
508
509
try:
510
ULID.from_str("INVALID_LENGTH") # Not 26 characters
511
except ValueError as e:
512
print(f"Invalid string: {e}")
513
514
try:
515
ULID.from_timestamp(2**48) # Exceeds maximum timestamp
516
except ValueError as e:
517
print(f"Timestamp overflow: {e}")
518
519
# TypeError examples
520
try:
521
ULID.parse(object()) # Unsupported type
522
except TypeError as e:
523
print(f"Cannot parse: {e}")
524
```