Skip to main content

dura

PyPI Documentation License

Long jobs get interrupted: a laptop sleeps, a process gets killed, a server loses power, a connection drops. Without a plan for that, the work either runs twice (a customer charged twice, a duplicate email) or silently stalls (a stuck order nobody notices until a customer complains).

dura picks that work back up exactly where it left off, without redoing anything that already succeeded. It needs nothing else to do that: no message broker, no workflow server, no database cluster, not even a network connection. Everything lives in one SQLite file. Fewer moving parts, fewer ways to break.

You can turn a script into a durable workflow in five minutes, or make a small app crash-proof with a few lines of code.

It is designed for workflows where interruptions or breaks are expected, but side-effects are a problem. It's already tested and running in production doing exactly that.

It's a lighter, hands-on alternative to setups like Edda and Absurd, both of which inspired served as inspiration. 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: one SQLite file. No broker, server, or cluster to run, and no network connection required.
  • Durable tasks and runs: a task is the job, each attempt is a run.
  • Retries with backoff: none, fixed, or exponential strategies with jitter, configured per task.
  • Checkpoints: memoize a step's result, keyed to the task. A checkpointed step runs at most once, even across a crash and retry.
  • Durable key-value state: a namespaced key-value store for cross-task memory (cursors, watermarks, dedup records).
  • Events and suspend/resume: a handler waits on a named event, with an optional timeout. The run parks itself and wakes on emit_event or the timeout, no polling.
  • Priorities and lanes: tasks carry a priority, and worker "lanes" can be restricted to a priority ceiling, so bulk work never starves urgent tasks.
  • Idempotent enqueue: spawn_task(..., idempotency_key=...) returns the existing task instead of creating a duplicate.
  • Graceful worker pool: signal handling (SIGINT/SIGTERM), a bounded shutdown grace period, and clean WAL checkpointing on close.
  • Health, no dependencies imposed: a Heartbeat for 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("quickstart.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=2)

Save this as quickstart.py and run it with python quickstart.py. It charges its way through 20 orders, two at a time. Press Ctrl+C partway through, before it reaches the last one. dura stops claiming new work, lets what's in flight finish, and exits.

Run the script again. The orders already charged don't get charged twice: idempotency_key skips re-queuing them, and the charge checkpoint means a successful charge is never retried. The orders the batch hadn't reached yet just pick up where it left off.

Note: a kill -9 instead of Ctrl+C recovers the same way. dura doesn't need a graceful shutdown to stay correct, only to be tidy about it.

Learn more

This README only covers the basics. See the full documentation:

Development

uv sync
uv run pytest

Metadata

Release files for dura 1.2.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 dura 1.2.0
File Size Uploaded
dura-1.2.0.tar.gz 79.1 kB Details

Built distribution (wheel)

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

Total release size: 99.8 kB

Release files / dura-1.2.0.tar.gz

Download URL dura-1.2.0.tar.gz
Size 79.1 kB
Tags Source
SHA-256 checksum
How to use checksums
67e2e6766d51ff5ac155ae68ceb83eb9aa221824d908f0953265d17e4f4e786d
BLAKE2b-256 checksum
How to use checksums
1fb1aac0095a319f14dd86f5b54eb8e63156f7754b6e1b627e1e89a9dc2ce53f
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 Sep 1, 2026.

Transparency log

Release files / dura-1.2.0-py3-none-any.whl

Download URL dura-1.2.0-py3-none-any.whl
Size 20.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
adec4836d2b088c32f8d0151cab23de698cbabbf4d3c43ba0d242ff9d3baa1eb
BLAKE2b-256 checksum
How to use checksums
78eba39fc335ac8392223ed7fea8dbc304a62fa887a7962bc939cd344e6a2b7c
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 Sep 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.0.0

2 release files

0.1.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