AWS Durable Execution SDK for Python
Build reliable, long-running AWS Lambda workflows with checkpointed steps, waits, callbacks, and parallel execution.
✨ Key Features
- Automatic checkpointing - Resume execution after Lambda pauses or restarts
- Durable steps - Run work with retry strategies and deterministic replay
- Waits and callbacks - Pause for time or external signals without blocking Lambda
- Parallel and map operations - Fan out work with configurable completion criteria
- Child contexts - Structure complex workflows into isolated subflows
- Replay-safe logging - Use
context.loggerfor structured, de-duplicated logs - Local and cloud testing - Validate workflows with the testing SDK
📦 Packages
| Package | Description | Version |
|---|---|---|
aws-durable-execution-sdk-python |
Execution SDK for Lambda durable functions | |
aws-durable-execution-sdk-python-testing |
Local/cloud test runner and pytest helpers |
Dynamic instrumentation plugins
Instrumentation plugins can be selected at Lambda cold start without importing them in the function artifact. Install a provider package in the function or a Lambda layer, then set an ordered allow-list:
DURABLE_EXECUTION_PLUGINS=otel-invocation,example_audit
The SDK resolves those names from the aws_durable_execution.plugins Python
entry-point group when the decorated handler is initialized. An unset or blank
variable preserves the existing behavior. The decorator's plugins argument
remains supported; explicit plugins run first and take precedence when a
dynamic provider creates the same concrete plugin type.
Provider packages expose a versioned factory:
from aws_durable_execution_sdk_python.plugin import (
DurableInstrumentationPlugin,
DurableInstrumentationPluginProvider,
)
class AuditPlugin(DurableInstrumentationPlugin):
pass
AUDIT_PLUGIN_PROVIDER = DurableInstrumentationPluginProvider(
plugin_type=AuditPlugin,
factory=AuditPlugin,
plugin_api_version=1,
)
Register the provider in the package's pyproject.toml:
[project.entry-points."aws_durable_execution.plugins"]
example_audit = "example_audit:AUDIT_PLUGIN_PROVIDER"
Set plugin_api_version to the literal API version the provider implements.
Update it only after verifying the provider against that API version.
Provider names must be unique across installed distributions. Missing,
ambiguous, incompatible, or invalid providers raise PluginLoadError during
handler initialization with the provider and distribution details.
Selecting an OpenTelemetry view
Enable at most one of InvocationOtelPlugin and ExecutionOtelPlugin. This
constraint applies to the combined plugins=[...] argument and
DURABLE_EXECUTION_PLUGINS=otel-invocation or otel-execution selection.
Core 2.1+ with OTel 1.1+ rejects both at cold start with PluginLoadError naming the
conflicting views; keep only one. Choose Invocation for work within each Lambda
invocation or Execution for logical operations across the durable execution.
Unrelated instrumentation plugins can run alongside either view. OTel 1.1 requires core 2.1 or later for the invocation-worker lifecycle as well
as view-exclusivity validation. Release core 2.1 before releasing OTel 1.1.
Plugin authors explicitly opt in by declaring
__durable_registration_api__ = 1 on a plugin class. That class and its subclasses
have their optional exclusive_group metadata validated and their
optional on_registration_result(registered) callback invoked. A missing group
or None adds no exclusivity constraint. The callback can release rejected
constructor resources and must preserve resources from earlier accepted use.
The first class declaring the marker in the method resolution order gates
registration and selects the callback contract. An explicit marker other than
the integer 1 shadows an ancestor's opt-in and disables this capability.
Once enabled, every class in the MRO explicitly declaring the integer 1
contributes its resolved exclusive_group. These constraints accumulate:
repeating the marker with a new group adds to inherited groups rather than
replacing them, and None does not erase an inherited constraint. A group is
counted once per plugin registration even if several ancestors declare it.
Unmarked classes' coincidental same-named legacy fields/helpers remain inert.
Subclasses of the bundled OTel views therefore retain the view group when they
add their own group. Repeat the marker to add a group or customize the callback;
the callback is still notified once per plugin. Plugins with no marker anywhere
in their hierarchy retain their existing attributes/helpers. The generic plugin base defines
neither attribute nor hook, and provider API version 1 and existing plugin
lifecycle order are unchanged.
Invocation worker lifecycle
The existing on_invocation_start and on_invocation_end hooks run on the
handler's invocation worker, both in registration order. Start hooks finish
before background checkpoint processing begins. The same worker runs the handler
(including its finally blocks), prepares the existing serialized output or
error, checkpoints large results when necessary, joins registered branches while
checkpointing is available, stops and waits for checkpoint processing, then calls
End before returning the outcome. The caller shuts down the handler executor.
There is no separate handler-context hook or context-manager plugin API.
When plugins are registered, the worker begins with a copy of the caller's
context-variable bindings. Successful Start bindings flow into later Start hooks,
the handler, serialization and resource cleanup. A later successful Start may
replace an earlier binding. If a Start hook raises, its new bindings are discarded
for subsequent work; its End still runs in the original Context so its tokens can
be reset. End hooks retain forward registration order, not reverse stack order,
so they must not rely on observing a stack-like unwind of other plugins' contexts.
Each SDK-managed map or parallel branch admission, including an in-process
resume, receives a fresh copy of the coordinator's bindings when plugins are
registered. Branch changes remain local even when pool threads are reused.
User-created threads retain normal Python context-variable behavior; without
registered plugins, SDK branch submission keeps its existing behavior.
The worker's invocation context is discarded on return, including after plugin
cleanup failures, leaving the host's bindings unchanged. This isolates bindings,
not mutations to shared objects or external side effects. Without plugins, the
handler retains its existing fresh-worker context behavior.
These lifecycle guarantees require core 2.1.0 or later. The OTel 1.1 plugin requires that core version and uses the existing Start/End hooks. Upgrading the core alone with OTel 1.0 isolates worker bindings, but does not add the newer Invocation-view fallback to that older plugin; upgrade both packages for it. Provider API version 1 and the independent registration API are unchanged.
Chained-invoke propagation
A new invoke START collects synchronous plugin metadata after resolving its
operation and target identity, before checkpointing. The SDK writes a non-blank
x_amzn_trace_id contribution to the flat
ChainedInvokeOptions.XAmznTraceId member. Function, tenant, payload, operation
name and identity remain unchanged. The member is omitted without a contribution.
Each invoke in a batch has its own metadata; this is not a request-wide header.
The SDK-owned frozen PropagationInput carries execution_arn, operation_id,
optional parent_operation_id, and target_function_name. Frozen
PropagationMetadata has optional x_amzn_trace_id. Neither type depends on
OpenTelemetry or generated service models. The optional synchronous plugin
provide_propagation_metadata(info) hook defaults to no contribution.
The collector keeps the first non-blank opaque value in configured order without
trimming it. Equal values do not conflict; different later values log both plugin
identities and a conflict count. Ordinary hook/getter/result/diagnostic failures
are isolated; cancellation and other BaseException control signals keep their
existing behavior. Replaying a checkpointed START, pending operation or terminal
result does not call the hook. If a START was never committed, a later attempt
can collect again; the callback is not exactly-once.
This requires boto3 1.43.114 or newer, whose botocore dependency includes the
published optional ChainedInvokeOptions.XAmznTraceId string field. The real
botocore tests exercise its parameter validation and wire serialization without
field removal, validation bypass or a model-capability fallback. Deployed backend
delivery and downstream topology still require separate validation.
Python has no DistributedMapOptions wrapper
or distributed-map START API: its existing map/parallel operations use CONTEXT.
The design's corresponding distributed-map field awaits that future modeled path.
Release coordination still requires the compatible core/OTel minor versions. Existing valid core/plugin combinations retain prior tracing behavior; older cores without this optional contract contribute no new propagation metadata. Backend rollout and deployed downstream trace-topology validation remain pending.
🚀 Quick Start
Install the execution SDK:
pip install aws-durable-execution-sdk-python
Create a durable Lambda handler:
from aws_durable_execution_sdk_python import (
DurableContext,
StepContext,
durable_execution,
durable_step,
)
from aws_durable_execution_sdk_python.config import Duration
@durable_step
def validate_order(step_ctx: StepContext, order_id: str) -> dict:
step_ctx.logger.info("Validating order", extra={"order_id": order_id})
return {"order_id": order_id, "valid": True}
@durable_execution
def handler(event: dict, context: DurableContext) -> dict:
order_id = event["order_id"]
context.logger.info("Starting workflow", extra={"order_id": order_id})
validation = context.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)
context.wait(duration=Duration.from_seconds(5), name="await_confirmation")
return {"status": "approved", "order_id": order_id}
📚 Documentation
The complete documentation for the AWS Durable Execution SDK for Python lives on the AWS Documentation site:
- AWS Durable Execution Documentation - Concepts, getting started, core operations, advanced topics, and API reference
- AWS Lambda Durable Functions Guide - How durable functions work on Lambda
💬 Feedback & Support
📄 License
See the LICENSE file for our project's licensing.
Metadata
Release files for aws-durable-execution-sdk-python 2.1.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 | Provenance | |
|---|---|---|---|---|
| aws_durable_execution_sdk_python-2.1.0.tar.gz | 353.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aws_durable_execution_sdk_python-2.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 495.1 kB
Release files / aws_durable_execution_sdk_python-2.1.0.tar.gz
| Download URL | aws_durable_execution_sdk_python-2.1.0.tar.gz |
|---|---|
| Size | 353.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
74e56ab587defc7b98b674fe3b5f8c46500b74e3941719caca5b76c57800d844
|
|
BLAKE2b-256 checksum How to use checksums |
1b13095b1c5fa5cecfad1e7fba906562184935b3c4c598a403261a0f0c082137
|
| 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 Oct 21, 2026.
Transparency logRelease files / aws_durable_execution_sdk_python-2.1.0-py3-none-any.whl
| Download URL | aws_durable_execution_sdk_python-2.1.0-py3-none-any.whl |
|---|---|
| Size | 141.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
485f235454cb9408c7da04fab34dbe48ba03d3d5617bc5e86601be8ed6128d9d
|
|
BLAKE2b-256 checksum How to use checksums |
e2b986b5c546b21940f953ca5e97035e73b5cf067f6fba605fffb2d88c935bd3
|
| 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 Oct 21, 2026.
Transparency logSecurity for aws-durable-execution-sdk-python 2.1.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for aws_durable_execution_sdk_python-2.1.0.tar.gz
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 Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.