aws-durable-execution-sdk-python 2.0.1__tar.gz → 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/.gitignore +1 -0
- aws_durable_execution_sdk_python-3.0.0/PKG-INFO +311 -0
- aws_durable_execution_sdk_python-3.0.0/README.md +290 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/pyproject.toml +1 -1
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/__about__.py +1 -1
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/concurrency/executor.py +24 -12
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/concurrency/models.py +31 -21
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/config.py +109 -7
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/context.py +87 -28
- aws_durable_execution_sdk_python-3.0.0/src/aws_durable_execution_sdk_python/execution.py +584 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/identifier.py +12 -11
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/lambda_service.py +65 -63
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/child.py +30 -5
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/invoke.py +6 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/map.py +4 -4
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/parallel.py +4 -4
- aws_durable_execution_sdk_python-3.0.0/src/aws_durable_execution_sdk_python/plugin.py +2035 -0
- aws_durable_execution_sdk_python-3.0.0/src/aws_durable_execution_sdk_python/plugin_discovery.py +517 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/state.py +240 -63
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/concurrency_test.py +237 -19
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/config_test.py +65 -3
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/context_test.py +297 -45
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/batch_result_none_replay_test.py +83 -0
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/branch_worker_context_test.py +138 -0
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/custom_subtype_int_test.py +165 -0
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/factory_registration_cleanup_int_test.py +93 -0
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/factory_registration_int_test.py +96 -0
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/handler_context_propagation_int_test.py +296 -0
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/handler_invocation_context_int_test.py +82 -0
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/invoke_propagation_int_test.py +286 -0
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/plugin_hook_contract_int_test.py +383 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/plugin_invocation_operations_int_test.py +36 -17
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/plugin_invocation_payload_int_test.py +5 -5
- aws_durable_execution_sdk_python-3.0.0/tests/e2e/plugin_operation_type_int_test.py +205 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/plugin_user_function_lifecycle_int_test.py +2 -1
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/execution_test.py +1011 -27
- aws_durable_execution_sdk_python-3.0.0/tests/factory_registration_test.py +475 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/filesystem_serdes_test.py +5 -1
- aws_durable_execution_sdk_python-3.0.0/tests/handler_worker_context_test.py +591 -0
- aws_durable_execution_sdk_python-3.0.0/tests/identifier_test.py +134 -0
- aws_durable_execution_sdk_python-3.0.0/tests/invoke_wire_propagation_test.py +144 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/lambda_service_test.py +257 -78
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/logger_test.py +14 -2
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/base_test.py +4 -1
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/callback_test.py +140 -34
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/child_test.py +278 -67
- aws_durable_execution_sdk_python-3.0.0/tests/operation/invoke_propagation_test.py +174 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/invoke_test.py +177 -58
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/map_test.py +103 -15
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/parallel_test.py +103 -15
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/step_test.py +231 -43
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/wait_for_condition_test.py +225 -45
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/wait_test.py +48 -11
- aws_durable_execution_sdk_python-3.0.0/tests/plugin_discovery_test.py +844 -0
- aws_durable_execution_sdk_python-3.0.0/tests/plugin_hook_registry_test.py +1704 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/plugin_test.py +964 -91
- aws_durable_execution_sdk_python-3.0.0/tests/propagation_test.py +198 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/state_test.py +645 -79
- aws_durable_execution_sdk_python-3.0.0/tests/test_helpers.py +79 -0
- aws_durable_execution_sdk_python-2.0.1/PKG-INFO +0 -156
- aws_durable_execution_sdk_python-2.0.1/README.md +0 -135
- aws_durable_execution_sdk_python-2.0.1/src/aws_durable_execution_sdk_python/execution.py +0 -469
- aws_durable_execution_sdk_python-2.0.1/src/aws_durable_execution_sdk_python/plugin.py +0 -863
- aws_durable_execution_sdk_python-2.0.1/src/aws_durable_execution_sdk_python/plugin_discovery.py +0 -209
- aws_durable_execution_sdk_python-2.0.1/tests/plugin_discovery_test.py +0 -479
- aws_durable_execution_sdk_python-2.0.1/tests/test_helpers.py +0 -20
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/LICENSE +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/NOTICE +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/.gitignore +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/__init__.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/concurrency/__init__.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/constants.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/exceptions.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/filesystem_serdes.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/logger.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/__init__.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/base.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/callback.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/step.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/wait.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/operation/wait_for_condition.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/preview.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/py.typed +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/retries.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/serdes.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/suspend.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/threading.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/types.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/src/aws_durable_execution_sdk_python/waits.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/__init__.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/durable_executions_python_language_sdk_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/__init__.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/checkpoint_response_int_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/custom_serdes_roundtrip_int_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/error_hierarchy_int_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/execution_int_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/filesystem_serdes_int_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/map_with_concurrent_waits_int_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/should_complete_suspend_resume_int_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/exceptions_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/__init__.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/preview_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/retries_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/serdes_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/suspend_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/threading_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/types_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/waits_test.py +0 -0
- {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/with_retry_test.py +0 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: aws-durable-execution-sdk-python
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: AWS Durable Execution SDK for Python
|
|
5
|
+
Project-URL: Documentation, https://github.com/aws/aws-durable-execution-sdk-python#readme
|
|
6
|
+
Project-URL: Issues, https://github.com/aws/aws-durable-execution-sdk-python/issues
|
|
7
|
+
Project-URL: Source, https://github.com/aws/aws-durable-execution-sdk-python
|
|
8
|
+
Author-email: AWS durable-execution-dev <durable-execution-dev@amazon.com>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
17
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: boto3>=1.43.114
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# AWS Durable Execution SDK for Python
|
|
23
|
+
|
|
24
|
+
[](https://github.com/aws/aws-durable-execution-sdk-python/actions/workflows/ci.yml)
|
|
25
|
+
[](https://pypi.org/project/aws-durable-execution-sdk-python)
|
|
26
|
+
[](https://pypi.org/project/aws-durable-execution-sdk-python)
|
|
27
|
+
[](https://scorecard.dev/viewer/?uri=github.com/aws/aws-durable-execution-sdk-python)
|
|
28
|
+
[](https://github.com/aws/aws-durable-execution-sdk-python/blob/main/LICENSE)
|
|
29
|
+
|
|
30
|
+
-----
|
|
31
|
+
|
|
32
|
+
Build reliable, long-running AWS Lambda workflows with checkpointed steps, waits, callbacks, and parallel execution.
|
|
33
|
+
|
|
34
|
+
## ✨ Key Features
|
|
35
|
+
|
|
36
|
+
- **Automatic checkpointing** - Resume execution after Lambda pauses or restarts
|
|
37
|
+
- **Durable steps** - Run work with retry strategies and deterministic replay
|
|
38
|
+
- **Waits and callbacks** - Pause for time or external signals without blocking Lambda
|
|
39
|
+
- **Parallel and map operations** - Fan out work with configurable completion criteria
|
|
40
|
+
- **Child contexts** - Structure complex workflows into isolated subflows
|
|
41
|
+
- **Replay-safe logging** - Use `context.logger` for structured, de-duplicated logs
|
|
42
|
+
- **Local and cloud testing** - Validate workflows with the testing SDK
|
|
43
|
+
|
|
44
|
+
## 📦 Packages
|
|
45
|
+
|
|
46
|
+
| Package | Description | Version |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `aws-durable-execution-sdk-python` | Execution SDK for Lambda durable functions | [](https://pypi.org/project/aws-durable-execution-sdk-python) |
|
|
49
|
+
| `aws-durable-execution-sdk-python-testing` | Local/cloud test runner and pytest helpers | [](https://pypi.org/project/aws-durable-execution-sdk-python-testing) |
|
|
50
|
+
|
|
51
|
+
## Dynamic instrumentation plugins
|
|
52
|
+
|
|
53
|
+
Upgrading an existing application? Follow the [2.x to 3.0 migration guide](https://github.com/aws/aws-durable-execution-sdk-python/blob/main/docs/migration-2.x-to-3.0.md)
|
|
54
|
+
for dependency versions, before/after examples, provider packaging, and validation.
|
|
55
|
+
|
|
56
|
+
Instrumentation plugins can be selected at Lambda cold start without importing
|
|
57
|
+
them in the function artifact. Install a provider package in the function or a
|
|
58
|
+
Lambda layer, then set an ordered allow-list:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
DURABLE_EXECUTION_PLUGINS=otel-invocation,example_audit
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The SDK resolves those names from the `aws_durable_execution.plugins` Python
|
|
65
|
+
entry-point group when the decorated handler is initialized. An unset or blank
|
|
66
|
+
variable preserves the existing behavior. The decorator's `plugins` argument
|
|
67
|
+
remains supported; explicit factories run first, and a factory passed to the
|
|
68
|
+
decorator is not registered a second time through the environment.
|
|
69
|
+
|
|
70
|
+
A plugin is registered as a *factory*, not as an instance. A factory is an object
|
|
71
|
+
with a `create_plugin(info)` method taking the invocation's `InvocationStartInfo`
|
|
72
|
+
and returning a `DurableInstrumentationPlugin`; the SDK calls that method once per
|
|
73
|
+
invocation, so the instance it returns serves that one invocation only and can
|
|
74
|
+
hold **per-invocation** state in ordinary attributes.
|
|
75
|
+
|
|
76
|
+
Per-invocation is narrower than per-execution, and the difference matters. A
|
|
77
|
+
durable execution spans as many invocations as it waits, retries or resumes, and
|
|
78
|
+
the instance is dropped when each of those returns — so anything a plugin keeps in
|
|
79
|
+
its attributes is gone by the next invocation of the same execution. State that
|
|
80
|
+
has to survive that has two honest homes: rebuild it from the operation map the
|
|
81
|
+
invocation hooks carry (`InvocationStartInfo.operations` is a full snapshot,
|
|
82
|
+
which is how the bundled Telemetry exporter reports operations that completed in an
|
|
83
|
+
earlier invocation), or put it on the factory, which outlives every invocation —
|
|
84
|
+
keyed by execution ARN, and pruned by the owner, because the SDK will not tell the
|
|
85
|
+
factory when an execution ends for good.
|
|
86
|
+
|
|
87
|
+
A factory is an object with a method rather than a plain callable so the
|
|
88
|
+
registration type can grow a second, optional member later -- a process-level
|
|
89
|
+
flush on execution-environment shutdown, for example -- without a second breaking
|
|
90
|
+
change to this surface.
|
|
91
|
+
|
|
92
|
+
Write a small factory class and construct the plugin in its `create_plugin`. The
|
|
93
|
+
factory holds what outlives an invocation, such as an exporter or a resolved
|
|
94
|
+
configuration, and setup work that can fail belongs in the factory's own
|
|
95
|
+
constructor rather than the plugin's:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
class AuditPluginFactory:
|
|
99
|
+
def __init__(self, sink):
|
|
100
|
+
self._sink = sink
|
|
101
|
+
|
|
102
|
+
def create_plugin(self, info):
|
|
103
|
+
return AuditPlugin(self._sink)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
plugins=[AuditPluginFactory(sink)]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A plugin class is not a factory, and neither is a bare callable. `plugins=[MyPlugin]`
|
|
110
|
+
and `plugins=[lambda info: MyPlugin(sink)]` raise `PluginLoadError` during handler
|
|
111
|
+
initialization, because neither carries `create_plugin`. A class that declares
|
|
112
|
+
`create_plugin` as a `@classmethod` is accepted, since the requirement is the
|
|
113
|
+
member and not the kind of object.
|
|
114
|
+
|
|
115
|
+
Provider packages expose such a factory:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from aws_durable_execution_sdk_python.plugin import (
|
|
119
|
+
DurableInstrumentationPlugin,
|
|
120
|
+
InvocationStartInfo,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class AuditPlugin(DurableInstrumentationPlugin):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class AuditPluginFactory:
|
|
129
|
+
def create_plugin(self, info: InvocationStartInfo) -> AuditPlugin:
|
|
130
|
+
return AuditPlugin()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
AUDIT_PLUGIN_FACTORY = AuditPluginFactory()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Register the factory instance in the package's `pyproject.toml`:
|
|
137
|
+
|
|
138
|
+
```toml
|
|
139
|
+
[project.entry-points."aws_durable_execution.plugins"]
|
|
140
|
+
example_audit = "example_audit:AUDIT_PLUGIN_FACTORY"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Provider names must be unique across installed distributions. Missing,
|
|
144
|
+
ambiguous, or wrongly shaped providers raise `PluginLoadError` during handler
|
|
145
|
+
initialization with the provider and distribution details.
|
|
146
|
+
|
|
147
|
+
### Selecting an OpenTelemetry view
|
|
148
|
+
|
|
149
|
+
Enable at most one of `InvocationOtelPluginFactory` and
|
|
150
|
+
`ExecutionOtelPluginFactory`. This
|
|
151
|
+
constraint applies to the combined `plugins=[...]` argument and
|
|
152
|
+
`DURABLE_EXECUTION_PLUGINS=otel-invocation` or `otel-execution` selection.
|
|
153
|
+
Core 3 with OTel 2 rejects both at cold start with `PluginLoadError` naming the
|
|
154
|
+
conflicting views; keep only one. Choose Invocation for work within each Lambda
|
|
155
|
+
invocation or Execution for logical operations across the durable execution.
|
|
156
|
+
Unrelated instrumentation plugin factories can run alongside either view.
|
|
157
|
+
OTel 2 requires core 3.x and the per-invocation factory contract. Release the
|
|
158
|
+
coordinated core version before releasing OTel 2.
|
|
159
|
+
|
|
160
|
+
Plugin authors explicitly opt in by declaring
|
|
161
|
+
`__durable_registration_api__ = 1` on a factory class. That class and its subclasses
|
|
162
|
+
have their optional `exclusive_group` metadata validated and their
|
|
163
|
+
optional `on_registration_result(registered)` callback invoked. A missing group
|
|
164
|
+
or `None` adds no exclusivity constraint. The callback can release rejected
|
|
165
|
+
constructor resources and must preserve resources from earlier accepted use.
|
|
166
|
+
|
|
167
|
+
The first class declaring the marker in the method resolution order gates
|
|
168
|
+
registration and selects the callback contract. An explicit marker other than
|
|
169
|
+
the integer `1` shadows an ancestor's opt-in and disables this capability.
|
|
170
|
+
Once enabled, every class in the MRO explicitly declaring the integer `1`
|
|
171
|
+
contributes its resolved `exclusive_group`. These constraints accumulate:
|
|
172
|
+
repeating the marker with a new group adds to inherited groups rather than
|
|
173
|
+
replacing them, and `None` does not erase an inherited constraint. A group is
|
|
174
|
+
counted once per factory registration even if several ancestors declare it.
|
|
175
|
+
Unmarked classes' coincidental same-named legacy fields/helpers remain inert.
|
|
176
|
+
Subclasses of the bundled OTel factories therefore retain the view group when they
|
|
177
|
+
add their own group. Repeat the marker to add a group or customize the callback;
|
|
178
|
+
the callback is still notified once per factory. Factories with no marker anywhere
|
|
179
|
+
in their hierarchy retain their existing attributes/helpers. The generic plugin
|
|
180
|
+
base defines neither attribute nor hook. The registration API remains independent
|
|
181
|
+
of the removed provider API version 1 contract.
|
|
182
|
+
|
|
183
|
+
### Invocation worker lifecycle
|
|
184
|
+
|
|
185
|
+
The existing `on_invocation_start` and `on_invocation_end` hooks run on the
|
|
186
|
+
handler's invocation worker, both in registration order. Start hooks finish
|
|
187
|
+
before background checkpoint processing begins. The same worker runs the handler
|
|
188
|
+
(including its `finally` blocks), prepares the existing serialized output or
|
|
189
|
+
error, checkpoints large results when necessary, joins registered branches while
|
|
190
|
+
checkpointing is available, stops and waits for checkpoint processing, then calls
|
|
191
|
+
End before returning the outcome. The caller shuts down the handler executor.
|
|
192
|
+
There is no separate handler-context hook or context-manager plugin API.
|
|
193
|
+
|
|
194
|
+
When plugins are registered, the worker begins with a copy of the caller's
|
|
195
|
+
context-variable bindings. Start hooks, the handler, serialization, resource
|
|
196
|
+
cleanup and End hooks use that same Context. Changes made by a Start hook remain
|
|
197
|
+
visible to later hooks and handler code even if that hook raises; the SDK contains
|
|
198
|
+
the exception and retains the plugin without rolling back its bindings. A later
|
|
199
|
+
Start hook may replace an earlier binding. Plugins are responsible for restoring
|
|
200
|
+
partial bindings themselves when needed, either before raising or during End.
|
|
201
|
+
End runs in the same Context as Start, so context-variable tokens retain their
|
|
202
|
+
owner. End hooks retain forward registration order, not reverse stack order,
|
|
203
|
+
so they must not rely on observing a stack-like unwind of other plugins' contexts.
|
|
204
|
+
Each SDK-managed `map` or `parallel` branch admission, including an in-process
|
|
205
|
+
resume, receives a fresh copy of the coordinator's bindings when plugins are
|
|
206
|
+
registered. Branch changes remain local even when pool threads are reused.
|
|
207
|
+
User-created threads retain normal Python context-variable behavior; without
|
|
208
|
+
registered plugins, SDK branch submission keeps its existing behavior.
|
|
209
|
+
The worker's invocation context is discarded on return, including after plugin
|
|
210
|
+
cleanup failures, leaving the host's bindings unchanged. This isolates bindings,
|
|
211
|
+
not mutations to shared objects or external side effects. Without plugins, the
|
|
212
|
+
handler retains its existing fresh-worker context behavior.
|
|
213
|
+
|
|
214
|
+
Core 3 creates a fresh plugin instance per invocation through each registered
|
|
215
|
+
factory. OTel 2 requires core 3.x and uses the existing Start/End hooks on those
|
|
216
|
+
instances. Upgrade both packages together; core 3 rejects OTel 1.x registrations
|
|
217
|
+
and the removed provider API version 1 contract. The independent registration
|
|
218
|
+
API remains unchanged.
|
|
219
|
+
|
|
220
|
+
### Chained-invoke propagation
|
|
221
|
+
|
|
222
|
+
A new invoke START collects synchronous plugin metadata after resolving its
|
|
223
|
+
operation and target identity, before checkpointing. The SDK writes a non-blank
|
|
224
|
+
`x_amzn_trace_id` contribution to the flat
|
|
225
|
+
`ChainedInvokeOptions.XAmznTraceId` member. Function, tenant, payload, operation
|
|
226
|
+
name and identity remain unchanged. The member is omitted without a contribution.
|
|
227
|
+
Each invoke in a batch has its own metadata; this is not a request-wide header.
|
|
228
|
+
|
|
229
|
+
The SDK-owned frozen `PropagationInput` carries `execution_arn`, `operation_id`,
|
|
230
|
+
optional `parent_operation_id`, and `target_function_name`. Frozen
|
|
231
|
+
`PropagationMetadata` has optional `x_amzn_trace_id`. Neither type depends on
|
|
232
|
+
OpenTelemetry or generated service models. The optional synchronous plugin
|
|
233
|
+
`provide_propagation_metadata(info)` hook defaults to no contribution.
|
|
234
|
+
|
|
235
|
+
The collector keeps the first non-blank opaque value in configured order without
|
|
236
|
+
trimming it. Equal values do not conflict; different later values log both plugin
|
|
237
|
+
identities and a conflict count. Ordinary hook/getter/result/diagnostic failures
|
|
238
|
+
are isolated; cancellation and other `BaseException` control signals keep their
|
|
239
|
+
existing behavior. Replaying a checkpointed START, pending operation or terminal
|
|
240
|
+
result does not call the hook. If a START was never committed, a later attempt
|
|
241
|
+
can collect again; the callback is not exactly-once.
|
|
242
|
+
|
|
243
|
+
This requires boto3 1.43.114 or newer, whose botocore dependency includes the
|
|
244
|
+
published optional `ChainedInvokeOptions.XAmznTraceId` string field. The real
|
|
245
|
+
botocore tests exercise its parameter validation and wire serialization without
|
|
246
|
+
field removal, validation bypass or a model-capability fallback. Deployed backend
|
|
247
|
+
delivery and downstream topology still require separate validation.
|
|
248
|
+
Python has no `DistributedMapOptions` wrapper
|
|
249
|
+
or distributed-map START API: its existing map/parallel operations use CONTEXT.
|
|
250
|
+
The design's corresponding distributed-map field awaits that future modeled path.
|
|
251
|
+
|
|
252
|
+
This release pairs core 3 with OTel 2 and its per-invocation factories. Custom
|
|
253
|
+
plugins that omit the optional hook retain their existing tracing behavior.
|
|
254
|
+
Backend rollout and deployed downstream trace-topology validation remain pending.
|
|
255
|
+
|
|
256
|
+
## 🚀 Quick Start
|
|
257
|
+
|
|
258
|
+
Install the execution SDK:
|
|
259
|
+
|
|
260
|
+
```console
|
|
261
|
+
pip install aws-durable-execution-sdk-python
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Create a durable Lambda handler:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from aws_durable_execution_sdk_python import (
|
|
268
|
+
DurableContext,
|
|
269
|
+
StepContext,
|
|
270
|
+
durable_execution,
|
|
271
|
+
durable_step,
|
|
272
|
+
)
|
|
273
|
+
from aws_durable_execution_sdk_python.config import Duration
|
|
274
|
+
|
|
275
|
+
@durable_step
|
|
276
|
+
def validate_order(step_ctx: StepContext, order_id: str) -> dict:
|
|
277
|
+
step_ctx.logger.info("Validating order", extra={"order_id": order_id})
|
|
278
|
+
return {"order_id": order_id, "valid": True}
|
|
279
|
+
|
|
280
|
+
@durable_execution
|
|
281
|
+
def handler(event: dict, context: DurableContext) -> dict:
|
|
282
|
+
order_id = event["order_id"]
|
|
283
|
+
context.logger.info("Starting workflow", extra={"order_id": order_id})
|
|
284
|
+
|
|
285
|
+
validation = context.step(validate_order(order_id), name="validate_order")
|
|
286
|
+
if not validation["valid"]:
|
|
287
|
+
return {"status": "rejected", "order_id": order_id}
|
|
288
|
+
|
|
289
|
+
# simulate approval (real world: use wait_for_callback)
|
|
290
|
+
context.wait(duration=Duration.from_seconds(5), name="await_confirmation")
|
|
291
|
+
|
|
292
|
+
return {"status": "approved", "order_id": order_id}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## 📚 Documentation
|
|
296
|
+
|
|
297
|
+
The complete documentation for the AWS Durable Execution SDK for Python lives on the AWS Documentation site:
|
|
298
|
+
|
|
299
|
+
- **[AWS Durable Execution Documentation](https://docs.aws.amazon.com/durable-execution/)** - Concepts, getting started, core operations, advanced topics, and API reference
|
|
300
|
+
- **[AWS Lambda Durable Functions Guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)** - How durable functions work on Lambda
|
|
301
|
+
|
|
302
|
+
## 💬 Feedback & Support
|
|
303
|
+
|
|
304
|
+
- [Bug report](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=bug_report.yml)
|
|
305
|
+
- [Feature request](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=feature_request.yml)
|
|
306
|
+
- [Documentation feedback](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=documentation.yml)
|
|
307
|
+
- [Contributing guide](CONTRIBUTING.md)
|
|
308
|
+
|
|
309
|
+
## 📄 License
|
|
310
|
+
|
|
311
|
+
See the [LICENSE](https://github.com/aws/aws-durable-execution-sdk-python/blob/main/LICENSE) file for our project's licensing.
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# AWS Durable Execution SDK for Python
|
|
2
|
+
|
|
3
|
+
[](https://github.com/aws/aws-durable-execution-sdk-python/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/aws-durable-execution-sdk-python)
|
|
5
|
+
[](https://pypi.org/project/aws-durable-execution-sdk-python)
|
|
6
|
+
[](https://scorecard.dev/viewer/?uri=github.com/aws/aws-durable-execution-sdk-python)
|
|
7
|
+
[](https://github.com/aws/aws-durable-execution-sdk-python/blob/main/LICENSE)
|
|
8
|
+
|
|
9
|
+
-----
|
|
10
|
+
|
|
11
|
+
Build reliable, long-running AWS Lambda workflows with checkpointed steps, waits, callbacks, and parallel execution.
|
|
12
|
+
|
|
13
|
+
## ✨ Key Features
|
|
14
|
+
|
|
15
|
+
- **Automatic checkpointing** - Resume execution after Lambda pauses or restarts
|
|
16
|
+
- **Durable steps** - Run work with retry strategies and deterministic replay
|
|
17
|
+
- **Waits and callbacks** - Pause for time or external signals without blocking Lambda
|
|
18
|
+
- **Parallel and map operations** - Fan out work with configurable completion criteria
|
|
19
|
+
- **Child contexts** - Structure complex workflows into isolated subflows
|
|
20
|
+
- **Replay-safe logging** - Use `context.logger` for structured, de-duplicated logs
|
|
21
|
+
- **Local and cloud testing** - Validate workflows with the testing SDK
|
|
22
|
+
|
|
23
|
+
## 📦 Packages
|
|
24
|
+
|
|
25
|
+
| Package | Description | Version |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `aws-durable-execution-sdk-python` | Execution SDK for Lambda durable functions | [](https://pypi.org/project/aws-durable-execution-sdk-python) |
|
|
28
|
+
| `aws-durable-execution-sdk-python-testing` | Local/cloud test runner and pytest helpers | [](https://pypi.org/project/aws-durable-execution-sdk-python-testing) |
|
|
29
|
+
|
|
30
|
+
## Dynamic instrumentation plugins
|
|
31
|
+
|
|
32
|
+
Upgrading an existing application? Follow the [2.x to 3.0 migration guide](https://github.com/aws/aws-durable-execution-sdk-python/blob/main/docs/migration-2.x-to-3.0.md)
|
|
33
|
+
for dependency versions, before/after examples, provider packaging, and validation.
|
|
34
|
+
|
|
35
|
+
Instrumentation plugins can be selected at Lambda cold start without importing
|
|
36
|
+
them in the function artifact. Install a provider package in the function or a
|
|
37
|
+
Lambda layer, then set an ordered allow-list:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
DURABLE_EXECUTION_PLUGINS=otel-invocation,example_audit
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The SDK resolves those names from the `aws_durable_execution.plugins` Python
|
|
44
|
+
entry-point group when the decorated handler is initialized. An unset or blank
|
|
45
|
+
variable preserves the existing behavior. The decorator's `plugins` argument
|
|
46
|
+
remains supported; explicit factories run first, and a factory passed to the
|
|
47
|
+
decorator is not registered a second time through the environment.
|
|
48
|
+
|
|
49
|
+
A plugin is registered as a *factory*, not as an instance. A factory is an object
|
|
50
|
+
with a `create_plugin(info)` method taking the invocation's `InvocationStartInfo`
|
|
51
|
+
and returning a `DurableInstrumentationPlugin`; the SDK calls that method once per
|
|
52
|
+
invocation, so the instance it returns serves that one invocation only and can
|
|
53
|
+
hold **per-invocation** state in ordinary attributes.
|
|
54
|
+
|
|
55
|
+
Per-invocation is narrower than per-execution, and the difference matters. A
|
|
56
|
+
durable execution spans as many invocations as it waits, retries or resumes, and
|
|
57
|
+
the instance is dropped when each of those returns — so anything a plugin keeps in
|
|
58
|
+
its attributes is gone by the next invocation of the same execution. State that
|
|
59
|
+
has to survive that has two honest homes: rebuild it from the operation map the
|
|
60
|
+
invocation hooks carry (`InvocationStartInfo.operations` is a full snapshot,
|
|
61
|
+
which is how the bundled Telemetry exporter reports operations that completed in an
|
|
62
|
+
earlier invocation), or put it on the factory, which outlives every invocation —
|
|
63
|
+
keyed by execution ARN, and pruned by the owner, because the SDK will not tell the
|
|
64
|
+
factory when an execution ends for good.
|
|
65
|
+
|
|
66
|
+
A factory is an object with a method rather than a plain callable so the
|
|
67
|
+
registration type can grow a second, optional member later -- a process-level
|
|
68
|
+
flush on execution-environment shutdown, for example -- without a second breaking
|
|
69
|
+
change to this surface.
|
|
70
|
+
|
|
71
|
+
Write a small factory class and construct the plugin in its `create_plugin`. The
|
|
72
|
+
factory holds what outlives an invocation, such as an exporter or a resolved
|
|
73
|
+
configuration, and setup work that can fail belongs in the factory's own
|
|
74
|
+
constructor rather than the plugin's:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
class AuditPluginFactory:
|
|
78
|
+
def __init__(self, sink):
|
|
79
|
+
self._sink = sink
|
|
80
|
+
|
|
81
|
+
def create_plugin(self, info):
|
|
82
|
+
return AuditPlugin(self._sink)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
plugins=[AuditPluginFactory(sink)]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
A plugin class is not a factory, and neither is a bare callable. `plugins=[MyPlugin]`
|
|
89
|
+
and `plugins=[lambda info: MyPlugin(sink)]` raise `PluginLoadError` during handler
|
|
90
|
+
initialization, because neither carries `create_plugin`. A class that declares
|
|
91
|
+
`create_plugin` as a `@classmethod` is accepted, since the requirement is the
|
|
92
|
+
member and not the kind of object.
|
|
93
|
+
|
|
94
|
+
Provider packages expose such a factory:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from aws_durable_execution_sdk_python.plugin import (
|
|
98
|
+
DurableInstrumentationPlugin,
|
|
99
|
+
InvocationStartInfo,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AuditPlugin(DurableInstrumentationPlugin):
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AuditPluginFactory:
|
|
108
|
+
def create_plugin(self, info: InvocationStartInfo) -> AuditPlugin:
|
|
109
|
+
return AuditPlugin()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
AUDIT_PLUGIN_FACTORY = AuditPluginFactory()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Register the factory instance in the package's `pyproject.toml`:
|
|
116
|
+
|
|
117
|
+
```toml
|
|
118
|
+
[project.entry-points."aws_durable_execution.plugins"]
|
|
119
|
+
example_audit = "example_audit:AUDIT_PLUGIN_FACTORY"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Provider names must be unique across installed distributions. Missing,
|
|
123
|
+
ambiguous, or wrongly shaped providers raise `PluginLoadError` during handler
|
|
124
|
+
initialization with the provider and distribution details.
|
|
125
|
+
|
|
126
|
+
### Selecting an OpenTelemetry view
|
|
127
|
+
|
|
128
|
+
Enable at most one of `InvocationOtelPluginFactory` and
|
|
129
|
+
`ExecutionOtelPluginFactory`. This
|
|
130
|
+
constraint applies to the combined `plugins=[...]` argument and
|
|
131
|
+
`DURABLE_EXECUTION_PLUGINS=otel-invocation` or `otel-execution` selection.
|
|
132
|
+
Core 3 with OTel 2 rejects both at cold start with `PluginLoadError` naming the
|
|
133
|
+
conflicting views; keep only one. Choose Invocation for work within each Lambda
|
|
134
|
+
invocation or Execution for logical operations across the durable execution.
|
|
135
|
+
Unrelated instrumentation plugin factories can run alongside either view.
|
|
136
|
+
OTel 2 requires core 3.x and the per-invocation factory contract. Release the
|
|
137
|
+
coordinated core version before releasing OTel 2.
|
|
138
|
+
|
|
139
|
+
Plugin authors explicitly opt in by declaring
|
|
140
|
+
`__durable_registration_api__ = 1` on a factory class. That class and its subclasses
|
|
141
|
+
have their optional `exclusive_group` metadata validated and their
|
|
142
|
+
optional `on_registration_result(registered)` callback invoked. A missing group
|
|
143
|
+
or `None` adds no exclusivity constraint. The callback can release rejected
|
|
144
|
+
constructor resources and must preserve resources from earlier accepted use.
|
|
145
|
+
|
|
146
|
+
The first class declaring the marker in the method resolution order gates
|
|
147
|
+
registration and selects the callback contract. An explicit marker other than
|
|
148
|
+
the integer `1` shadows an ancestor's opt-in and disables this capability.
|
|
149
|
+
Once enabled, every class in the MRO explicitly declaring the integer `1`
|
|
150
|
+
contributes its resolved `exclusive_group`. These constraints accumulate:
|
|
151
|
+
repeating the marker with a new group adds to inherited groups rather than
|
|
152
|
+
replacing them, and `None` does not erase an inherited constraint. A group is
|
|
153
|
+
counted once per factory registration even if several ancestors declare it.
|
|
154
|
+
Unmarked classes' coincidental same-named legacy fields/helpers remain inert.
|
|
155
|
+
Subclasses of the bundled OTel factories therefore retain the view group when they
|
|
156
|
+
add their own group. Repeat the marker to add a group or customize the callback;
|
|
157
|
+
the callback is still notified once per factory. Factories with no marker anywhere
|
|
158
|
+
in their hierarchy retain their existing attributes/helpers. The generic plugin
|
|
159
|
+
base defines neither attribute nor hook. The registration API remains independent
|
|
160
|
+
of the removed provider API version 1 contract.
|
|
161
|
+
|
|
162
|
+
### Invocation worker lifecycle
|
|
163
|
+
|
|
164
|
+
The existing `on_invocation_start` and `on_invocation_end` hooks run on the
|
|
165
|
+
handler's invocation worker, both in registration order. Start hooks finish
|
|
166
|
+
before background checkpoint processing begins. The same worker runs the handler
|
|
167
|
+
(including its `finally` blocks), prepares the existing serialized output or
|
|
168
|
+
error, checkpoints large results when necessary, joins registered branches while
|
|
169
|
+
checkpointing is available, stops and waits for checkpoint processing, then calls
|
|
170
|
+
End before returning the outcome. The caller shuts down the handler executor.
|
|
171
|
+
There is no separate handler-context hook or context-manager plugin API.
|
|
172
|
+
|
|
173
|
+
When plugins are registered, the worker begins with a copy of the caller's
|
|
174
|
+
context-variable bindings. Start hooks, the handler, serialization, resource
|
|
175
|
+
cleanup and End hooks use that same Context. Changes made by a Start hook remain
|
|
176
|
+
visible to later hooks and handler code even if that hook raises; the SDK contains
|
|
177
|
+
the exception and retains the plugin without rolling back its bindings. A later
|
|
178
|
+
Start hook may replace an earlier binding. Plugins are responsible for restoring
|
|
179
|
+
partial bindings themselves when needed, either before raising or during End.
|
|
180
|
+
End runs in the same Context as Start, so context-variable tokens retain their
|
|
181
|
+
owner. End hooks retain forward registration order, not reverse stack order,
|
|
182
|
+
so they must not rely on observing a stack-like unwind of other plugins' contexts.
|
|
183
|
+
Each SDK-managed `map` or `parallel` branch admission, including an in-process
|
|
184
|
+
resume, receives a fresh copy of the coordinator's bindings when plugins are
|
|
185
|
+
registered. Branch changes remain local even when pool threads are reused.
|
|
186
|
+
User-created threads retain normal Python context-variable behavior; without
|
|
187
|
+
registered plugins, SDK branch submission keeps its existing behavior.
|
|
188
|
+
The worker's invocation context is discarded on return, including after plugin
|
|
189
|
+
cleanup failures, leaving the host's bindings unchanged. This isolates bindings,
|
|
190
|
+
not mutations to shared objects or external side effects. Without plugins, the
|
|
191
|
+
handler retains its existing fresh-worker context behavior.
|
|
192
|
+
|
|
193
|
+
Core 3 creates a fresh plugin instance per invocation through each registered
|
|
194
|
+
factory. OTel 2 requires core 3.x and uses the existing Start/End hooks on those
|
|
195
|
+
instances. Upgrade both packages together; core 3 rejects OTel 1.x registrations
|
|
196
|
+
and the removed provider API version 1 contract. The independent registration
|
|
197
|
+
API remains unchanged.
|
|
198
|
+
|
|
199
|
+
### Chained-invoke propagation
|
|
200
|
+
|
|
201
|
+
A new invoke START collects synchronous plugin metadata after resolving its
|
|
202
|
+
operation and target identity, before checkpointing. The SDK writes a non-blank
|
|
203
|
+
`x_amzn_trace_id` contribution to the flat
|
|
204
|
+
`ChainedInvokeOptions.XAmznTraceId` member. Function, tenant, payload, operation
|
|
205
|
+
name and identity remain unchanged. The member is omitted without a contribution.
|
|
206
|
+
Each invoke in a batch has its own metadata; this is not a request-wide header.
|
|
207
|
+
|
|
208
|
+
The SDK-owned frozen `PropagationInput` carries `execution_arn`, `operation_id`,
|
|
209
|
+
optional `parent_operation_id`, and `target_function_name`. Frozen
|
|
210
|
+
`PropagationMetadata` has optional `x_amzn_trace_id`. Neither type depends on
|
|
211
|
+
OpenTelemetry or generated service models. The optional synchronous plugin
|
|
212
|
+
`provide_propagation_metadata(info)` hook defaults to no contribution.
|
|
213
|
+
|
|
214
|
+
The collector keeps the first non-blank opaque value in configured order without
|
|
215
|
+
trimming it. Equal values do not conflict; different later values log both plugin
|
|
216
|
+
identities and a conflict count. Ordinary hook/getter/result/diagnostic failures
|
|
217
|
+
are isolated; cancellation and other `BaseException` control signals keep their
|
|
218
|
+
existing behavior. Replaying a checkpointed START, pending operation or terminal
|
|
219
|
+
result does not call the hook. If a START was never committed, a later attempt
|
|
220
|
+
can collect again; the callback is not exactly-once.
|
|
221
|
+
|
|
222
|
+
This requires boto3 1.43.114 or newer, whose botocore dependency includes the
|
|
223
|
+
published optional `ChainedInvokeOptions.XAmznTraceId` string field. The real
|
|
224
|
+
botocore tests exercise its parameter validation and wire serialization without
|
|
225
|
+
field removal, validation bypass or a model-capability fallback. Deployed backend
|
|
226
|
+
delivery and downstream topology still require separate validation.
|
|
227
|
+
Python has no `DistributedMapOptions` wrapper
|
|
228
|
+
or distributed-map START API: its existing map/parallel operations use CONTEXT.
|
|
229
|
+
The design's corresponding distributed-map field awaits that future modeled path.
|
|
230
|
+
|
|
231
|
+
This release pairs core 3 with OTel 2 and its per-invocation factories. Custom
|
|
232
|
+
plugins that omit the optional hook retain their existing tracing behavior.
|
|
233
|
+
Backend rollout and deployed downstream trace-topology validation remain pending.
|
|
234
|
+
|
|
235
|
+
## 🚀 Quick Start
|
|
236
|
+
|
|
237
|
+
Install the execution SDK:
|
|
238
|
+
|
|
239
|
+
```console
|
|
240
|
+
pip install aws-durable-execution-sdk-python
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Create a durable Lambda handler:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from aws_durable_execution_sdk_python import (
|
|
247
|
+
DurableContext,
|
|
248
|
+
StepContext,
|
|
249
|
+
durable_execution,
|
|
250
|
+
durable_step,
|
|
251
|
+
)
|
|
252
|
+
from aws_durable_execution_sdk_python.config import Duration
|
|
253
|
+
|
|
254
|
+
@durable_step
|
|
255
|
+
def validate_order(step_ctx: StepContext, order_id: str) -> dict:
|
|
256
|
+
step_ctx.logger.info("Validating order", extra={"order_id": order_id})
|
|
257
|
+
return {"order_id": order_id, "valid": True}
|
|
258
|
+
|
|
259
|
+
@durable_execution
|
|
260
|
+
def handler(event: dict, context: DurableContext) -> dict:
|
|
261
|
+
order_id = event["order_id"]
|
|
262
|
+
context.logger.info("Starting workflow", extra={"order_id": order_id})
|
|
263
|
+
|
|
264
|
+
validation = context.step(validate_order(order_id), name="validate_order")
|
|
265
|
+
if not validation["valid"]:
|
|
266
|
+
return {"status": "rejected", "order_id": order_id}
|
|
267
|
+
|
|
268
|
+
# simulate approval (real world: use wait_for_callback)
|
|
269
|
+
context.wait(duration=Duration.from_seconds(5), name="await_confirmation")
|
|
270
|
+
|
|
271
|
+
return {"status": "approved", "order_id": order_id}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## 📚 Documentation
|
|
275
|
+
|
|
276
|
+
The complete documentation for the AWS Durable Execution SDK for Python lives on the AWS Documentation site:
|
|
277
|
+
|
|
278
|
+
- **[AWS Durable Execution Documentation](https://docs.aws.amazon.com/durable-execution/)** - Concepts, getting started, core operations, advanced topics, and API reference
|
|
279
|
+
- **[AWS Lambda Durable Functions Guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)** - How durable functions work on Lambda
|
|
280
|
+
|
|
281
|
+
## 💬 Feedback & Support
|
|
282
|
+
|
|
283
|
+
- [Bug report](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=bug_report.yml)
|
|
284
|
+
- [Feature request](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=feature_request.yml)
|
|
285
|
+
- [Documentation feedback](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=documentation.yml)
|
|
286
|
+
- [Contributing guide](CONTRIBUTING.md)
|
|
287
|
+
|
|
288
|
+
## 📄 License
|
|
289
|
+
|
|
290
|
+
See the [LICENSE](https://github.com/aws/aws-durable-execution-sdk-python/blob/main/LICENSE) file for our project's licensing.
|
{aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/pyproject.toml
RENAMED
|
@@ -21,7 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
22
22
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
23
23
|
]
|
|
24
|
-
dependencies = ["boto3>=1.
|
|
24
|
+
dependencies = ["boto3>=1.43.114"]
|
|
25
25
|
|
|
26
26
|
[project.urls]
|
|
27
27
|
Documentation = "https://github.com/aws/aws-durable-execution-sdk-python#readme"
|