Skip to main content

Async Durable Execution for Python

简体中文 繁體中文 Quick start Read the docs

Build Conformance Coverage PyPI - Version PyPI - Python Version License

Build fully compliant, long-running AWS Lambda workflows with native async/await. Checkpoint state automatically, pause without active compute, and resume after failures without running a workflow server.

Import all user-facing APIs from async_durable_execution. Extension contracts, filesystem SerDes stages, and preview helpers are exported at the package root; their implementation modules are private.

Project Status

Fully compliant with the AWS Durable Execution conformance suite. Every upstream requirement is continuously validated against deployed Lambda functions in CI.

The project also maintains extensive local and cloud runner coverage, publishes generated API documentation and coverage reports, and includes executable examples for async durable workflows.

Community-maintained async fork of the Apache-2.0 licensed AWS Durable Execution Python SDK.

This project continues to ship under Apache License 2.0 with the upstream notices preserved.

The fork exists because the official Python SDK does not support async/await, making integration with asyncio libraries difficult. This SDK adds async durable callables, background operation tasks, direct asyncio task composition, and APIs designed for modern Python applications.

✨ Key Features

  • Async-first durable code - Compared with the official AWS SDK, user-provided durable handlers, steps, child contexts, flow nodes, callback submitters, map item functions, parallel branches, and wait-for-condition checks are written with async def.
  • Operations not available in the official SDK - This SDK adds replay-safe helpers (random(), now(), timestamp(), and uuid()) and durable self-invocation (recurse()).
  • Stable custom operation SPI - Third-party packages can reserve opaque deterministic primitive identities, use custom subtypes, and build stateful replay-safe operations without importing SDK internals.
  • Declarative DAG workflows - Define acyclic workflows with typed node inputs, inferred or conditional dependencies, failure routes, and durable operations inside each node. The SDK validates the graph before execution and skips nodes that are not required by the selected outputs.
  • Suspension-aware cleanup and compensation - Register durable cleanup and reverse-order compensation that runs at logical completion or failure without firing when a wait, callback, retry, or branch merely suspends.
  • Background operation tasks - Durable operations such as step(...), wait(...), invoke(...), recurse(...), run_in_child_context(...), terminal_scope(...), and flow(...) return asyncio.Task objects, so independent operations can run in the background and be awaited together with asyncio.gather without using parallel() or map().
  • Pythonic operation parameters - Operations use direct keyword arguments, standard Python types such as datetime.timedelta, and keyword-only names instead of configuration wrapper objects.
  • Composable SerDes pipelines - Chain async string transformations and offload large checkpoint payloads to EFS or S3 Files with bounded previews and validated immutable storage.
  • Integrated local and cloud runner - Runner functionality now ships through async_durable_execution, with separate local and cloud runner factories and typed test result helpers.
  • Model-free Lambda clients - The SDK owns its Lambda REST wire format instead of depending on botocore service models. Install the optional httpx extra to send requests with HTTPX; otherwise the same requests use botocore's synchronous HTTP transport through an async adapter.
  • Replay-aware logging with stdlib logging - Standard logging loggers are enriched by durable context filtering so workflow logs remain replay safe.
  • Lambda layer packaging - The repo includes tooling and workflows to build and publish an SDK Lambda layer for functions that do not vendor dependencies directly.

🚀 Quick Start

Install the execution SDK:

pip install async-durable-execution

For an async Lambda service client, install the optional httpx extra:

pip install "async-durable-execution[httpx]"

The httpx extra installs HTTPX, which the SDK uses for asynchronous model-free Lambda REST calls. Without it, the SDK sends the same signed requests with botocore's synchronous HTTP transport through a threaded async adapter. Botocore continues to provide AWS credentials, endpoint metadata, and SigV4 signing, but its generated Lambda service model is not used.

The previous aioboto extra remains available as a backward-compatible alias for httpx.

Create a durable Lambda handler:

import logging
from datetime import timedelta

from async_durable_execution import (
    durable_callable,
    durable_execution,
    step,
    wait,
)

logger = logging.getLogger(__name__)


@durable_callable
async def validate_order(order_id: str) -> dict:
    logger.info("Validating order", extra={"order_id": order_id})
    return {"order_id": order_id, "valid": True}


@durable_callable
async def create_receipt(order_id: str) -> dict:
    logger.info("Creating receipt", extra={"order_id": order_id})
    return {"receipt_id": f"receipt-{order_id}", "order_id": order_id}


@durable_execution
async def handler(event: dict) -> dict:
    order_id = event["order_id"]
    logger.info("Starting workflow", extra={"order_id": order_id})

    validation = await step(validate_order(order_id), name="validate_order")
    if not validation["valid"]:
        return {"status": "rejected", "order_id": order_id}

    # simulate approval (real world: use wait_for_callback)
    await wait(duration=timedelta(seconds=5), name="await_confirmation")

    receipt = await step(create_receipt(order_id), name="create_receipt")

    return {"status": "approved", "order_id": order_id, "receipt": receipt}

Durable operations return asyncio.Task objects. If you call an operation without immediately awaiting it, it is scheduled to run in the background and can be awaited later. This lets independent operations run concurrently with normal asyncio patterns:

import asyncio

pricing_tasks = [
    step(price_line_item(item), name=f"price-{item['sku']}")
    for item in items
]
priced_items = await asyncio.gather(*pricing_tasks)

🧪 Testing Durable Functions

