Durable backend correctness for single-machine Python on one SQLite file: transactional outbox, exactly-once jobs, forward-only migrations, snapshots. Zero dependencies.
Project description
ballast
Durable backend correctness for single-machine Python — on one SQLite file, with zero dependencies.
The guarantees you'd normally stand up Redis + Postgres + a broker for, delivered against one WAL-mode SQLite database with no server and nothing to operate:
- Transactional outbox — publish events inside your own transaction, so an event exists if and only if the work that produced it committed. No phantom events, ever.
- Exactly-once jobs — a background worker runs a handler and marks the job done in the same transaction. A crash rolls back the side effects and the completion together; the job retries. No double-sends, no lost work.
- Forward-only migrations — a tiny ladder that applies pending steps atomically and refuses to run against a newer database (the install-over-install downgrade guard).
- Consistent snapshots & restore —
VACUUM INTObackups with rotation, and a guarded restore to roll back. - OS-keyring secrets — read from the keyring when present, fall back to env vars always.
Built for the deployments that Temporal, Celery, and cloud queues structurally can't serve: desktop apps, on-prem appliances, air-gapped / regulated environments, and CLIs that must be crash-safe on a single box.
Why
Getting durable execution right on SQLite is a minefield most teams cross badly: writer serialization, BEGIN IMMEDIATE, the WAL pragma regime, the ambiguous-send problem after a crash, phantom events on rollback, idempotency, startup recovery of leased jobs. The usual escape hatch — run Redis/Postgres/Kafka — is impossible to ship to a customer's laptop or an air-gapped box, and absurd overkill for one process.
ballast packages the crash-tested version of all of that behind a small, boring API. It is deliberately dependency-free (standard library only), so there is nothing to audit but the code and it runs anywhere Python does.
Install
pip install ballast.py # core: zero dependencies
pip install "ballast.py[keyring]" # optional: OS-keyring-backed secrets
The PyPI distribution is named
ballast.py(the plainballastname belongs to an unrelated load-balancing library). The import name is unaffected:import ballast.
Requires Python 3.10+.
Quick start
from ballast import Database, EventBus, JobWorker, install
db = Database("app.db")
install(db) # create ballast's tables (idempotent)
bus = EventBus()
bus.declare("order.placed")
bus.subscribe("fulfilment", "order.placed",
lambda conn, e: conn.execute("INSERT INTO orders VALUES (?)", (e.entity_id,)))
with db.transaction() as conn: # publish atomically with your own writes
bus.publish(conn, "order.placed", {"total": 4999}, entity_type="order", entity_id="ord-1")
JobWorker(db, bus=bus).drain() # process to quiescence (or .start() in the background)
See examples/quickstart.py and examples/durable_jobs.py.
What you get
| Component | What it does |
|---|---|
Database |
Correctly-configured connections + a transaction() (BEGIN IMMEDIATE → commit/rollback). |
MigrationRunner / Migration |
Forward-only ladder; atomic per-step; DowngradeError on a newer DB. |
snapshot() / restore() |
VACUUM INTO backups with keep-N rotation; guarded restore. |
EventBus |
Declare topics, publish() inside your transaction, register subscribers. |
dispatch_pending / prune_events |
Advance cursors + enqueue dispatch jobs; prune consumed + aged events. |
JobQueue / JobWorker |
Transactional enqueue/enqueue_unique; single-writer worker; exactly-once, retry+backoff, startup recovery. |
SecretStore |
Keyring-first, env-var fallback secrets. |
Design in one paragraph
Everything hangs off two ideas. First, BEGIN IMMEDIATE on every write (ballast runs SQLite in autocommit and issues transactions explicitly) so busy_timeout actually applies and writers queue instead of erroring. Second, the outbox and the job-completion live in the caller's / worker's transaction, so "publish the event" and "do the work" and "mark it done" are atomic with the state change. Ordering is by an AUTOINCREMENT seq (commit order under serialized writers), never by a random id. Full rationale in docs/ARCHITECTURE.md.
Documentation
- Architecture & design decisions
- API reference
- FAQ & troubleshooting
- Roadmap · Changelog
- Contributing · Security policy · Code of conduct
Scope & non-goals
ballast is single-process, single-machine by design — that constraint is what makes the guarantees cheap and true. It is not a distributed queue, not a multi-writer cluster, and not a replacement for Temporal at scale. If you can run a broker and need multi-node fan-out, use one. If you must ship crash-safe durability inside one binary with no infrastructure, that is exactly what this is for.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ballast_py-0.1.1.tar.gz.
File metadata
- Download URL: ballast_py-0.1.1.tar.gz
- Upload date:
- Size: 28.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51daac704977e4a55c6b41a8bc4c64b387757208aabd44b9d6a116b598a6b470
|
|
| MD5 |
e4b774e717bd89b5f0138d46ab6cbab3
|
|
| BLAKE2b-256 |
a63ed7b1bbeb3e54a7e7eaf310a6233279c334e2219c213e8e6724794a5adab5
|
Provenance
The following attestation bundles were made for ballast_py-0.1.1.tar.gz:
Publisher:
release.yml on Vibecoder012/ballast
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ballast_py-0.1.1.tar.gz -
Subject digest:
51daac704977e4a55c6b41a8bc4c64b387757208aabd44b9d6a116b598a6b470 - Sigstore transparency entry: 2107006088
- Sigstore integration time:
-
Permalink:
Vibecoder012/ballast@5dcb5779b3556b892d31a6fd8227a78f063803fa -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Vibecoder012
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5dcb5779b3556b892d31a6fd8227a78f063803fa -
Trigger Event:
push
-
Statement type:
File details
Details for the file ballast_py-0.1.1-py3-none-any.whl.
File metadata
- Download URL: ballast_py-0.1.1-py3-none-any.whl
- Upload date:
- Size: 27.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
188cf57f397c6c873935e01fc2c33b3e8bdd595e4fb2d72429da9c7105489b41
|
|
| MD5 |
da652d3db3170105ef4895ab66e1c45f
|
|
| BLAKE2b-256 |
f2c4afc9874d59cc4b3b6e3551df9fe8c92857e01c17034cd252b70bbe3477be
|
Provenance
The following attestation bundles were made for ballast_py-0.1.1-py3-none-any.whl:
Publisher:
release.yml on Vibecoder012/ballast
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ballast_py-0.1.1-py3-none-any.whl -
Subject digest:
188cf57f397c6c873935e01fc2c33b3e8bdd595e4fb2d72429da9c7105489b41 - Sigstore transparency entry: 2107006204
- Sigstore integration time:
-
Permalink:
Vibecoder012/ballast@5dcb5779b3556b892d31a6fd8227a78f063803fa -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Vibecoder012
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5dcb5779b3556b892d31a6fd8227a78f063803fa -
Trigger Event:
push
-
Statement type: