Skip to main content

Distributed Durable Functions in Python

Project description

Stent

Distributed durable functions for Python. Write reliable, stateful workflows using async/await.

pip install stent

Quick Example

import asyncio
from stent import Stent

@Stent.durable
async def process_order(order_id: str) -> dict:
    await asyncio.sleep(1)  # Simulate work
    return {"order_id": order_id, "status": "processed"}

@Stent.durable
async def order_workflow(order_ids: list[str]) -> list:
    results = []
    for order_id in order_ids:
        result = await process_order(order_id)
        results.append(result)
    return results

async def main():
    backend = Stent.backends.SQLiteBackend("workflow.db")
    async with Stent.bootstrap(backend, serve=True) as executor:
        result = await order_workflow(["ORD-001", "ORD-002"])
        print(result)

asyncio.run(main())

Why Stent?

Feature Temporal Celery Prefect Airflow Stent
Durable Execution Yes No Partial No Yes
Setup Complexity High Medium Medium High Very Low
Infrastructure Server cluster Broker Server Multi-component SQLite/Postgres
Native Async Yes No Yes Limited Yes

Stent fills the gap between simple task queues (Celery) and enterprise platforms (Temporal):

  • vs Temporal: Same durability guarantees, fraction of the infrastructure
  • vs Celery/Dramatiq: True workflow durability, not just task retries
  • vs Prefect/Airflow: Application workflows, not batch data pipelines

See full comparison for details.

Features

  • Durable Execution - Workflow state survives crashes and restarts
  • Auto-dispatch - Durable functions work like normal await calls via Stent.use() or Stent.bootstrap()
  • Automatic Retries - Configurable retry policies with exponential backoff
  • Distributed Workers - Scale horizontally across multiple processes
  • Parallel Execution - Fan-out/fan-in with fn.map(), fn.starmap(), and asyncio.gather
  • Rate Limiting - Control concurrent executions per function
  • External Signals - Coordinate workflows with external events
  • Dead Letter Queue - Inspect and replay failed tasks
  • Cancellation - Cancel running or pending executions with executor.cancel()
  • Idempotency & Caching - Prevent duplicate work
  • Multiple Backends - SQLite (dev) or PostgreSQL (production)
  • Testing Fixtures - Built-in pytest fixtures via stent.testing
  • OpenTelemetry - Distributed tracing support

Key Concepts

from stent import Stent, RetryPolicy

# Decorator works with or without parens
@Stent.durable
async def my_activity(data: dict) -> dict:
    ...

# Configurable options
@Stent.durable(
    retry_policy=RetryPolicy(max_attempts=5, initial_delay=1.0),
    queue="high_priority",
    max_concurrent=10,
    idempotent=True,
)
async def my_configured_activity(data: dict) -> dict:
    ...

# Durable sleep (doesn't block workers) — asyncio.sleep >= 1s auto-promotes
await asyncio.sleep(30)  # becomes durable inside execution context

# Parallel execution via .map()
results = await my_activity.map([item1, item2, item3])

# External signals
payload = await Stent.wait_for_signal("approval")
await executor.send_signal(exec_id, "approval", {"approved": True})

# Cancellation
await executor.cancel(exec_id)

Setup

# One-liner with bootstrap (recommended)
async with Stent.bootstrap(backend, serve=True) as executor:
    result = await my_workflow()

# Or manual setup
backend = Stent.backends.SQLiteBackend("stent.db")
await backend.init_db()
executor = Stent.use(backend=backend)
worker = asyncio.create_task(executor.serve())
await executor.wait_until_ready()

Backends

# SQLite (development)
backend = Stent.backends.SQLiteBackend("stent.db")

# PostgreSQL (production)
backend = Stent.backends.PostgresBackend("postgresql://user:pass@host/db")

# Optional: Redis for low-latency notifications
executor = Stent(
    backend=backend,
    notification_backend=Stent.notifications.RedisBackend("redis://localhost")
)

Result Type

from stent import Result

result = Result.Ok(42)
result.unwrap()        # 42
result.unwrap_or(0)    # 42
result.map(str)        # Result.Ok("42")

err = Result.Error("oops")
err.unwrap_or(0)       # 0
bool(err)              # False

Testing

# conftest.py
pytest_plugins = ["stent.testing"]

# test_my_workflow.py
async def test_workflow(stent_worker):
    result = await my_durable_fn(42)
    assert result == 84

CLI

stent list                    # List executions
stent show <exec_id>          # Show execution details
stent dlq list                # List dead-lettered tasks
stent dlq replay <task_id>    # Replay failed task

Documentation

Full documentation available in docs/:

Examples

See examples/ for complete workflows:

  • simple_flow.py - Basic workflow
  • saga_trip_booking.py - Saga pattern with compensation
  • batch_processing.py - Fan-out/fan-in
  • media_pipeline.py - Complex multi-stage pipeline

Requirements

  • Python 3.12+
  • aiosqlite or asyncpg (backend)
  • redis (optional, for notifications)

License

MIT

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

stent-1.1.1.tar.gz (88.6 kB view details)

Uploaded Source

Built Distribution

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

stent-1.1.1-py3-none-any.whl (107.4 kB view details)

Uploaded Python 3

File details

Details for the file stent-1.1.1.tar.gz.

File metadata

  • Download URL: stent-1.1.1.tar.gz
  • Upload date:
  • Size: 88.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.20

File hashes

Hashes for stent-1.1.1.tar.gz
Algorithm Hash digest
SHA256 ada179e7167ef396b2964d9901f41fdfd179056d905bc5a908f846db2149d6c8
MD5 0020b201ce3d0d4cbebd782792109f1e
BLAKE2b-256 42dbee58d1864e15f68cc9158fc26c9d4c4201638d1e16296e8b7407f9d14c18

See more details on using hashes here.

File details

Details for the file stent-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: stent-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 107.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.20

File hashes

Hashes for stent-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 416120d051fa1cbb1cc9502acab2c7954f7afd18264be134aef1cba4f20a625e
MD5 f9875bd5d8dc6f9ff444920983bf0128
BLAKE2b-256 2972f62885f554925ce6d87ad98aa6f559178c60740b1eddc161067f3611d694

See more details on using hashes here.

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