The SDK includes runner helpers for testing durable functions locally or against deployed Lambda functions. The local runner executes the durable handler in process, intercepts checkpoint operations with an in-memory service client, and returns a DurableFunctionTestResult that can be inspected by operation name.

Assuming the Quick Start handler above is saved in order_workflow.py, a local test can run the same durable function:

import json

from async_durable_execution import (
    DurableFunctionTestResult,
    InvocationStatus,
    create_local_runner,
)

from order_workflow import handler


async def test_my_durable_function() -> None:
    with create_local_runner(
        handler=handler,
        input={"order_id": "order-123"},
        timeout=10,
    ) as runner:
        result: DurableFunctionTestResult = await runner.run()

    receipt = {"receipt_id": "receipt-order-123", "order_id": "order-123"}

    assert result.status is InvocationStatus.SUCCEEDED
    assert result.result == json.dumps(
        {"status": "approved", "order_id": "order-123", "receipt": receipt}
    )

    validation_result = result.get_step("validate_order")
    assert validation_result.step_details is not None
    assert validation_result.step_details.result == json.dumps(
        {"order_id": "order-123", "valid": True}
    )

    receipt_result = result.get_step("create_receipt")
    assert receipt_result.step_details is not None
    assert receipt_result.step_details.result == json.dumps(receipt)

After deploying the same handler to Lambda, use the cloud runner to test the deployed durable function. The function name must be qualified with a version or alias, for example order-workflow:$LATEST or order-workflow:prod.

import os

from async_durable_execution import InvocationStatus, create_cloud_runner


async def test_order_workflow_in_cloud() -> None:
    with create_cloud_runner(
        function_name=os.environ["ORDER_WORKFLOW_FUNCTION_NAME"],
        region=os.environ.get("AWS_REGION", "us-east-1"),
        input={"order_id": "order-123"},
        timeout=45,
    ) as runner:
        result = await runner.run()

    receipt = {"receipt_id": "receipt-order-123", "order_id": "order-123"}

    assert result.status is InvocationStatus.SUCCEEDED
    assert result.get_deserialized_result() == {
        "status": "approved",
        "order_id": "order-123",
        "receipt": receipt,
    }

🧩 Examples

Example durable functions live in examples/. Start with hello_world.py for the smallest complete handler.

The example tests in test_examples/ are also useful as executable recipes. Browse them by operation or pattern:

  • step/, wait/, wait_for_callback/, and wait_for_condition/ for core durable operations
  • step/steps_with_gather.py for starting multiple step tasks and awaiting them together with asyncio.gather
  • flow/, map/, parallel/, and run_in_child_context/ for composition patterns
  • terminal_scope/ for callback-safe cleanup and failure compensation
  • invoke/, including invoke/recurse.py, with_retry/, callback/, and logger_example/ for integrations and operational behavior

For the developer workflow to run or deploy example integration tests, see the Contributing Guide.

📚 Documentation

  • Documentation Site - Searchable guides and API reference generated from Python docstrings
  • DAG Workflow API - Build declarative workflows with flow(), typed node inputs, conditional dependencies, and failure routes
  • Official Python SDK Comparison - Side-by-side comparison with the official AWS Durable Execution Python SDK
  • Migration Guide - Move from the official synchronous Python SDK to this async-first SDK
  • Workflow Patterns - Build agentic loops, human approval workflows, and compensating transactions
  • Durable Terminal Scopes - Run cleanup and compensation at logical terminal outcomes without treating suspension as failure
  • Deploy and Invoke - Configure IAM, qualified function identifiers, invocations, CloudFormation, and SAM
  • Using Synchronous Code - Wrap existing synchronous business logic and blocking clients safely
  • Advanced Usage - Explore background operation tasks, batch completion conditions, Lambda clients, and Lambda layers
  • Custom Durable Operations - Build third-party durable operation libraries on the stable extension-author interface
  • Runner Architecture - Local and cloud runner execution flow, components, and diagrams
  • Contributing Guide - Development workflow, Hatch commands, testing, and pull request guidance

References

💬 Feedback & Support

📄 License

See the LICENSE file for our project's licensing.

Metadata

Release files for async-durable-execution 2.5.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 async-durable-execution 2.5.0
File Size Uploaded
async_durable_execution-2.5.0.tar.gz 154.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for async-durable-execution 2.5.0
File Interpreter ABI Platform
async_durable_execution-2.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 351.3 kB

Release files / async_durable_execution-2.5.0.tar.gz

Download URL async_durable_execution-2.5.0.tar.gz
Size 154.7 kB
Tags Source
SHA-256 checksum
How to use checksums
66dd5691c54923f2ce19518dfff127b13e1bce8c9c94e7f6add8614839c9ef5a
BLAKE2b-256 checksum
How to use checksums
91c0586e120f8c8b27a35728644d5e22a95383e0d8be5b9a8726b6825a36dfd6
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 10, 2026.

Transparency log

Release files / async_durable_execution-2.5.0-py3-none-any.whl

Download URL async_durable_execution-2.5.0-py3-none-any.whl
Size 196.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4fd8f664cfb6bd5a7fd30096e454d19cc0104d610adb546ea8a4642fd06cf34d
BLAKE2b-256 checksum
How to use checksums
2d311a18f97b0e3e267a0315b0f08f51d191fd9aa3f382df094c47947eb7f9a0
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 10, 2026.

Transparency log
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