aws-durable-execution-sdk-python 2.0.1__tar.gz → 2.1.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 (97) hide show
  1. aws_durable_execution_sdk_python-2.1.0/PKG-INFO +261 -0
  2. aws_durable_execution_sdk_python-2.1.0/README.md +240 -0
  3. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/pyproject.toml +1 -1
  4. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/__about__.py +1 -1
  5. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/concurrency/executor.py +22 -8
  6. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/context.py +31 -13
  7. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/execution.py +230 -125
  8. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/identifier.py +4 -4
  9. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/lambda_service.py +10 -27
  10. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/child.py +30 -0
  11. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/invoke.py +6 -0
  12. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/plugin.py +169 -35
  13. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/plugin_discovery.py +146 -2
  14. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/state.py +141 -58
  15. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/concurrency_test.py +47 -1
  16. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/context_test.py +176 -21
  17. aws_durable_execution_sdk_python-2.1.0/tests/e2e/branch_worker_context_test.py +129 -0
  18. aws_durable_execution_sdk_python-2.1.0/tests/e2e/handler_invocation_context_int_test.py +79 -0
  19. aws_durable_execution_sdk_python-2.1.0/tests/e2e/invoke_propagation_int_test.py +272 -0
  20. aws_durable_execution_sdk_python-2.1.0/tests/e2e/plugin_operation_type_int_test.py +202 -0
  21. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/execution_test.py +433 -3
  22. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/filesystem_serdes_test.py +5 -1
  23. aws_durable_execution_sdk_python-2.1.0/tests/handler_worker_context_test.py +589 -0
  24. aws_durable_execution_sdk_python-2.1.0/tests/identifier_test.py +56 -0
  25. aws_durable_execution_sdk_python-2.1.0/tests/invoke_wire_propagation_test.py +144 -0
  26. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/lambda_service_test.py +85 -11
  27. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/logger_test.py +14 -2
  28. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/base_test.py +4 -1
  29. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/callback_test.py +140 -34
  30. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/child_test.py +249 -35
  31. aws_durable_execution_sdk_python-2.1.0/tests/operation/invoke_propagation_test.py +173 -0
  32. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/invoke_test.py +177 -58
  33. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/map_test.py +103 -15
  34. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/parallel_test.py +103 -15
  35. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/step_test.py +222 -34
  36. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/wait_for_condition_test.py +225 -45
  37. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/wait_test.py +48 -11
  38. aws_durable_execution_sdk_python-2.1.0/tests/plugin_discovery_test.py +1002 -0
  39. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/plugin_test.py +46 -0
  40. aws_durable_execution_sdk_python-2.1.0/tests/propagation_test.py +213 -0
  41. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/state_test.py +589 -44
  42. aws_durable_execution_sdk_python-2.0.1/PKG-INFO +0 -156
  43. aws_durable_execution_sdk_python-2.0.1/README.md +0 -135
  44. aws_durable_execution_sdk_python-2.0.1/tests/plugin_discovery_test.py +0 -479
  45. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/.gitignore +0 -0
  46. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/LICENSE +0 -0
  47. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/NOTICE +0 -0
  48. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/.gitignore +0 -0
  49. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/__init__.py +0 -0
  50. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/concurrency/__init__.py +0 -0
  51. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/concurrency/models.py +0 -0
  52. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/config.py +0 -0
  53. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/constants.py +0 -0
  54. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/exceptions.py +0 -0
  55. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/filesystem_serdes.py +0 -0
  56. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/logger.py +0 -0
  57. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/__init__.py +0 -0
  58. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/base.py +0 -0
  59. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/callback.py +0 -0
  60. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/map.py +0 -0
  61. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/parallel.py +0 -0
  62. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/step.py +0 -0
  63. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/wait.py +0 -0
  64. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/operation/wait_for_condition.py +0 -0
  65. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/preview.py +0 -0
  66. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/py.typed +0 -0
  67. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/retries.py +0 -0
  68. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/serdes.py +0 -0
  69. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/suspend.py +0 -0
  70. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/threading.py +0 -0
  71. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/types.py +0 -0
  72. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/src/aws_durable_execution_sdk_python/waits.py +0 -0
  73. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/__init__.py +0 -0
  74. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/config_test.py +0 -0
  75. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/durable_executions_python_language_sdk_test.py +0 -0
  76. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/__init__.py +0 -0
  77. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/checkpoint_response_int_test.py +0 -0
  78. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/custom_serdes_roundtrip_int_test.py +0 -0
  79. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/error_hierarchy_int_test.py +0 -0
  80. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/execution_int_test.py +0 -0
  81. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/filesystem_serdes_int_test.py +0 -0
  82. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/map_with_concurrent_waits_int_test.py +0 -0
  83. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/plugin_invocation_operations_int_test.py +0 -0
  84. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/plugin_invocation_payload_int_test.py +0 -0
  85. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/plugin_user_function_lifecycle_int_test.py +0 -0
  86. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/e2e/should_complete_suspend_resume_int_test.py +0 -0
  87. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/exceptions_test.py +0 -0
  88. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/operation/__init__.py +0 -0
  89. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/preview_test.py +0 -0
  90. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/retries_test.py +0 -0
  91. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/serdes_test.py +0 -0
  92. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/suspend_test.py +0 -0
  93. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/test_helpers.py +0 -0
  94. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/threading_test.py +0 -0
  95. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/types_test.py +0 -0
  96. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/waits_test.py +0 -0
  97. {aws_durable_execution_sdk_python-2.0.1 → aws_durable_execution_sdk_python-2.1.0}/tests/with_retry_test.py +0 -0
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.5
2
+ Name: aws-durable-execution-sdk-python
3
+ Version: 2.1.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
+ Instrumentation plugins can be selected at Lambda cold start without importing
54
+ them in the function artifact. Install a provider package in the function or a
55
+ Lambda layer, then set an ordered allow-list:
56
+
57
+ ```text
58
+ DURABLE_EXECUTION_PLUGINS=otel-invocation,example_audit
59
+ ```
60
+
61
+ The SDK resolves those names from the `aws_durable_execution.plugins` Python
62
+ entry-point group when the decorated handler is initialized. An unset or blank
63
+ variable preserves the existing behavior. The decorator's `plugins` argument
64
+ remains supported; explicit plugins run first and take precedence when a
65
+ dynamic provider creates the same concrete plugin type.
66
+
67
+ Provider packages expose a versioned factory:
68
+
69
+ ```python
70
+ from aws_durable_execution_sdk_python.plugin import (
71
+ DurableInstrumentationPlugin,
72
+ DurableInstrumentationPluginProvider,
73
+ )
74
+
75
+
76
+ class AuditPlugin(DurableInstrumentationPlugin):
77
+ pass
78
+
79
+
80
+ AUDIT_PLUGIN_PROVIDER = DurableInstrumentationPluginProvider(
81
+ plugin_type=AuditPlugin,
82
+ factory=AuditPlugin,
83
+ plugin_api_version=1,
84
+ )
85
+ ```
86
+
87
+ Register the provider in the package's `pyproject.toml`:
88
+
89
+ ```toml
90
+ [project.entry-points."aws_durable_execution.plugins"]
91
+ example_audit = "example_audit:AUDIT_PLUGIN_PROVIDER"
92
+ ```
93
+
94
+ Set `plugin_api_version` to the literal API version the provider implements.
95
+ Update it only after verifying the provider against that API version.
96
+
97
+ Provider names must be unique across installed distributions. Missing,
98
+ ambiguous, incompatible, or invalid providers raise `PluginLoadError` during
99
+ handler initialization with the provider and distribution details.
100
+
101
+ ### Selecting an OpenTelemetry view
102
+
103
+ Enable at most one of `InvocationOtelPlugin` and `ExecutionOtelPlugin`. This
104
+ constraint applies to the combined `plugins=[...]` argument and
105
+ `DURABLE_EXECUTION_PLUGINS=otel-invocation` or `otel-execution` selection.
106
+ Core 2.1+ with OTel 1.1+ rejects both at cold start with `PluginLoadError` naming the
107
+ conflicting views; keep only one. Choose Invocation for work within each Lambda
108
+ invocation or Execution for logical operations across the durable execution.
109
+ Unrelated instrumentation plugins can run alongside either view. OTel 1.1 requires core 2.1 or later for the invocation-worker lifecycle as well
110
+ as view-exclusivity validation. Release core 2.1 before releasing OTel 1.1.
111
+
112
+ Plugin authors explicitly opt in by declaring
113
+ `__durable_registration_api__ = 1` on a plugin class. That class and its subclasses
114
+ have their optional `exclusive_group` metadata validated and their
115
+ optional `on_registration_result(registered)` callback invoked. A missing group
116
+ or `None` adds no exclusivity constraint. The callback can release rejected
117
+ constructor resources and must preserve resources from earlier accepted use.
118
+
119
+ The first class declaring the marker in the method resolution order gates
120
+ registration and selects the callback contract. An explicit marker other than
121
+ the integer `1` shadows an ancestor's opt-in and disables this capability.
122
+ Once enabled, every class in the MRO explicitly declaring the integer `1`
123
+ contributes its resolved `exclusive_group`. These constraints accumulate:
124
+ repeating the marker with a new group adds to inherited groups rather than
125
+ replacing them, and `None` does not erase an inherited constraint. A group is
126
+ counted once per plugin registration even if several ancestors declare it.
127
+ Unmarked classes' coincidental same-named legacy fields/helpers remain inert.
128
+ Subclasses of the bundled OTel views therefore retain the view group when they
129
+ add their own group. Repeat the marker to add a group or customize the callback;
130
+ the callback is still notified once per plugin. Plugins with no marker anywhere
131
+ in their hierarchy retain their existing attributes/helpers. The generic plugin base defines
132
+ neither attribute nor hook, and provider API version 1 and existing plugin
133
+ lifecycle order are unchanged.
134
+
135
+ ### Invocation worker lifecycle
136
+
137
+ The existing `on_invocation_start` and `on_invocation_end` hooks run on the
138
+ handler's invocation worker, both in registration order. Start hooks finish
139
+ before background checkpoint processing begins. The same worker runs the handler
140
+ (including its `finally` blocks), prepares the existing serialized output or
141
+ error, checkpoints large results when necessary, joins registered branches while
142
+ checkpointing is available, stops and waits for checkpoint processing, then calls
143
+ End before returning the outcome. The caller shuts down the handler executor.
144
+ There is no separate handler-context hook or context-manager plugin API.
145
+
146
+ When plugins are registered, the worker begins with a copy of the caller's
147
+ context-variable bindings. Successful Start bindings flow into later Start hooks,
148
+ the handler, serialization and resource cleanup. A later successful Start may
149
+ replace an earlier binding. If a Start hook raises, its new bindings are discarded
150
+ for subsequent work; its End still runs in the original Context so its tokens can
151
+ be reset. End hooks retain forward registration order, not reverse stack order,
152
+ so they must not rely on observing a stack-like unwind of other plugins' contexts.
153
+ Each SDK-managed `map` or `parallel` branch admission, including an in-process
154
+ resume, receives a fresh copy of the coordinator's bindings when plugins are
155
+ registered. Branch changes remain local even when pool threads are reused.
156
+ User-created threads retain normal Python context-variable behavior; without
157
+ registered plugins, SDK branch submission keeps its existing behavior.
158
+ The worker's invocation context is discarded on return, including after plugin
159
+ cleanup failures, leaving the host's bindings unchanged. This isolates bindings,
160
+ not mutations to shared objects or external side effects. Without plugins, the
161
+ handler retains its existing fresh-worker context behavior.
162
+
163
+ These lifecycle guarantees require core 2.1.0 or later. The OTel 1.1 plugin
164
+ requires that core version and uses the existing Start/End hooks. Upgrading the
165
+ core alone with OTel 1.0 isolates worker bindings, but does not add the newer
166
+ Invocation-view fallback to that older plugin; upgrade both packages for it.
167
+ Provider API version 1 and the independent registration API are unchanged.
168
+
169
+ ### Chained-invoke propagation
170
+
171
+ A new invoke START collects synchronous plugin metadata after resolving its
172
+ operation and target identity, before checkpointing. The SDK writes a non-blank
173
+ `x_amzn_trace_id` contribution to the flat
174
+ `ChainedInvokeOptions.XAmznTraceId` member. Function, tenant, payload, operation
175
+ name and identity remain unchanged. The member is omitted without a contribution.
176
+ Each invoke in a batch has its own metadata; this is not a request-wide header.
177
+
178
+ The SDK-owned frozen `PropagationInput` carries `execution_arn`, `operation_id`,
179
+ optional `parent_operation_id`, and `target_function_name`. Frozen
180
+ `PropagationMetadata` has optional `x_amzn_trace_id`. Neither type depends on
181
+ OpenTelemetry or generated service models. The optional synchronous plugin
182
+ `provide_propagation_metadata(info)` hook defaults to no contribution.
183
+
184
+ The collector keeps the first non-blank opaque value in configured order without
185
+ trimming it. Equal values do not conflict; different later values log both plugin
186
+ identities and a conflict count. Ordinary hook/getter/result/diagnostic failures
187
+ are isolated; cancellation and other `BaseException` control signals keep their
188
+ existing behavior. Replaying a checkpointed START, pending operation or terminal
189
+ result does not call the hook. If a START was never committed, a later attempt
190
+ can collect again; the callback is not exactly-once.
191
+
192
+ This requires boto3 1.43.114 or newer, whose botocore dependency includes the
193
+ published optional `ChainedInvokeOptions.XAmznTraceId` string field. The real
194
+ botocore tests exercise its parameter validation and wire serialization without
195
+ field removal, validation bypass or a model-capability fallback. Deployed backend
196
+ delivery and downstream topology still require separate validation.
197
+ Python has no `DistributedMapOptions` wrapper
198
+ or distributed-map START API: its existing map/parallel operations use CONTEXT.
199
+ The design's corresponding distributed-map field awaits that future modeled path.
200
+
201
+ Release coordination still requires the compatible core/OTel minor versions.
202
+ Existing valid core/plugin combinations retain prior tracing behavior; older
203
+ cores without this optional contract contribute no new propagation metadata.
204
+ Backend rollout and deployed downstream trace-topology validation remain pending.
205
+
206
+ ## 🚀 Quick Start
207
+
208
+ Install the execution SDK:
209
+
210
+ ```console
211
+ pip install aws-durable-execution-sdk-python
212
+ ```
213
+
214
+ Create a durable Lambda handler:
215
+
216
+ ```python
217
+ from aws_durable_execution_sdk_python import (
218
+ DurableContext,
219
+ StepContext,
220
+ durable_execution,
221
+ durable_step,
222
+ )
223
+ from aws_durable_execution_sdk_python.config import Duration
224
+
225
+ @durable_step
226
+ def validate_order(step_ctx: StepContext, order_id: str) -> dict:
227
+ step_ctx.logger.info("Validating order", extra={"order_id": order_id})
228
+ return {"order_id": order_id, "valid": True}
229
+
230
+ @durable_execution
231
+ def handler(event: dict, context: DurableContext) -> dict:
232
+ order_id = event["order_id"]
233
+ context.logger.info("Starting workflow", extra={"order_id": order_id})
234
+
235
+ validation = context.step(validate_order(order_id), name="validate_order")
236
+ if not validation["valid"]:
237
+ return {"status": "rejected", "order_id": order_id}
238
+
239
+ # simulate approval (real world: use wait_for_callback)
240
+ context.wait(duration=Duration.from_seconds(5), name="await_confirmation")
241
+
242
+ return {"status": "approved", "order_id": order_id}
243
+ ```
244
+
245
+ ## 📚 Documentation
246
+
247
+ The complete documentation for the AWS Durable Execution SDK for Python lives on the AWS Documentation site:
248
+
249
+ - **[AWS Durable Execution Documentation](https://docs.aws.amazon.com/durable-execution/)** - Concepts, getting started, core operations, advanced topics, and API reference
250
+ - **[AWS Lambda Durable Functions Guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)** - How durable functions work on Lambda
251
+
252
+ ## 💬 Feedback & Support
253
+
254
+ - [Bug report](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=bug_report.yml)
255
+ - [Feature request](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=feature_request.yml)
256
+ - [Documentation feedback](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=documentation.yml)
257
+ - [Contributing guide](CONTRIBUTING.md)
258
+
259
+ ## 📄 License
260
+
261
+ See the [LICENSE](https://github.com/aws/aws-durable-execution-sdk-python/blob/main/LICENSE) file for our project's licensing.
@@ -0,0 +1,240 @@
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
+ Instrumentation plugins can be selected at Lambda cold start without importing
33
+ them in the function artifact. Install a provider package in the function or a
34
+ Lambda layer, then set an ordered allow-list:
35
+
36
+ ```text
37
+ DURABLE_EXECUTION_PLUGINS=otel-invocation,example_audit
38
+ ```
39
+
40
+ The SDK resolves those names from the `aws_durable_execution.plugins` Python
41
+ entry-point group when the decorated handler is initialized. An unset or blank
42
+ variable preserves the existing behavior. The decorator's `plugins` argument
43
+ remains supported; explicit plugins run first and take precedence when a
44
+ dynamic provider creates the same concrete plugin type.
45
+
46
+ Provider packages expose a versioned factory:
47
+
48
+ ```python
49
+ from aws_durable_execution_sdk_python.plugin import (
50
+ DurableInstrumentationPlugin,
51
+ DurableInstrumentationPluginProvider,
52
+ )
53
+
54
+
55
+ class AuditPlugin(DurableInstrumentationPlugin):
56
+ pass
57
+
58
+
59
+ AUDIT_PLUGIN_PROVIDER = DurableInstrumentationPluginProvider(
60
+ plugin_type=AuditPlugin,
61
+ factory=AuditPlugin,
62
+ plugin_api_version=1,
63
+ )
64
+ ```
65
+
66
+ Register the provider in the package's `pyproject.toml`:
67
+
68
+ ```toml
69
+ [project.entry-points."aws_durable_execution.plugins"]
70
+ example_audit = "example_audit:AUDIT_PLUGIN_PROVIDER"
71
+ ```
72
+
73
+ Set `plugin_api_version` to the literal API version the provider implements.
74
+ Update it only after verifying the provider against that API version.
75
+
76
+ Provider names must be unique across installed distributions. Missing,
77
+ ambiguous, incompatible, or invalid providers raise `PluginLoadError` during
78
+ handler initialization with the provider and distribution details.
79
+
80
+ ### Selecting an OpenTelemetry view
81
+
82
+ Enable at most one of `InvocationOtelPlugin` and `ExecutionOtelPlugin`. This
83
+ constraint applies to the combined `plugins=[...]` argument and
84
+ `DURABLE_EXECUTION_PLUGINS=otel-invocation` or `otel-execution` selection.
85
+ Core 2.1+ with OTel 1.1+ rejects both at cold start with `PluginLoadError` naming the
86
+ conflicting views; keep only one. Choose Invocation for work within each Lambda
87
+ invocation or Execution for logical operations across the durable execution.
88
+ Unrelated instrumentation plugins can run alongside either view. OTel 1.1 requires core 2.1 or later for the invocation-worker lifecycle as well
89
+ as view-exclusivity validation. Release core 2.1 before releasing OTel 1.1.
90
+
91
+ Plugin authors explicitly opt in by declaring
92
+ `__durable_registration_api__ = 1` on a plugin class. That class and its subclasses
93
+ have their optional `exclusive_group` metadata validated and their
94
+ optional `on_registration_result(registered)` callback invoked. A missing group
95
+ or `None` adds no exclusivity constraint. The callback can release rejected
96
+ constructor resources and must preserve resources from earlier accepted use.
97
+
98
+ The first class declaring the marker in the method resolution order gates
99
+ registration and selects the callback contract. An explicit marker other than
100
+ the integer `1` shadows an ancestor's opt-in and disables this capability.
101
+ Once enabled, every class in the MRO explicitly declaring the integer `1`
102
+ contributes its resolved `exclusive_group`. These constraints accumulate:
103
+ repeating the marker with a new group adds to inherited groups rather than
104
+ replacing them, and `None` does not erase an inherited constraint. A group is
105
+ counted once per plugin registration even if several ancestors declare it.
106
+ Unmarked classes' coincidental same-named legacy fields/helpers remain inert.
107
+ Subclasses of the bundled OTel views therefore retain the view group when they
108
+ add their own group. Repeat the marker to add a group or customize the callback;
109
+ the callback is still notified once per plugin. Plugins with no marker anywhere
110
+ in their hierarchy retain their existing attributes/helpers. The generic plugin base defines
111
+ neither attribute nor hook, and provider API version 1 and existing plugin
112
+ lifecycle order are unchanged.
113
+
114
+ ### Invocation worker lifecycle
115
+
116
+ The existing `on_invocation_start` and `on_invocation_end` hooks run on the
117
+ handler's invocation worker, both in registration order. Start hooks finish
118
+ before background checkpoint processing begins. The same worker runs the handler
119
+ (including its `finally` blocks), prepares the existing serialized output or
120
+ error, checkpoints large results when necessary, joins registered branches while
121
+ checkpointing is available, stops and waits for checkpoint processing, then calls
122
+ End before returning the outcome. The caller shuts down the handler executor.
123
+ There is no separate handler-context hook or context-manager plugin API.
124
+
125
+ When plugins are registered, the worker begins with a copy of the caller's
126
+ context-variable bindings. Successful Start bindings flow into later Start hooks,
127
+ the handler, serialization and resource cleanup. A later successful Start may
128
+ replace an earlier binding. If a Start hook raises, its new bindings are discarded
129
+ for subsequent work; its End still runs in the original Context so its tokens can
130
+ be reset. End hooks retain forward registration order, not reverse stack order,
131
+ so they must not rely on observing a stack-like unwind of other plugins' contexts.
132
+ Each SDK-managed `map` or `parallel` branch admission, including an in-process
133
+ resume, receives a fresh copy of the coordinator's bindings when plugins are
134
+ registered. Branch changes remain local even when pool threads are reused.
135
+ User-created threads retain normal Python context-variable behavior; without
136
+ registered plugins, SDK branch submission keeps its existing behavior.
137
+ The worker's invocation context is discarded on return, including after plugin
138
+ cleanup failures, leaving the host's bindings unchanged. This isolates bindings,
139
+ not mutations to shared objects or external side effects. Without plugins, the
140
+ handler retains its existing fresh-worker context behavior.
141
+
142
+ These lifecycle guarantees require core 2.1.0 or later. The OTel 1.1 plugin
143
+ requires that core version and uses the existing Start/End hooks. Upgrading the
144
+ core alone with OTel 1.0 isolates worker bindings, but does not add the newer
145
+ Invocation-view fallback to that older plugin; upgrade both packages for it.
146
+ Provider API version 1 and the independent registration API are unchanged.
147
+
148
+ ### Chained-invoke propagation
149
+
150
+ A new invoke START collects synchronous plugin metadata after resolving its
151
+ operation and target identity, before checkpointing. The SDK writes a non-blank
152
+ `x_amzn_trace_id` contribution to the flat
153
+ `ChainedInvokeOptions.XAmznTraceId` member. Function, tenant, payload, operation
154
+ name and identity remain unchanged. The member is omitted without a contribution.
155
+ Each invoke in a batch has its own metadata; this is not a request-wide header.
156
+
157
+ The SDK-owned frozen `PropagationInput` carries `execution_arn`, `operation_id`,
158
+ optional `parent_operation_id`, and `target_function_name`. Frozen
159
+ `PropagationMetadata` has optional `x_amzn_trace_id`. Neither type depends on
160
+ OpenTelemetry or generated service models. The optional synchronous plugin
161
+ `provide_propagation_metadata(info)` hook defaults to no contribution.
162
+
163
+ The collector keeps the first non-blank opaque value in configured order without
164
+ trimming it. Equal values do not conflict; different later values log both plugin
165
+ identities and a conflict count. Ordinary hook/getter/result/diagnostic failures
166
+ are isolated; cancellation and other `BaseException` control signals keep their
167
+ existing behavior. Replaying a checkpointed START, pending operation or terminal
168
+ result does not call the hook. If a START was never committed, a later attempt
169
+ can collect again; the callback is not exactly-once.
170
+
171
+ This requires boto3 1.43.114 or newer, whose botocore dependency includes the
172
+ published optional `ChainedInvokeOptions.XAmznTraceId` string field. The real
173
+ botocore tests exercise its parameter validation and wire serialization without
174
+ field removal, validation bypass or a model-capability fallback. Deployed backend
175
+ delivery and downstream topology still require separate validation.
176
+ Python has no `DistributedMapOptions` wrapper
177
+ or distributed-map START API: its existing map/parallel operations use CONTEXT.
178
+ The design's corresponding distributed-map field awaits that future modeled path.
179
+
180
+ Release coordination still requires the compatible core/OTel minor versions.
181
+ Existing valid core/plugin combinations retain prior tracing behavior; older
182
+ cores without this optional contract contribute no new propagation metadata.
183
+ Backend rollout and deployed downstream trace-topology validation remain pending.
184
+
185
+ ## 🚀 Quick Start
186
+
187
+ Install the execution SDK:
188
+
189
+ ```console
190
+ pip install aws-durable-execution-sdk-python
191
+ ```
192
+
193
+ Create a durable Lambda handler:
194
+
195
+ ```python
196
+ from aws_durable_execution_sdk_python import (
197
+ DurableContext,
198
+ StepContext,
199
+ durable_execution,
200
+ durable_step,
201
+ )
202
+ from aws_durable_execution_sdk_python.config import Duration
203
+
204
+ @durable_step
205
+ def validate_order(step_ctx: StepContext, order_id: str) -> dict:
206
+ step_ctx.logger.info("Validating order", extra={"order_id": order_id})
207
+ return {"order_id": order_id, "valid": True}
208
+
209
+ @durable_execution
210
+ def handler(event: dict, context: DurableContext) -> dict:
211
+ order_id = event["order_id"]
212
+ context.logger.info("Starting workflow", extra={"order_id": order_id})
213
+
214
+ validation = context.step(validate_order(order_id), name="validate_order")
215
+ if not validation["valid"]:
216
+ return {"status": "rejected", "order_id": order_id}
217
+
218
+ # simulate approval (real world: use wait_for_callback)
219
+ context.wait(duration=Duration.from_seconds(5), name="await_confirmation")
220
+
221
+ return {"status": "approved", "order_id": order_id}
222
+ ```
223
+
224
+ ## 📚 Documentation
225
+
226
+ The complete documentation for the AWS Durable Execution SDK for Python lives on the AWS Documentation site:
227
+
228
+ - **[AWS Durable Execution Documentation](https://docs.aws.amazon.com/durable-execution/)** - Concepts, getting started, core operations, advanced topics, and API reference
229
+ - **[AWS Lambda Durable Functions Guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)** - How durable functions work on Lambda
230
+
231
+ ## 💬 Feedback & Support
232
+
233
+ - [Bug report](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=bug_report.yml)
234
+ - [Feature request](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=feature_request.yml)
235
+ - [Documentation feedback](https://github.com/aws/aws-durable-execution-sdk-python/issues/new?template=documentation.yml)
236
+ - [Contributing guide](CONTRIBUTING.md)
237
+
238
+ ## 📄 License
239
+
240
+ 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__ = "2.1.0"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import contextvars
5
6
  import heapq
6
7
  import logging
7
8
  import queue
@@ -44,7 +45,7 @@ from aws_durable_execution_sdk_python.identifier import (
44
45
  OperationIdentifier,
45
46
  OperationIdNamespace,
46
47
  )
47
- from aws_durable_execution_sdk_python.lambda_service import ErrorObject
48
+ from aws_durable_execution_sdk_python.lambda_service import ErrorObject, OperationType
48
49
  from aws_durable_execution_sdk_python.operation.child import child_handler
49
50
 
50
51
 
@@ -183,6 +184,7 @@ class ConcurrentExecutor(Generic[CallableType, ResultType]):
183
184
  executable.index
184
185
  ),
185
186
  sub_type=self.sub_type_iteration,
187
+ operation_type=OperationType.CONTEXT,
186
188
  parent_id=executor_context._parent_id, # noqa: SLF001
187
189
  name=self.get_iteration_name(executable.index),
188
190
  )
@@ -291,13 +293,25 @@ class ConcurrentExecutor(Generic[CallableType, ResultType]):
291
293
 
292
294
  def submit(branch: Branch[CallableType, ResultType]) -> None:
293
295
  branch.start()
294
- pool.submit(
295
- self._branch_worker,
296
- execution_state,
297
- executor_context,
298
- events,
299
- branch.executable,
300
- )
296
+ if execution_state._plugin_executor._plugins: # noqa: SLF001
297
+ # Every admission/resume gets its own Context. Branch bindings
298
+ # cannot leak into siblings, the coordinator, or reused workers.
299
+ pool.submit(
300
+ contextvars.copy_context().run,
301
+ self._branch_worker,
302
+ execution_state,
303
+ executor_context,
304
+ events,
305
+ branch.executable,
306
+ )
307
+ else:
308
+ pool.submit(
309
+ self._branch_worker,
310
+ execution_state,
311
+ executor_context,
312
+ events,
313
+ branch.executable,
314
+ )
301
315
 
302
316
  try:
303
317
  # Only rebuild the items snapshot after a terminal event changes