Skip to main content

SQLite for event-sourced state — Python bindings for salamander-db.

Project description

salamander-db (Python)

Native Python bindings for salamander-dbSQLite for event-sourced state. This is a compiled extension (via PyO3 + maturin), so it embeds the engine in your process exactly like Python's built-in sqlite3 module wraps libsqlite3: you open one handle and reuse it. No server, no subprocess.

The binding is a translation layer over the safe, non-generic Rust Engine. Database ownership and cross-thread sequencing live in the core; Python does not contain storage, replay, branching, or projection algorithms.

import salamander

with salamander.open("./mem", commit_every_count=8) as db:
    db.append("session-1", {"kind": "user_msg", "text": "hi"})
    db.append("session-1", {"kind": "tool_call", "tool": "grep", "args": {"q": "500"}})
    db.commit()

    for ev in db.replay("session-1"):
        print(ev["offset"], ev["body"])      # plain dicts, nested fields intact

The open handle owns the single-writer lock, any registered views, and the group-commit state — which is why it must be long-lived and in-process, not re-created per call. Use it as a context manager or call close() explicitly to release the lock deterministically.

The wheel includes a py.typed marker and salamander.pyi, so editors and static type checkers can discover the public API and common dictionary shapes. The runtime API is synchronous; its storage calls release the GIL, but asyncio applications should use asyncio.to_thread or a dedicated worker rather than block the event-loop thread. See the Python usage guide.

Install

pip install salamander-db

Prebuilt abi3 wheels cover CPython 3.9+ on Linux (x86_64, aarch64), macOS (Intel, Apple Silicon), and Windows (x86_64). The import name is salamander.

Build from source

Requires a Rust toolchain and Python ≥ 3.9.

python -m venv .venv && source .venv/bin/activate    # Windows: .venv\Scripts\activate
pip install maturin pytest
maturin develop -m salamander-py/Cargo.toml          # compiles + installs into the venv
python examples/py/quickstart.py
python -m pytest examples/py/test_roundtrip.py -v

On a very new Python that the pinned PyO3 doesn't recognize yet, prefix the build with PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 (the abi3 stable ABI is forward-compatible).

API

Python Engine
salamander.open(path, commit_every_bytes=, commit_every_count=, commit_every_millis=) JsonDb::open_with_policy
db.append(namespace, event: dict) -> int append (payload = JSON)
db.append_batch(namespace, events, ...) -> dict atomic batch append with concurrency, idempotency, and durability
db.append_branch(branch, namespace, event) -> int append to an engine branch
db.commit() -> int commit (fsync)
db.head() -> int head
db.uncommitted_count() -> int group-commit tally
db.retention_floor() -> int lowest position accepted by historical reads
db.retention_status(keep_from=None) -> dict read-only generation, floor, proposed progress, blockers, bootstrap readiness, handles, and cleanup state
db.plan_retention(keep_from) -> dict non-destructive floor/segment/blocker report
db.plan_retention_policy(policy, value) -> dict resolve keep_from, keep_latest_events, keep_newer_than (Unix nanoseconds), or target_log_bytes into the normal safe plan
db.create_retention_anchor(keep_from) -> dict publish a verified core anchor and promote registered-view checkpoints without deleting log data
db.apply_retention(plan_id) -> dict atomically commit a ready plan, advance the floor, and reclaim old closed segments
db.register_branch_bootstrap(branch, keep_from, checkpoint) -> int stage opaque branch state at the returned effective floor
db.register_consumer_bootstrap(consumer_id, keep_from, checkpoint) -> int stage opaque durable-consumer state at the returned effective floor
db.register_feed_bootstrap(consumer_id, keep_from, checkpoint, branch=, codec=, codec_version=) -> int stage a checkpoint bound to an exact feed scope and codec
db.fetch_feed_bootstrap(descriptor, maximum_bytes) -> bytes fetch and verify bounded checkpoint bytes from PositionUnavailableError.bootstrap
db.resume_watch(descriptor, ...) -> Watch resume the exact verified feed scope at the descriptor continuation
`db.branch_bootstrap(branch) -> bytes None`
`db.consumer_bootstrap(consumer_id) -> bytes None`
db.replay(namespace, start=0, end=None) -> list[dict] replay (literal)
db.register_view(name, key, indexes={}, where_field=, where_value=) register an IndexedView
db.view(name) -> View typed query handle
db.deregister_view(name) -> bool deregister
db.fork(namespace, at, parent=None) -> str create branch off parent (default: root timeline); forks of forks supported
db.diff(left, right, namespace=, left_until=, right_until=) -> dict divergence of two timelines: common ancestor, exact divergence offset, and three pre-scoped readers (shared prefix + both suffixes) — computed from the branch catalog, no records compared
db.watch(namespace=, branch=, start=, consumer_id=, timeout=) -> Watch blocking iterator over the committed-batch feed — tail -f for the log; yields events once durable
db.history(namespace) -> list[dict] default-branch replay
db.branch_history(branch, namespace) -> list[dict] inherited branch replay

View handles support .get(key), .by(index, key), .range(lo, hi), .prefix(p), .len().

Atomic batches and concurrency

append_batch exposes the engine's complete append contract. Every event is a descriptor with a required body and optional event_type, schema_version, metadata (byte strings or UTF-8 strings), and a 32-digit hexadecimal event_id:

receipt = db.append_batch(
    "orders",
    [
        {
            "body": {"order_id": "o-1", "state": "created"},
            "event_type": "order.created",
            "schema_version": 2,
            "metadata": {"trace_id": "trace-1"},
        },
        {
            "body": {"order_id": "o-1", "state": "paid"},
            "event_type": "order.paid",
        },
    ],
    expected_revision="no_stream",  # or "any" / an exact non-negative revision
    idempotency_key="create-o-1",   # bytes and UTF-8 strings are accepted
    durability="sync",              # "buffered" / "flush" / "sync"
)

The batch is visible all-or-nothing. Identical retries with the same idempotency key return the original receipt; conflicting reuse raises salamander.ConflictError and writes nothing. The other stable exception categories include InvalidArgumentError, NotFoundError, LockedError, IoError, CorruptionError, UnsupportedFormatError, CodecError, ResourceLimitError, and CancelledError. They retain compatibility with their former built-in bases (ValueError, KeyError, OSError, or RuntimeError).

Pass branch="branch-name" to append the batch to an existing branch. Optimistic revisions are branch-local: inherited history is replay-visible, but the first local write to a stream on a new child branch uses expected_revision="no_stream" (or "any"). Replay rows include event and batch IDs, batch index, branch id, namespace, stream revision, event type, schema version, codec, and metadata in addition to offset, timestamp_ms, and body.

Watching the log live

db.watch turns the engine's committed-batch feed into a blocking iterator — build dashboards and bots on events as they land, not just after the fact. Events are yielded only once durable (committed); the GIL is released while waiting, and Ctrl+C stays responsive.

watch = db.watch(namespace="jobs")        # live tail from the durable head
for ev in watch:                          # blocks until commits arrive
    handle(ev["body"])

db.watch(start=0)                         # full durable history, then follow
db.watch(branch="chat-fork-8")            # one timeline only
db.watch(timeout=5.0)                     # stop after 5 idle seconds

with db.watch(consumer_id="worker-1") as w:   # durable resume checkpoint
    for ev in w:
        process(ev)
        w.ack()          # a later watch with the same consumer_id resumes here

start=None (the default) tails from the durable head — or, with a consumer_id, resumes from its last acknowledged checkpoint. A timeout in seconds ends the iteration after that long without a matching event, which is also what makes watches testable.

Payloads are any JSON-able Python value (dict/list/str/int/float/ bool/None), converted to/from serde_json::Value at the boundary. Views are declared by field name (primary key, indexes mapping name→field, an optional where_field/where_value filter) — no per-event Python callback crosses the FFI boundary.

