Skip to content

Getting Started

This guide creates and locally tests a durable order workflow. You need Python 3.10 or newer.

Install

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

The aioboto extra uses aiobotocore for Lambda checkpoint and state API calls. Without it, the SDK runs its bundled synchronous client through an async adapter.

Create a Workflow

Save this as order_workflow.py:

from datetime import timedelta

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


@durable_callable
async def validate_order(order_id: str) -> dict:
    return {"order_id": order_id, "valid": True}


@durable_execution
async def handler(event: dict) -> dict:
    order = await step(
        validate_order(event["order_id"]),
        name="validate-order",
    )
    await wait(timedelta(seconds=5), name="processing-delay")
    return {"status": "complete", "order": order}

validate_order() is a durable callable. step() checkpoints its result, and wait() suspends execution until the delay ends.

Test Locally

import asyncio

from async_durable_execution import InvocationStatus, create_local_runner
from order_workflow import handler


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

    assert result.status is InvocationStatus.SUCCEEDED
    assert result.get_step("validate-order").step_details is not None
    print(result.get_deserialized_result())


asyncio.run(main())

The local runner intercepts checkpoint operations in memory and requires no AWS credentials.

Understand Replay

On resume, durable workflows run again from the beginning. Completed operations return saved results, but ordinary Python code executes again.

Keep replayed code deterministic

Put API calls, database queries, filesystem access, UUID generation, random values, and clock reads inside step(). Do not mutate outer variables from a step or perform side effects directly in the handler.

Standard logging calls are replay-aware when they run within a durable execution.

Compose Durable Operations Outside Steps

A callable passed to step() is one atomic operation and cannot start another durable operation. Use run_in_child_context() when a reusable sub-workflow needs its own steps or waits:

from datetime import timedelta

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


@durable_callable
async def load_order(order_id: str) -> dict:
    return {"order_id": order_id}


@durable_callable
async def process_order(order_id: str) -> dict:
    order = await step(load_order(order_id), name="load-order")
    await wait(timedelta(seconds=1), name="processing-delay")
    return order


result = await run_in_child_context(
    process_order("order-123"),
    name="process-order",
)

Handlers and child contexts compose durable operations. Step bodies perform the nondeterministic work that should be checkpointed atomically.

Continue