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.
Files changed (109) hide show
  1. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/.gitignore +1 -0
  2. aws_durable_execution_sdk_python-3.0.0/PKG-INFO +311 -0
  3. aws_durable_execution_sdk_python-3.0.0/README.md +290 -0
  4. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/pyproject.toml +1 -1
  5. {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
  6. {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
  7. {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
  8. {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
  9. {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
  10. aws_durable_execution_sdk_python-3.0.0/src/aws_durable_execution_sdk_python/execution.py +584 -0
  11. {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
  12. {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
  13. {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
  14. {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
  15. {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
  16. {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
  17. aws_durable_execution_sdk_python-3.0.0/src/aws_durable_execution_sdk_python/plugin.py +2035 -0
  18. aws_durable_execution_sdk_python-3.0.0/src/aws_durable_execution_sdk_python/plugin_discovery.py +517 -0
  19. {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
  20. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/concurrency_test.py +237 -19
  21. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/config_test.py +65 -3
  22. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/context_test.py +297 -45
  23. aws_durable_execution_sdk_python-3.0.0/tests/e2e/batch_result_none_replay_test.py +83 -0
  24. aws_durable_execution_sdk_python-3.0.0/tests/e2e/branch_worker_context_test.py +138 -0
  25. aws_durable_execution_sdk_python-3.0.0/tests/e2e/custom_subtype_int_test.py +165 -0
  26. aws_durable_execution_sdk_python-3.0.0/tests/e2e/factory_registration_cleanup_int_test.py +93 -0
  27. aws_durable_execution_sdk_python-3.0.0/tests/e2e/factory_registration_int_test.py +96 -0
  28. aws_durable_execution_sdk_python-3.0.0/tests/e2e/handler_context_propagation_int_test.py +296 -0
  29. aws_durable_execution_sdk_python-3.0.0/tests/e2e/handler_invocation_context_int_test.py +82 -0
  30. aws_durable_execution_sdk_python-3.0.0/tests/e2e/invoke_propagation_int_test.py +286 -0
  31. aws_durable_execution_sdk_python-3.0.0/tests/e2e/plugin_hook_contract_int_test.py +383 -0
  32. {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
  33. {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
  34. aws_durable_execution_sdk_python-3.0.0/tests/e2e/plugin_operation_type_int_test.py +205 -0
  35. {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
  36. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/execution_test.py +1011 -27
  37. aws_durable_execution_sdk_python-3.0.0/tests/factory_registration_test.py +475 -0
  38. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/filesystem_serdes_test.py +5 -1
  39. aws_durable_execution_sdk_python-3.0.0/tests/handler_worker_context_test.py +591 -0
  40. aws_durable_execution_sdk_python-3.0.0/tests/identifier_test.py +134 -0
  41. aws_durable_execution_sdk_python-3.0.0/tests/invoke_wire_propagation_test.py +144 -0
  42. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/lambda_service_test.py +257 -78
  43. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/logger_test.py +14 -2
  44. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/base_test.py +4 -1
  45. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/callback_test.py +140 -34
  46. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/child_test.py +278 -67
  47. aws_durable_execution_sdk_python-3.0.0/tests/operation/invoke_propagation_test.py +174 -0
  48. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/invoke_test.py +177 -58
  49. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/map_test.py +103 -15
  50. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/parallel_test.py +103 -15
  51. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/step_test.py +231 -43
  52. {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
  53. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/wait_test.py +48 -11
  54. aws_durable_execution_sdk_python-3.0.0/tests/plugin_discovery_test.py +844 -0
  55. aws_durable_execution_sdk_python-3.0.0/tests/plugin_hook_registry_test.py +1704 -0
  56. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/plugin_test.py +964 -91
  57. aws_durable_execution_sdk_python-3.0.0/tests/propagation_test.py +198 -0
  58. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/state_test.py +645 -79
  59. aws_durable_execution_sdk_python-3.0.0/tests/test_helpers.py +79 -0
  60. aws_durable_execution_sdk_python-2.0.1/PKG-INFO +0 -156
  61. aws_durable_execution_sdk_python-2.0.1/README.md +0 -135
  62. aws_durable_execution_sdk_python-2.0.1/src/aws_durable_execution_sdk_python/execution.py +0 -469
  63. aws_durable_execution_sdk_python-2.0.1/src/aws_durable_execution_sdk_python/plugin.py +0 -863
  64. aws_durable_execution_sdk_python-2.0.1/src/aws_durable_execution_sdk_python/plugin_discovery.py +0 -209
  65. aws_durable_execution_sdk_python-2.0.1/tests/plugin_discovery_test.py +0 -479
  66. aws_durable_execution_sdk_python-2.0.1/tests/test_helpers.py +0 -20
  67. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/LICENSE +0 -0
  68. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/NOTICE +0 -0
  69. {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
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {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
  76. {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
  77. {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
  78. {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
  79. {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
  80. {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
  81. {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
  82. {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
  83. {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
  84. {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
  85. {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
  86. {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
  87. {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
  88. {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
  89. {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
  90. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/__init__.py +0 -0
  91. {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
  92. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/__init__.py +0 -0
  93. {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
  94. {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
  95. {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
  96. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/e2e/execution_int_test.py +0 -0
  97. {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
  98. {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
  99. {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
  100. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/exceptions_test.py +0 -0
  101. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/operation/__init__.py +0 -0
  102. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/preview_test.py +0 -0
  103. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/retries_test.py +0 -0
  104. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/serdes_test.py +0 -0
  105. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/suspend_test.py +0 -0
  106. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/threading_test.py +0 -0
  107. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/types_test.py +0 -0
  108. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/waits_test.py +0 -0
  109. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-3.0.0}/tests/with_retry_test.py +0 -0
@@ -34,6 +34,7 @@ dist/
34
34
  **/build/
35
35
  **/*.zip
36
36
  .aws-sam/
37
+ .microvm-image-build/
37
38
  **/template.generated.json
38
39
 
39
40
  .env
@@ -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
+ [![Build](https://github.com/aws/aws-durable-execution-sdk-python/actions/workflows/ci.yml/badge.svg)](https://github.com/aws/aws-durable-execution-sdk-python/actions/workflows/ci.yml)
25
+ [![PyPI - Version](https://img.shields.io/pypi/v/aws-durable-execution-sdk-python.svg)](https://pypi.org/project/aws-durable-execution-sdk-python)
26
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aws-durable-execution-sdk-python.svg)](https://pypi.org/project/aws-durable-execution-sdk-python)
27
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/aws/aws-durable-execution-sdk-python/badge)](https://scorecard.dev/viewer/?uri=github.com/aws/aws-durable-execution-sdk-python)
28
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](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 | [![PyPI - Version](https://img.shields.io/pypi/v/aws-durable-execution-sdk-python.svg)](https://pypi.org/project/aws-durable-execution-sdk-python) |
49
+ | `aws-durable-execution-sdk-python-testing` | Local/cloud test runner and pytest helpers | [![PyPI - Version](https://img.shields.io/pypi/v/aws-durable-execution-sdk-python-testing.svg)](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
+ [![Build](https://github.com/aws/aws-durable-execution-sdk-python/actions/workflows/ci.yml/badge.svg)](https://github.com/aws/aws-durable-execution-sdk-python/actions/workflows/ci.yml)
4
+ [![PyPI - Version](https://img.shields.io/pypi/v/aws-durable-execution-sdk-python.svg)](https://pypi.org/project/aws-durable-execution-sdk-python)
5
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aws-durable-execution-sdk-python.svg)](https://pypi.org/project/aws-durable-execution-sdk-python)
6
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/aws/aws-durable-execution-sdk-python/badge)](https://scorecard.dev/viewer/?uri=github.com/aws/aws-durable-execution-sdk-python)
7
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](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 | [![PyPI - Version](https://img.shields.io/pypi/v/aws-durable-execution-sdk-python.svg)](https://pypi.org/project/aws-durable-execution-sdk-python) |
28
+ | `aws-durable-execution-sdk-python-testing` | Local/cloud test runner and pytest helpers | [![PyPI - Version](https://img.shields.io/pypi/v/aws-durable-execution-sdk-python-testing.svg)](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.
@@ -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.42.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"
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Amazon.com, Inc. or its affiliates.
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
- __version__ = "2.0.1"
4
+ __version__ = "3.0.0"