Demos ship in examples/py/: dungeon.py (a browser roguelike where rewind is a replay, dying is a fork, and a pull-the-plug button proves the save can't corrupt), chat.py (a chat CLI where rewind, fork, and timeline diff are storage primitives — Claude API when available, offline mock otherwise), unkillable_agent.py (an agent hard-killed mid-task that resumes from replay with exactly-once steps), and a LangGraph checkpointer that survives process restarts. An MCP server is on the roadmap. Branch lifecycle and ancestry use the same engine catalog as Rust:

ancestry = db.branch_ancestry(branch_name)  # root-first metadata dictionaries
archived = db.archive_branch(branch_name)  # history remains readable

For version changes and on-disk compatibility, follow the upgrade guide before opening a production directory with a newer release.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

salamander_db-0.2.0.tar.gz (154.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

salamander_db-0.2.0-cp39-abi3-win_amd64.whl (891.9 kB view details)

Uploaded CPython 3.9+Windows x86-64

salamander_db-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

salamander_db-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.1 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

salamander_db-0.2.0-cp39-abi3-macosx_11_0_arm64.whl (968.2 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

salamander_db-0.2.0-cp39-abi3-macosx_10_12_x86_64.whl (1.0 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file salamander_db-0.2.0.tar.gz.

File metadata

  • Download URL: salamander_db-0.2.0.tar.gz
  • Upload date:
  • Size: 154.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for salamander_db-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ec54b3df35514b5e5615ca5a6212b2d93939eed27b6d901b64989787bc01e7b6
MD5 f255917c5a3a876a4c2cebe45ecedf8e
BLAKE2b-256 bf51dc62f58834839fccf298da524ca4fdefafa6c2afc67caf21c63707918e55

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.2.0.tar.gz:

Publisher: release-python.yml on rdelprete/salamander-db

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file salamander_db-0.2.0-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: salamander_db-0.2.0-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 891.9 kB
  • Tags: CPython 3.9+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for salamander_db-0.2.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 26fd0b0fbb2cfcfbb85bafbf3bd4e087428050b750797cf010a8170caf94f88d
MD5 1ff5697020d7cfba7a966e521e4e5396
BLAKE2b-256 f95eadcac87f8d62bd020aa714dadbbda7242df2f4a209c56949f4951c89a87f

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.2.0-cp39-abi3-win_amd64.whl:

Publisher: release-python.yml on rdelprete/salamander-db

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file salamander_db-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for salamander_db-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 af5323ce98cb48281a0a7004a713efcf01a87a4a2c2996e05a7f43a196039177
MD5 64fd8f872faaef0ef8c3b9f86c20524c
BLAKE2b-256 34102b919cfa68d9ac8b004ddafe0764d33f68e0ded8e2e34d5894c0f819e67b

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release-python.yml on rdelprete/salamander-db

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file salamander_db-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for salamander_db-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 be091b46f0f01381c99cbed4a611981a283aebb94a7894ea732846abb596f058
MD5 300bfeab19ad499b6ae3a5c160308ae2
BLAKE2b-256 aae74831ede123bb11ebd301d04d61d2ab1244aa3a3f8e077f08f9285f11c246

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release-python.yml on rdelprete/salamander-db

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file salamander_db-0.2.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for salamander_db-0.2.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c3e0844075dac557160359bcbf0c8f2795fcb4bf1bc371e3673a9bdf47c557e6
MD5 c50eb5a8833b0d506f043ef4797ef207
BLAKE2b-256 4c8fa0bf1e3230302e40ce9e53e7e0e8e05fd0ebef9ac7d8f9f3f84f0ce1f5ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.2.0-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: release-python.yml on rdelprete/salamander-db

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file salamander_db-0.2.0-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for salamander_db-0.2.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 681557addc6f334f9b88ce1c8580bc7fe45366a14e4a2c0c9e31648383062c05
MD5 f4a2ecd59c633adc0f4d172121c8ff22
BLAKE2b-256 b97dcd2d4de3224cecff848105a9e9aabdd2b832c4d2c64d6370d2c4cb52ff35

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.2.0-cp39-abi3-macosx_10_12_x86_64.whl:

Publisher: release-python.yml on rdelprete/salamander-db

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page