Metadata-Version: 2.4
Name: flakeid
Version: 0.1.1
Summary: Flexible 64-bit Snowflake-style unique ID generation for Python
Project-URL: Homepage, https://gitlab.com/rabin97/flakeid
Project-URL: Documentation, https://gitlab.com/rabin97/flakeid#readme
Project-URL: Repository, https://gitlab.com/rabin97/flakeid
Project-URL: Bug Tracker, https://gitlab.com/rabin97/flakeid/issues
Author: Rabin Karmakar
License: MIT
License-File: LICENSE
Keywords: 64bit,distributed,id-generator,snowflake,twitter-snowflake,unique-id,uuid
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# flakeid

**Flexible 64-bit Snowflake-style unique ID generation for Python.**

Zero dependencies. Thread-safe. Works for a solo project or a large distributed system.

```
pip install flakeid
```

---

## How it works

Each ID is a **64-bit integer** whose bits encode *when* and *where* it was generated:

```
 63        22 21    17 16    12 11          0
┌──┬────────────┬───────┬───────┬────────────┐
│ 0│ timestamp  │  DC   │  node │  sequence  │
│1b│  41 bits   │ 5 bits│ 5 bits│  12 bits   │
└──┴────────────┴───────┴───────┴────────────┘
```

- **Sign bit** — always `0`, keeping IDs positive in every database and language.
- **Timestamp** — milliseconds since a custom epoch (default: 2020-01-01 UTC).
- **Datacenter / Machine IDs** — identify the node that generated the ID.
- **Sequence** — a counter that resets each millisecond, preventing collisions on the same node.

All four field widths are **configurable** — any field can be resized or removed entirely, as long as the four values sum to 63.

---

## Installation

```bash
pip install flakeid
```

Requires Python ≥ 3.9. No external dependencies.

---

## Quickstart

### Zero-config (smallest possible integration)

```python
import flakeid

uid = flakeid.next_id()        # → 123456789012345678  (one ID)
ids = flakeid.next_ids(100)    # → [...]               (bulk)
```

The default generator uses a minimal layout (no node identity) and is lazily initialised on first call.

---

### Custom generator — Twitter layout

```python
from flakeid import make_generator, PRESET_TWITTER

gen = make_generator(PRESET_TWITTER, datacenter_id=1, machine_id=7)
uid = gen.next_id()

# Inspect what's inside
print(gen.decode(uid))
# DecodedID(raw=..., generated_at='2025-03-15T10:22:01.123Z',
#           datacenter_id=1, machine_id=7, sequence=0)

# Check the theoretical limits of this config
print(gen.capacity())
# Capacity(years=69.7, nodes=1024, ids_per_sec=4,096,000)
```

---

### Built-in presets

| Preset                | Layout                           | Use case                           |
|-----------------------|----------------------------------|------------------------------------|
| `PRESET_TWITTER`      | 41 ts · 5 DC · 5 node · 12 seq   | Classic Twitter / Discord layout   |
| `PRESET_MINIMAL`      | 53 ts · 10 seq                   | Solo projects, scripts, single box |
| `PRESET_SINGLE_DC`    | 41 ts · 10 node · 12 seq         | One DC, many workers               |
| `PRESET_LARGE_CLUSTER`| 41 ts · 8 DC · 8 node · 6 seq    | 256 DCs × 256 machines             |
| `PRESET_HIGH_THROUGHPUT`| 41 ts · 3 DC · 3 node · 16 seq | Max IDs/ms, few nodes              |

```python
from flakeid import make_generator, PRESET_MINIMAL, PRESET_LARGE_CLUSTER

# By dict
gen = make_generator(PRESET_MINIMAL)

# By name string
gen = make_generator("large_cluster", datacenter_id=200, machine_id=100)
```

---

### Fully custom layout

Any four values that sum to 63 work:

```python
from flakeid import SnowflakeGenerator

gen = SnowflakeGenerator(
    timestamp_bits=41,
    datacenter_bits=8,   # up to 256 datacenters
    machine_bits=8,      # up to 256 machines per DC
    sequence_bits=6,     # 64 IDs/ms/node
    datacenter_id=200,
    machine_id=100,
)
uid = gen.next_id()
```

---

### Container / cloud deployment — read node IDs from env vars

```python
from flakeid import make_generator_from_env

# Reads FLAKEID_DATACENTER_ID and FLAKEID_MACHINE_ID from the environment.
# Falls back to 0 if the variables are not set.
gen = make_generator_from_env("twitter")
```

Set in your `Dockerfile` / `docker-compose.yml` / Kubernetes `env:` block:

```
FLAKEID_DATACENTER_ID=3
FLAKEID_MACHINE_ID=12
```

Custom variable names are supported:

```python
gen = make_generator_from_env(
    "twitter",
    datacenter_env="MY_APP_DC",
    machine_env="MY_APP_NODE",
)
```

---

### Infinite ID stream

```python
from flakeid import make_generator, PRESET_TWITTER

gen = make_generator(PRESET_TWITTER)

for uid in gen.stream():
    save_to_db(uid)
```

---

### Decoding an ID

```python
dec = gen.decode(uid)

dec.timestamp_ms    # Unix ms when it was generated
dec.generated_at    # ISO-8601 UTC string, e.g. '2025-03-15T10:22:01.123Z'
dec.datacenter_id   # 3
dec.machine_id      # 12
dec.sequence        # 0
```

---

### Thread safety

`SnowflakeGenerator` is thread-safe — a single instance can be shared across any number of threads.

```python
import threading
from flakeid import make_generator, PRESET_TWITTER

gen = make_generator(PRESET_TWITTER, machine_id=1)

def worker():
    for _ in range(10_000):
        uid = gen.next_id()
        save(uid)

threads = [threading.Thread(target=worker) for _ in range(16)]
for t in threads: t.start()
for t in threads: t.join()
# All 160,000 IDs are guaranteed unique
```

---

## Error handling

```python
from flakeid import ClockBackwardsError, TimestampOverflowError, EpochError

try:
    uid = gen.next_id()
except ClockBackwardsError:
    # System clock jumped backwards by > 10 ms — check NTP
    ...
except TimestampOverflowError:
    # timestamp bits exhausted — increase timestamp_bits or update epoch_ms
    ...
except EpochError:
    # epoch_ms is in the future relative to current time
    ...
```

---

## API reference

### `flakeid.next_id() → int`
Module-level shortcut. Zero-config unique ID.

### `flakeid.next_ids(count) → list[int]`
Module-level shortcut for bulk generation.

### `make_generator(preset=None, **overrides) → SnowflakeGenerator`
Build a generator from a preset dict/name plus optional overrides.

### `make_generator_from_env(preset=None, *, datacenter_env, machine_env, epoch_env, **overrides) → SnowflakeGenerator`
Build a generator whose node IDs come from environment variables.

### `SnowflakeGenerator`

| Method | Description |
|--------|-------------|
| `next_id()` | One unique ID |
| `next_ids(count)` | Many unique IDs, single lock |
| `stream()` | Infinite iterator |
| `decode(uid)` | Decompose an ID → `DecodedID` |
| `capacity()` | Limits of this config → `Capacity` |

---

## Publishing to PyPI

```bash
# 1. Install the package + dev test deps (src/ layout) and build tools
pip install -e ".[dev]" hatch twine

# 2. Run the tests
pytest

# 3. Build distributions
hatch build        # creates dist/flakeid-0.1.0.tar.gz and .whl

# 4. Upload to PyPI
twine upload dist/*
# Enter your PyPI username/password or API token when prompted.

# 5. Users can now install with:
# pip install flakeid
```

For a test run first, upload to [TestPyPI](https://test.pypi.org):

```bash
twine upload --repository testpypi dist/*
pip install --index-url https://test.pypi.org/simple/ flakeid
```

---

## License

MIT
