Skip to main content

NimbusKV v3.1.0

PyPI Python Versions License: Apache 2.0 Tests Free-threading ready

Reactive, TTL-aware, concurrent key/value store for Python - lock-free reads always, one short lock per write, correct on both standard GIL Python and free-threaded Python (3.13t/3.14t).

Free-threaded Python note: NimbusKV depends on immutables, whose C extension has not yet declared free-threading support, so importing it re-enables the GIL on 3.13t/3.14t. Run with PYTHON_GIL=0 (or python -X gil=0) to keep the GIL off. The NimbusKV test suite passes in that mode.

Renamed from livedict. If you're upgrading: pip uninstall livedict && pip install nimbuskv, then change from livedict import LiveDict to from nimbuskv import NimbusKV. The API is otherwise unchanged.


What changed in v3 (rewrite, not a patch)

v2 was a TTL dict with callbacks and a fake "sandbox" - a recombination of things cachetools/aiocache already do better. v3 is a different architecture:

  • Immutable-snapshot core - state lives as an immutable persistent snapshot (HAMT). Readers never lock, ever. Each write takes one short lock, builds the new snapshot from the current one and publishes it. (Up to v3.0.1 writers retried on conflict instead; under a hot key that turned into a retry storm, so it was replaced.)
  • Adapts to the interpreter automatically - same code path on standard GIL Python and free-threaded Python (3.13t/3.14t). No mode flag; detected once at import time.
  • Genuinely reactive - subscribe() pushes every change (set/delete/expire) to callbacks, per-key or global.
  • Real async, not fake async - for the in-memory path, aget/aset are truly non-blocking (no run_in_executor spent on work that was never I/O). When a persistence backend involves real I/O (SQLite/Redis), only that part is delegated to an executor.
  • atomic() replaces v2's manual lock()/unlock() for compound read-modify-write (e.g. counters). Your function runs exactly once, under the write lock, so keep it short and don't write to the same store from inside it.
  • Persistence is a write-through mirror, not the source of truth - Memory (default), SQLite, or Redis. Reads never touch the backend; it only exists for durability and startup rehydration.
  • mset()/mget() - batch multiple keys into a single atomic swap (readers never see a partial update across related keys), plus a convenience multi-key read.
  • wait_for() - block a thread until a condition on a key holds, driven by the subscription system, not polling. Built for producer/consumer coordination across threads.
  • Context manager support - with NimbusKV() as ld: guarantees the scheduler and backend are cleanly stopped, even on exception.
  • TTL scheduler bugfix - rescheduling a key's TTL now correctly invalidates the old deadline via a generation counter, instead of leaving a stale heap entry behind.
  • py.typed - ships as a properly typed package (PEP 561); type checkers (mypy/pyright) will pick up the annotations.
  • Sandbox dropped for this release. v2's "sandbox" was a timeout wrapper, not real isolation, and calling it a sandbox was misleading. Real isolation (subprocess/WASM-based) is planned for a later release instead of shipping something that doesn't do what its name claims.

Install

pip install nimbuskv
pip install "nimbuskv[redis]"   # only if you want the Redis backend

Works on Python 3.9+. For the lock-free write path, use a free-threaded build (3.13t/3.14t) - on standard GIL Python everything is still correct, just without the extra parallelism.


Quick usage

from nimbuskv import NimbusKV

ld = NimbusKV()  # in-memory, default
ld.set('foo', 123)
print(ld.get('foo'))  # 123

TTL

ld.set('temp', 'expire-me', ttl=2)

Reactive subscriptions

def on_change(key, event, old_value, new_value):
    print(key, event, old_value, '->', new_value)

sub = ld.subscribe(on_change)          # all keys
sub_foo = ld.subscribe(on_change, key='foo')  # just 'foo'

ld.set('foo', 1)     # -> foo set None -> 1
ld.delete('foo')     # -> foo delete 1 -> None
# TTL expiry fires its own 'expire' event, distinct from 'delete'

Atomic compound updates (replaces manual locking)

ld.set('counter', 0)
ld.atomic('counter', lambda v: v + 1)   # safe under concurrent writers, no lock held

Batch operations

ld.mset({'a': 1, 'b': 2})       # one atomic swap for both keys
ld.mget(['a', 'b', 'missing'])  # {'a': 1, 'b': 2, 'missing': None}

wait_for - thread coordination without polling

