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.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.1.3.tar.gz (136.7 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.1.3-cp39-abi3-win_amd64.whl (746.3 kB view details)

Uploaded CPython 3.9+Windows x86-64

salamander_db-0.1.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (917.7 kB view details)

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

salamander_db-0.1.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (909.3 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

salamander_db-0.1.3-cp39-abi3-macosx_11_0_arm64.whl (818.1 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

salamander_db-0.1.3-cp39-abi3-macosx_10_12_x86_64.whl (873.1 kB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: salamander_db-0.1.3.tar.gz
  • Upload date:
  • Size: 136.7 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.1.3.tar.gz
Algorithm Hash digest
SHA256 3b4e6b1390b9b04e44f4e66d4b41d7c392982b5a7bcfdb770f01658bf979be13
MD5 afbfd0c17df0715a601429ecdb297d37
BLAKE2b-256 cfeedbf6fe6ceb5a6e421e17a62c15720f54dd9c00660a7ad887870e7a8db097

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.1.3.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.1.3-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: salamander_db-0.1.3-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 746.3 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.1.3-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 a12c5d3336d10fa5cc10e07b60833fe65cfa3019233bd0f6134f579d858c739f
MD5 adcbecdb47486a5ddc818382e0ba9078
BLAKE2b-256 56911aa27e94b5cfd6b1fea558a2a093f9cff08ac4abc9adb52085aa5a640a39

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.1.3-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.1.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for salamander_db-0.1.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 6ed7bba0e62993e06415945cd361cdd6fc4066495fd48eb1a43509b0cc8f5748
MD5 9b96615b4f1227585a2cd327a9a97370
BLAKE2b-256 c1d81e1d79dc35290764c498cfb82461ce2e0b3165011ec92db1d289757de851

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.1.3-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.1.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for salamander_db-0.1.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 a8011204638661942082882cc99c0392a3e96df3592cb33a0f848b00b9d92e92
MD5 994394fa068bb7ab19e8729aee611b59
BLAKE2b-256 4f6e88657ed6c72d3966474a6aec199296d8023d73abef8863c445a137980d80

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.1.3-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.1.3-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for salamander_db-0.1.3-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b41dcc1189487e8acd7c0c0edbebbfd0a7306d7884fd08fbeecaac87c0f3361f
MD5 9c22386ad81d8783a695d46192dd565f
BLAKE2b-256 bca27ab6bb61a3fa6782e78cd76b9d738eebc6d97dce8c344c94886268a49f2a

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.1.3-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.1.3-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for salamander_db-0.1.3-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 88c4b5faf4b390828419d322fbfbbc4f2e72a23b7fded5b05379cbff6f38773b
MD5 0a071a93fae527830153c9882d09d941
BLAKE2b-256 b0c5d81d7f00ea07bb02a06b003969b2523e7c22c1254138e4a66dd5835c2292

See more details on using hashes here.

Provenance

The following attestation bundles were made for salamander_db-0.1.3-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