Skip to content

kakilangit/pynulid

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PyPI License

nulid

Nanosecond-Precision Universally Lexicographically Sortable Identifier (NULID) for Python

Python bindings for the nulid Rust crate, powered by PyO3.

A NULID is a 128-bit identifier with:

  • 68-bit nanosecond timestamp for precise chronological ordering
  • 60-bit cryptographically secure randomness for collision resistance
  • 26-character Crockford Base32 encoding that is URL-safe and lexicographically sortable
  • UUID-compatible size (16 bytes)

Installation

pip install nulid

A Rust toolchain (1.88+) is required to build from source. Pre-built wheels are available for common platforms.

Usage

Generating NULIDs

import nulid

# As a 26-character Base32 string
id = nulid.generate()
# => "01AN4Z07BY79K47PAZ7R9SZK18"

# As a 16-byte binary
raw = nulid.generate_bytes()
# => b'\x01\xan...'

Encoding and Decoding

raw = nulid.generate_bytes()

# Bytes -> String
encoded = nulid.encode(raw)

# String -> Bytes
decoded = nulid.decode(encoded)
assert raw == decoded

Inspecting Components

raw = nulid.generate_bytes()

ns = nulid.nanos(raw)    # nanoseconds since epoch
ms = nulid.millis(raw)   # milliseconds since epoch
rand = nulid.random(raw) # 60-bit random value
assert not nulid.is_nil(raw)

assert nulid.is_nil(b"\x00" * 16)

Monotonic Generator

For guaranteed strictly increasing IDs, even within the same nanosecond:

gen = nulid.Generator()

id1 = gen.generate()
id2 = gen.generate()
id3 = gen.generate()

assert id1 < id2 < id3

Binary output:

gen = nulid.Generator()

b1 = gen.generate_bytes()
b2 = gen.generate_bytes()

assert b1 < b2

Distributed Generation

For multi-node deployments, assign each node a unique ID (0-65535):

gen = nulid.DistributedGenerator(node_id=1)

id = gen.generate()
assert gen.node_id == 1

The node ID is embedded in the random bits, guaranteeing cross-node uniqueness even with identical timestamps.

Why NULID over ULID?

Feature ULID NULID
Total Bits 128 128
String Length 26 chars 26 chars
Timestamp Bits 48 (milliseconds) 68 (nanoseconds)
Randomness Bits 80 60
Time Precision 1 millisecond 1 nanosecond
Lifespan Until 10889 AD Until ~11326 AD

NULID trades 20 bits of randomness for 20 extra bits of timestamp precision, giving nanosecond-level ordering while still providing 1.15 quintillion unique IDs per nanosecond.

Development

# Install in development mode
make develop

# Run all CI checks (format, clippy, lint, test)
make ci

# Run tests only
make test

# Format code (Python + Rust)
make fmt

# Show all available targets
make help

License

MIT - see LICENSE for details.

About

Nanosecond-Precision Universally Lexicographically Sortable Identifier (NULID) for Python

Topics

Resources

License

Stars

Watchers

Forks

Contributors