import threading

def consumer():
    result = ld.wait_for('job:status', lambda v: v == 'done', timeout=30)
    print('unblocked with:', result)

threading.Thread(target=consumer).start()
# ... elsewhere, another thread ...
ld.set('job:status', 'done')  # consumer's wait_for() returns right here

Context manager

with NimbusKV() as ld:
    ld.set('foo', 1)
# scheduler + backend cleanly stopped here, even if an exception was raised

Async (genuinely non-blocking for the in-memory path)

import asyncio
from nimbuskv import NimbusKV

async def main():
    ld = NimbusKV()
    await ld.aset('bar', 'hello')
    print(await ld.aget('bar'))

asyncio.run(main())

Persistence backends

ld = NimbusKV(backend='sqlite:/path/to/store.db')
# or
ld = NimbusKV(backend='redis://localhost:6379/0')

State is rehydrated from the backend on startup (already-expired keys are skipped).


Architecture

graph TD
    A[NimbusKV public API]
    A --> B[ReactiveStore]
    B --> B1["in-memory, immutable-snapshot core"]
    B --> B2["readers: lock-free, always"]
    B --> B3["writers: one short lock,<br/>build + publish new snapshot"]
    A --> C[ExpiryScheduler]
    C --> C1["background thread, min-heap,<br/>fires TTL expiry against the store"]
    A --> D[PersistenceBackend]
    D --> D1["write-through mirror + startup rehydration"]
    D --> D2["NullBackend / SQLiteBackend / RedisBackend"]

Status

v3.1.0 replaces the optimistic retry loop on the write path with a short lock and fixes several concurrency bugs (see CHANGELOG.md). Core, TTL, reactive subscriptions, atomic(), and all three persistence backends are implemented and tested for correctness (race-free under concurrent writers, verified with 10,000+ concurrent operations across 20 threads on standard Python - free-threaded benchmarks pending, to be published once run on a 3.14t interpreter). Real sandboxing (proper isolation, not a timeout wrapper) is planned for a future release.


Documentation

Full detailed docs, versioned:


Development / Contributing

git clone https://github.com/hemanshu03/NimbusKV.git
cd NimbusKV
pip install -e ".[dev]"
pytest tests/ -v

See CONTRIBUTING.md for guidelines, and the examples/ folder for runnable end-to-end scripts. Free-threaded (3.13t/3.14t) testing is run in CI, but if you have a free-threaded interpreter locally, running the suite there too is especially valuable - that's the runtime this library's core claim is actually about.


License

NimbusKV is licensed under the Apache License 2.0. You can use it freely, including in commercial and closed-source products. If you redistribute it, keep the LICENSE and NOTICE files.

License change: versions up to and including 3.0.1 were released under the GNU GPL v3.0. From 3.1.0 onwards, NimbusKV is Apache-2.0. Copies of 3.0.1 and earlier that you already have remain under the GPL v3.0.

Copyright 2026 Hemanshu Vaidya.


Support development

If NimbusKV is useful to you, consider supporting development: 👉 NimbusKV on GitHub

Metadata

Release files for nimbuskv 3.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nimbuskv 3.1.0
File Size Uploaded
nimbuskv-3.1.0.tar.gz 36.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nimbuskv 3.1.0
File Interpreter ABI Platform
nimbuskv-3.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 68.2 kB

Release files / nimbuskv-3.1.0.tar.gz

Download URL nimbuskv-3.1.0.tar.gz
Size 36.4 kB
Tags Source
SHA-256 checksum
How to use checksums
a0e9f2cf1cdff4311efdb611310b32d3294d72c1c4fa3cb922f9849d61cd92d7
BLAKE2b-256 checksum
How to use checksums
d82cb02d83e596ac9f950aff7ecbf184d1934b4e22ba87a8503017ec6e1c6132
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / nimbuskv-3.1.0-py3-none-any.whl

Download URL nimbuskv-3.1.0-py3-none-any.whl
Size 31.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
95a77ef4d5196836864c94ebb7c2dc1809d86f29bc2ad83c410e1312d43932cc
BLAKE2b-256 checksum
How to use checksums
8856f49fc670bdb697abd7617ee03cebb3e04c62495d28e9621ed49eb0a9cc6f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release history Release notifications | RSS feed

3.1.1

2 release files

This release

3.1.0 This release

2 release files

3.0.1

2 release files

3.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page