dura
dura is built to be tough as nails: a laptop that goes to sleep mid-script,
a process that gets OOM-killed mid-charge, a server that loses power
mid-order, a device that drops off the network for an hour - none of it
should be able to take your work down with it. Without a plan for that,
interrupted work either happens twice (a customer charged twice, a
duplicate email) or silently never resumes (a stuck order nobody notices
until a customer complains).
dura picks interrupted work back up exactly where it left off - never
redoing what already succeeded, never losing track of what's left - and
it earns that toughness by depending on nothing: no message broker, no
workflow server, no database cluster, not even a network connection.
Everything lives in one SQLite file inside your own process, so there is
nothing else that can go down, drift out of sync with reality, or need
its own on-call rotation. Fewer moving parts, fewer ways to break.
That's also what makes it the hands-on alternative to heavier setups like Edda or Absurd, both of which inspired it: wrap a script in a durable task in five minutes, then reuse the same primitives to grow it into a small daemon that shrugs off crashes, restarts, and dropped connections.
It's built for a single process, or a handful of independent ones: a script you want to be able to kill and re-run safely, a simple application that can't afford to fail outright, or a device that can't rely on networked resources at all (embedded hardware, intermittent connectivity, air-gapped environments) - and it's already running in production doing exactly that.
It's not built to coordinate work across many machines or services sharing one queue; see scope and alternatives for that boundary and what to reach for instead.
Full documentation: https://ericgazoni.github.io/dura/
Key features
- Depends on nothing - the entire engine is one SQLite file: no broker, server, or cluster to run alongside your app, and no network connection required at all. There's nothing else that can be down.
- Durable tasks and runs - a task is the logical job; each execution attempt is a run. Crashes reclaim in-flight runs automatically (leases expire and are picked back up) without consuming a retry, since a crash is not a logical failure.
- Retries with backoff -
none,fixed, orexponentialretry strategies with jitter, configured per task. - Checkpoints - durable memoization of a step's result, keyed to the task. A checkpointed step runs at most once, even across a crash and retry of the surrounding handler.
- Durable key-value state - a namespaced store for cross-task memory (cursors, watermarks, dedup records) that outlives the tasks that wrote it and is never touched by cleanup.
- Events and suspend/resume - a handler can wait on a named event with
an optional timeout; the run parks itself (freeing the worker) and is
woken by
emit_eventor by the timeout, without polling. - Priorities and lanes - tasks carry a priority; worker "lanes" can be restricted to claim only up to a given priority ceiling, so a backlog of low-priority work never starves latency-sensitive tasks.
- Idempotent enqueue -
spawn_task(..., idempotency_key=...)returns the existing task instead of creating a duplicate. - Graceful worker pool - a threaded pool with signal handling (SIGINT/SIGTERM), a bounded shutdown grace period, and clean WAL checkpointing on close.
- Health, no dependencies imposed - a
Heartbeatfor pool-wide liveness that opens no sockets and starts no threads; expose it however fits your app, and query the SQLite database directly for metrics.
Installation
pip install dura
dura has zero runtime dependencies.
Quick start
import time
from dataclasses import dataclass
from dura import DurableEngine, run_workers
@dataclass
class Order:
id: str
customer_id: str
amount_cents: int
def fetch_pending_orders():
# Stand-in for wherever your orders actually come from.
for n in range(1, 21):
yield Order(id=f"ord_{n}", customer_id=f"cust_{n}", amount_cents=1000 + n * 100)
def charge_card(*, customer_id, amount_cents, idempotency_key):
# Stand-in for a real payment gateway call.
time.sleep(1)
print(f"charged {customer_id} {amount_cents}c ({idempotency_key})")
def send_receipt(customer_id, order_id):
# Stand-in for a real email/notification call.
time.sleep(1)
print(f"receipt sent to {customer_id} for {order_id}")
def charge_order(engine, task):
engine.checkpoint(
task_id=task.task_id,
step_name="charge",
fn=lambda: charge_card(
customer_id=task.params["customer_id"],
amount_cents=task.params["amount_cents"],
idempotency_key=f"charge:{task.task_id}",
),
)
engine.checkpoint(
task_id=task.task_id,
step_name="receipt",
fn=lambda: send_receipt(task.params["customer_id"], task.params["order_id"]),
)
return {"charged": task.params["order_id"]}
engine = DurableEngine("engine.db")
for order in fetch_pending_orders():
engine.spawn_task(
name="charge_order",
params={"order_id": order.id, "customer_id": order.customer_id, "amount_cents": order.amount_cents},
idempotency_key=f"charge_order:{order.id}",
)
run_workers(engine, handlers={"charge_order": charge_order}, worker_count=4)
Save this as quickstart.py and run it with python quickstart.py. It
starts charging its way through 20 orders, four at a time; press
Ctrl+C partway through, well before it reaches the last one. dura
stops claiming new work, lets whatever's currently in flight finish, and
exits.
Run the script again: the orders that were already charged don't get
charged twice - idempotency_key on spawn_task skips re-queuing them,
and the charge checkpoint means a charge that already succeeded is
never retried - while the ones the batch hadn't reached yet pick up right
where it left off. Reach for kill -9 instead of Ctrl+C and it recovers
exactly the same way: dura doesn't depend on a graceful shutdown to
stay correct, only to be tidy about it.
Learn more
This README only covers the basics. See the full documentation:
- Tutorial: build a task, run it, crash it on purpose, watch it recover.
- How-to guides:
- API reference: the full
DurableEngine, worker pool, and heartbeat API. - Explanation: how the durability model works, and when
durais (and isn't) the right tool.
Development
uv sync
uv run pytest
Metadata
Release files for dura 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dura-1.0.0.tar.gz | 71.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dura-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 91.8 kB
Release files / dura-1.0.0.tar.gz
| Download URL | dura-1.0.0.tar.gz |
|---|---|
| Size | 71.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bcd342703456e87ac0fbb06cdf33eab555bab583a97435d806ab36261dfe5178
|
|
BLAKE2b-256 checksum How to use checksums |
c7ac36355f55059ed3e416706c0d8aa51cf214fe0a168e26003c58a8da06b09d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency logRelease files / dura-1.0.0-py3-none-any.whl
| Download URL | dura-1.0.0-py3-none-any.whl |
|---|---|
| Size | 20.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eeed2f79680e8039d5d3dd4daec24220ccdaa1c366b623ca1acb99b4c1f00c92
|
|
BLAKE2b-256 checksum How to use checksums |
15fd43f2cbf21c47902cf5d970ee32eb0b14965fba021ab69ff9cf85f411d21b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency log