async-durable-execution 0.1.0__tar.gz → 2.0.0a2__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. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/.gitignore +11 -2
  2. async_durable_execution-2.0.0a2/NOTICE +8 -0
  3. async_durable_execution-2.0.0a2/PKG-INFO +155 -0
  4. async_durable_execution-2.0.0a2/README.md +131 -0
  5. async_durable_execution-0.1.0/src/async_durable_execution/__about__.py → async_durable_execution-2.0.0a2/VERSION.py +2 -1
  6. async_durable_execution-2.0.0a2/pyproject.toml +90 -0
  7. async_durable_execution-2.0.0a2/src/async_durable_execution/__about__.py +33 -0
  8. async_durable_execution-2.0.0a2/src/async_durable_execution/__init__.py +132 -0
  9. async_durable_execution-2.0.0a2/src/async_durable_execution/async_tools.py +95 -0
  10. async_durable_execution-2.0.0a2/src/async_durable_execution/client.py +94 -0
  11. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/config.py +352 -71
  12. async_durable_execution-2.0.0a2/src/async_durable_execution/context.py +68 -0
  13. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/exceptions.py +105 -69
  14. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/execution.py +158 -165
  15. async_durable_execution-2.0.0a2/src/async_durable_execution/logger.py +139 -0
  16. async_durable_execution-2.0.0a2/src/async_durable_execution/models.py +1478 -0
  17. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/operation/base.py +34 -7
  18. async_durable_execution-2.0.0a2/src/async_durable_execution/operation/callback.py +397 -0
  19. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/operation/child.py +218 -49
  20. async_durable_execution-2.0.0a2/src/async_durable_execution/operation/concurrency.py +382 -0
  21. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/operation/invoke.py +46 -11
  22. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/operation/map.py +85 -21
  23. async_durable_execution-2.0.0a2/src/async_durable_execution/operation/parallel.py +277 -0
  24. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/operation/step.py +121 -35
  25. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/operation/wait.py +43 -7
  26. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/operation/wait_for_condition.py +90 -37
  27. async_durable_execution-2.0.0a2/src/async_durable_execution/operation/with_retry.py +61 -0
  28. async_durable_execution-2.0.0a2/src/async_durable_execution/plugin.py +439 -0
  29. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/serdes.py +5 -12
  30. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/state.py +415 -231
  31. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/suspend.py +3 -3
  32. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/types.py +52 -95
  33. async_durable_execution-2.0.0a2/tests_async_durable_execution/__init__.py +1 -0
  34. async_durable_execution-2.0.0a2/tests_async_durable_execution/async_tools_test.py +60 -0
  35. async_durable_execution-2.0.0a2/tests_async_durable_execution/client_test.py +486 -0
  36. async_durable_execution-0.1.0/tests/retries_test.py → async_durable_execution-2.0.0a2/tests_async_durable_execution/config_retry_test.py +73 -172
  37. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/config_test.py +5 -49
  38. async_durable_execution-0.1.0/tests/waits_test.py → async_durable_execution-2.0.0a2/tests_async_durable_execution/config_wait_test.py +66 -114
  39. async_durable_execution-2.0.0a2/tests_async_durable_execution/config_with_retry_test.py +37 -0
  40. async_durable_execution-2.0.0a2/tests_async_durable_execution/context_test.py +2511 -0
  41. async_durable_execution-2.0.0a2/tests_async_durable_execution/context_with_retry_test.py +421 -0
  42. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/e2e/checkpoint_response_int_test.py +106 -95
  43. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/e2e/execution_int_test.py +172 -98
  44. async_durable_execution-2.0.0a2/tests_async_durable_execution/e2e/map_with_concurrent_waits_int_test.py +202 -0
  45. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/exceptions_test.py +145 -51
  46. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/execution_test.py +913 -346
  47. async_durable_execution-2.0.0a2/tests_async_durable_execution/logger_test.py +319 -0
  48. async_durable_execution-2.0.0a2/tests_async_durable_execution/models_retry_test.py +19 -0
  49. async_durable_execution-0.1.0/tests/lambda_service_test.py → async_durable_execution-2.0.0a2/tests_async_durable_execution/models_test.py +285 -704
  50. async_durable_execution-2.0.0a2/tests_async_durable_execution/models_wait_test.py +37 -0
  51. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/operation/base_test.py +42 -33
  52. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/operation/callback_test.py +505 -340
  53. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/operation/child_test.py +439 -92
  54. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution/operation}/concurrency_test.py +773 -580
  55. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/operation/invoke_test.py +238 -158
  56. async_durable_execution-2.0.0a2/tests_async_durable_execution/operation/map_test.py +1512 -0
  57. async_durable_execution-2.0.0a2/tests_async_durable_execution/operation/parallel_test.py +1480 -0
  58. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/operation/step_test.py +307 -179
  59. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/operation/wait_for_condition_test.py +353 -272
  60. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/operation/wait_test.py +71 -48
  61. async_durable_execution-2.0.0a2/tests_async_durable_execution/plugin_test.py +751 -0
  62. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/serdes_test.py +15 -59
  63. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/state_test.py +1511 -1016
  64. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/suspend_test.py +4 -2
  65. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution}/test_helpers.py +9 -4
  66. async_durable_execution-2.0.0a2/tests_async_durable_execution/types_test.py +217 -0
  67. async_durable_execution-0.1.0/tests/durable_executions_python_language_sdk_test.py → async_durable_execution-2.0.0a2/tests_async_durable_execution/version_test.py +0 -5
  68. async_durable_execution-0.1.0/.github/workflows/pypi.yml +0 -70
  69. async_durable_execution-0.1.0/PKG-INFO +0 -80
  70. async_durable_execution-0.1.0/README.md +0 -70
  71. async_durable_execution-0.1.0/pyproject.toml +0 -44
  72. async_durable_execution-0.1.0/src/async_durable_execution/__init__.py +0 -42
  73. async_durable_execution-0.1.0/src/async_durable_execution/concurrency/executor.py +0 -461
  74. async_durable_execution-0.1.0/src/async_durable_execution/concurrency/models.py +0 -540
  75. async_durable_execution-0.1.0/src/async_durable_execution/context.py +0 -635
  76. async_durable_execution-0.1.0/src/async_durable_execution/identifier.py +0 -14
  77. async_durable_execution-0.1.0/src/async_durable_execution/lambda_service.py +0 -1120
  78. async_durable_execution-0.1.0/src/async_durable_execution/logger.py +0 -131
  79. async_durable_execution-0.1.0/src/async_durable_execution/operation/callback.py +0 -182
  80. async_durable_execution-0.1.0/src/async_durable_execution/operation/parallel.py +0 -122
  81. async_durable_execution-0.1.0/src/async_durable_execution/retries.py +0 -174
  82. async_durable_execution-0.1.0/src/async_durable_execution/threading.py +0 -222
  83. async_durable_execution-0.1.0/src/async_durable_execution/waits.py +0 -130
  84. async_durable_execution-0.1.0/tests/context_test.py +0 -1961
  85. async_durable_execution-0.1.0/tests/e2e/__init__.py +0 -0
  86. async_durable_execution-0.1.0/tests/logger_test.py +0 -418
  87. async_durable_execution-0.1.0/tests/operation/__init__.py +0 -0
  88. async_durable_execution-0.1.0/tests/operation/map_test.py +0 -1112
  89. async_durable_execution-0.1.0/tests/operation/parallel_test.py +0 -1104
  90. async_durable_execution-0.1.0/tests/threading_test.py +0 -954
  91. async_durable_execution-0.1.0/tests/types_test.py +0 -198
  92. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/LICENSE +0 -0
  93. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/.gitignore +0 -0
  94. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/operation/__init__.py +0 -0
  95. {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a2}/src/async_durable_execution/py.typed +0 -0
  96. {async_durable_execution-0.1.0/src/async_durable_execution/concurrency → async_durable_execution-2.0.0a2/tests_async_durable_execution/e2e}/__init__.py +0 -0
  97. {async_durable_execution-0.1.0/tests → async_durable_execution-2.0.0a2/tests_async_durable_execution/operation}/__init__.py +0 -0
@@ -9,6 +9,7 @@ __pycache__/
9
9
  *$py.class
10
10
  *.egg-info/
11
11
 
12
+ *.coverage
12
13
  /.coverage
13
14
  /.coverage.*
14
15
  /.cache
@@ -20,7 +21,7 @@ __pycache__/
20
21
  /build
21
22
 
22
23
  .venv
23
- .venv/
24
+ .venv*/
24
25
 
25
26
  .attach_*
26
27
 
@@ -28,4 +29,12 @@ dist/
28
29
 
29
30
  .idea
30
31
 
31
- .kiro/
32
+ .kiro/
33
+
34
+ **/build/
35
+ **/*.zip
36
+ packages/async-durable-execution-examples/template.yaml
37
+ **/template.generated.json
38
+
39
+ .env
40
+ *.egg-info
@@ -0,0 +1,8 @@
1
+ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+
3
+ This repository is a modified fork of software originally released by
4
+ Amazon.com, Inc. or its affiliates under the Apache License 2.0.
5
+
6
+ Fork modifications and continued maintenance:
7
+ Copyright 2026 Zhongke Chen
8
+ Repository: https://github.com/zhongkechen/async-durable-execution
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: async-durable-execution
3
+ Version: 2.0.0a2
4
+ Summary: Community-maintained durable execution SDK for AWS Lambda in Python
5
+ Project-URL: Documentation, https://github.com/zhongkechen/async-durable-execution#readme
6
+ Project-URL: Issues, https://github.com/zhongkechen/async-durable-execution/issues
7
+ Project-URL: Source, https://github.com/zhongkechen/async-durable-execution
8
+ Author: Zhongke Chen
9
+ License-Expression: Apache-2.0
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Programming Language :: Python :: Implementation :: CPython
18
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: boto3>=1.42.1
21
+ Provides-Extra: typing
22
+ Requires-Dist: boto3-stubs[lambda]>=1.42.1; extra == 'typing'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Async Durable Execution for Python
26
+
27
+ [![Build](https://github.com/zhongkechen/async-durable-execution/actions/workflows/build.yml/badge.svg)](https://github.com/zhongkechen/async-durable-execution/actions/workflows/build.yml)
28
+ [![PyPI - Version](https://img.shields.io/pypi/v/async-durable-execution.svg)](https://pypi.org/project/async-durable-execution)
29
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/async-durable-execution.svg)](https://pypi.org/project/async-durable-execution)
30
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/zhongkechen/async-durable-execution/badge)](https://scorecard.dev/viewer/?uri=github.com/zhongkechen/async-durable-execution)
31
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](../../LICENSE)
32
+
33
+ -----
34
+
35
+ Build reliable, long-running AWS Lambda workflows with checkpointed steps, waits, callbacks, and parallel execution.
36
+
37
+ This package is distributed from a community-maintained fork of the original Apache-2.0 licensed AWS project and continues under Apache License 2.0 with upstream notices preserved.
38
+
39
+ This fork is specifically focused on making async Python work naturally with durable functions. The public API remains synchronous at the durable operation boundary, but user-provided durable callables must now use `async def` for handlers, steps, child contexts, callback submitters, and condition checks.
40
+
41
+ ## ✨ Key Features
42
+
43
+ - **Async-only user callables** - Durable handlers and user-provided durable callbacks must use `async def`
44
+ - **Automatic checkpointing** - Resume execution after Lambda pauses or restarts
45
+ - **Durable steps** - Run work with retry strategies and deterministic replay
46
+ - **Waits and callbacks** - Pause for time or external signals without blocking Lambda
47
+ - **Parallel and map operations** - Fan out work with configurable completion criteria
48
+ - **Child contexts** - Structure complex workflows into isolated subflows
49
+ - **Replay-safe logging** - Use standard `logging` loggers enriched by the durable context filter
50
+ - **Local and cloud testing** - Validate workflows with the testing SDK
51
+ - **Async Python support** - Use `async def` for handlers, steps, child contexts, callback submitters, and wait-for-condition checks
52
+
53
+ ## 📦 Packages
54
+
55
+ | Package | Description | Version |
56
+ | --- | --- | --- |
57
+ | `async-durable-execution` | Execution SDK for Lambda durable functions | [![PyPI - Version](https://img.shields.io/pypi/v/async-durable-execution.svg)](https://pypi.org/project/async-durable-execution) |
58
+ | `async-durable-execution-runner` | Local/cloud test runner and pytest helpers | [![PyPI - Version](https://img.shields.io/pypi/v/async-durable-execution-runner.svg)](https://pypi.org/project/async-durable-execution-runner) |
59
+ | `async-durable-execution-examples` | Example durable functions and integration tests for local and cloud workflows | Shared repo version |
60
+
61
+ ## 🚀 Quick Start
62
+
63
+ This fork now requires async callables for all user-provided durable code.
64
+ Requires Python 3.10 or newer.
65
+
66
+ Install the execution SDK:
67
+
68
+ ```console
69
+ pip install async-durable-execution
70
+ ```
71
+
72
+ Create a durable Lambda handler:
73
+
74
+ ```python
75
+ import asyncio
76
+ import logging
77
+ from datetime import timedelta
78
+
79
+ from async_durable_execution import (
80
+ durable_step,
81
+ durable_execution,
82
+ step,
83
+ wait,
84
+ )
85
+
86
+ logger = logging.getLogger(__name__)
87
+
88
+
89
+ @durable_step
90
+ async def validate_order(order_id: str) -> dict:
91
+ await asyncio.sleep(0)
92
+ logger.info("Validating order", extra={"order_id": order_id})
93
+ return {"order_id": order_id, "valid": True}
94
+
95
+
96
+ @durable_execution
97
+ async def handler(event: dict) -> dict:
98
+ order_id = event["order_id"]
99
+ logger.info("Starting workflow", extra={"order_id": order_id})
100
+
101
+ validation = await step(validate_order(order_id), name="validate_order")
102
+ if not validation["valid"]:
103
+ return {"status": "rejected", "order_id": order_id}
104
+
105
+ # simulate approval (real world: use wait_for_callback)
106
+ await wait(duration=timedelta(seconds=5), name="await_confirmation")
107
+
108
+ return {"status": "approved", "order_id": order_id}
109
+ ```
110
+
111
+ Async callables are required anywhere the SDK accepts user code, including `map()` item functions, `parallel()` branches, child contexts, callback submitters, and wait-for-condition checks. Durable context operations are awaitable and run on the same event loop as your handler:
112
+
113
+ ```python
114
+ import asyncio
115
+ import logging
116
+
117
+ from async_durable_execution import (
118
+ durable_step,
119
+ durable_execution,
120
+ step,
121
+ )
122
+
123
+ logger = logging.getLogger(__name__)
124
+
125
+
126
+ @durable_step
127
+ async def fetch_order(order_id: str) -> dict:
128
+ await asyncio.sleep(0)
129
+ logger.info("Fetched order", extra={"order_id": order_id})
130
+ return {"order_id": order_id, "status": "ready"}
131
+
132
+
133
+ @durable_execution
134
+ async def handler(event: dict) -> dict:
135
+ order = await step(fetch_order(event["order_id"]), name="fetch_order")
136
+ return {"order": order}
137
+ ```
138
+
139
+ ## 📚 Documentation
140
+
141
+ The complete documentation for the AWS Durable Execution SDK for Python lives on the AWS Documentation site:
142
+
143
+ - **[AWS Durable Execution Documentation](https://docs.aws.amazon.com/durable-execution/)** - Concepts, getting started, core operations, advanced topics, and API reference
144
+ - **[AWS Lambda Durable Functions Guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)** - How durable functions work on Lambda
145
+
146
+ ## 💬 Feedback & Support
147
+
148
+ - [Bug report](https://github.com/zhongkechen/async-durable-execution/issues/new?template=bug_report.yml)
149
+ - [Feature request](https://github.com/zhongkechen/async-durable-execution/issues/new?template=feature_request.yml)
150
+ - [Documentation feedback](https://github.com/zhongkechen/async-durable-execution/issues/new?template=documentation.yml)
151
+ - [Contributing guide](../../CONTRIBUTING.md)
152
+
153
+ ## 📄 License
154
+
155
+ See the [LICENSE](../../LICENSE) file for our project's licensing.
@@ -0,0 +1,131 @@
1
+ # Async Durable Execution for Python
2
+
3
+ [![Build](https://github.com/zhongkechen/async-durable-execution/actions/workflows/build.yml/badge.svg)](https://github.com/zhongkechen/async-durable-execution/actions/workflows/build.yml)
4
+ [![PyPI - Version](https://img.shields.io/pypi/v/async-durable-execution.svg)](https://pypi.org/project/async-durable-execution)
5
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/async-durable-execution.svg)](https://pypi.org/project/async-durable-execution)
6
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/zhongkechen/async-durable-execution/badge)](https://scorecard.dev/viewer/?uri=github.com/zhongkechen/async-durable-execution)
7
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](../../LICENSE)
8
+
9
+ -----
10
+
11
+ Build reliable, long-running AWS Lambda workflows with checkpointed steps, waits, callbacks, and parallel execution.
12
+
13
+ This package is distributed from a community-maintained fork of the original Apache-2.0 licensed AWS project and continues under Apache License 2.0 with upstream notices preserved.
14
+
15
+ This fork is specifically focused on making async Python work naturally with durable functions. The public API remains synchronous at the durable operation boundary, but user-provided durable callables must now use `async def` for handlers, steps, child contexts, callback submitters, and condition checks.
16
+
17
+ ## ✨ Key Features
18
+
19
+ - **Async-only user callables** - Durable handlers and user-provided durable callbacks must use `async def`
20
+ - **Automatic checkpointing** - Resume execution after Lambda pauses or restarts
21
+ - **Durable steps** - Run work with retry strategies and deterministic replay
22
+ - **Waits and callbacks** - Pause for time or external signals without blocking Lambda
23
+ - **Parallel and map operations** - Fan out work with configurable completion criteria
24
+ - **Child contexts** - Structure complex workflows into isolated subflows
25
+ - **Replay-safe logging** - Use standard `logging` loggers enriched by the durable context filter
26
+ - **Local and cloud testing** - Validate workflows with the testing SDK
27
+ - **Async Python support** - Use `async def` for handlers, steps, child contexts, callback submitters, and wait-for-condition checks
28
+
29
+ ## 📦 Packages
30
+
31
+ | Package | Description | Version |
32
+ | --- | --- | --- |
33
+ | `async-durable-execution` | Execution SDK for Lambda durable functions | [![PyPI - Version](https://img.shields.io/pypi/v/async-durable-execution.svg)](https://pypi.org/project/async-durable-execution) |
34
+ | `async-durable-execution-runner` | Local/cloud test runner and pytest helpers | [![PyPI - Version](https://img.shields.io/pypi/v/async-durable-execution-runner.svg)](https://pypi.org/project/async-durable-execution-runner) |
35
+ | `async-durable-execution-examples` | Example durable functions and integration tests for local and cloud workflows | Shared repo version |
36
+
37
+ ## 🚀 Quick Start
38
+
39
+ This fork now requires async callables for all user-provided durable code.
40
+ Requires Python 3.10 or newer.
41
+
42
+ Install the execution SDK:
43
+
44
+ ```console
45
+ pip install async-durable-execution
46
+ ```
47
+
48
+ Create a durable Lambda handler:
49
+
50
+ ```python
51
+ import asyncio
52
+ import logging
53
+ from datetime import timedelta
54
+
55
+ from async_durable_execution import (
56
+ durable_step,
57
+ durable_execution,
58
+ step,
59
+ wait,
60
+ )
61
+
62
+ logger = logging.getLogger(__name__)
63
+
64
+
65
+ @durable_step
66
+ async def validate_order(order_id: str) -> dict:
67
+ await asyncio.sleep(0)
68
+ logger.info("Validating order", extra={"order_id": order_id})
69
+ return {"order_id": order_id, "valid": True}
70
+
71
+
72
+ @durable_execution
73
+ async def handler(event: dict) -> dict:
74
+ order_id = event["order_id"]
75
+ logger.info("Starting workflow", extra={"order_id": order_id})
76
+
77
+ validation = await step(validate_order(order_id), name="validate_order")
78
+ if not validation["valid"]:
79
+ return {"status": "rejected", "order_id": order_id}
80
+
81
+ # simulate approval (real world: use wait_for_callback)
82
+ await wait(duration=timedelta(seconds=5), name="await_confirmation")
83
+
84
+ return {"status": "approved", "order_id": order_id}
85
+ ```
86
+
87
+ Async callables are required anywhere the SDK accepts user code, including `map()` item functions, `parallel()` branches, child contexts, callback submitters, and wait-for-condition checks. Durable context operations are awaitable and run on the same event loop as your handler:
88
+
89
+ ```python
90
+ import asyncio
91
+ import logging
92
+
93
+ from async_durable_execution import (
94
+ durable_step,
95
+ durable_execution,
96
+ step,
97
+ )
98
+
99
+ logger = logging.getLogger(__name__)
100
+
101
+
102
+ @durable_step
103
+ async def fetch_order(order_id: str) -> dict:
104
+ await asyncio.sleep(0)
105
+ logger.info("Fetched order", extra={"order_id": order_id})
106
+ return {"order_id": order_id, "status": "ready"}
107
+
108
+
109
+ @durable_execution
110
+ async def handler(event: dict) -> dict:
111
+ order = await step(fetch_order(event["order_id"]), name="fetch_order")
112
+ return {"order": order}
113
+ ```
114
+
115
+ ## 📚 Documentation
116
+
117
+ The complete documentation for the AWS Durable Execution SDK for Python lives on the AWS Documentation site:
118
+
119
+ - **[AWS Durable Execution Documentation](https://docs.aws.amazon.com/durable-execution/)** - Concepts, getting started, core operations, advanced topics, and API reference
120
+ - **[AWS Lambda Durable Functions Guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)** - How durable functions work on Lambda
121
+
122
+ ## 💬 Feedback & Support
123
+
124
+ - [Bug report](https://github.com/zhongkechen/async-durable-execution/issues/new?template=bug_report.yml)
125
+ - [Feature request](https://github.com/zhongkechen/async-durable-execution/issues/new?template=feature_request.yml)
126
+ - [Documentation feedback](https://github.com/zhongkechen/async-durable-execution/issues/new?template=documentation.yml)
127
+ - [Contributing guide](../../CONTRIBUTING.md)
128
+
129
+ ## 📄 License
130
+
131
+ See the [LICENSE](../../LICENSE) file for our project's licensing.
@@ -1,4 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Amazon.com, Inc. or its affiliates.
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
- __version__ = "0.1.0"
4
+ # Modified for the async-durable-execution fork; shared version source for all packages.
5
+ __version__ = "2.0.0a2"
@@ -0,0 +1,90 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ # Modified fork metadata for async-durable-execution; upstream Apache-2.0 notices are preserved in the repository LICENSE and NOTICE files.
6
+ [project]
7
+ name = "async-durable-execution"
8
+ dynamic = ["version"]
9
+ description = "Community-maintained durable execution SDK for AWS Lambda in Python"
10
+ readme = "README.md"
11
+ requires-python = ">=3.10"
12
+ license = "Apache-2.0"
13
+ keywords = []
14
+ authors = [{ name = "Zhongke Chen" }]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Programming Language :: Python :: Implementation :: CPython",
24
+ "Programming Language :: Python :: Implementation :: PyPy",
25
+ ]
26
+ dependencies = ["boto3>=1.42.1"]
27
+
28
+ [project.optional-dependencies]
29
+ typing = ["boto3-stubs[lambda]>=1.42.1"]
30
+
31
+ [project.urls]
32
+ Documentation = "https://github.com/zhongkechen/async-durable-execution#readme"
33
+ Issues = "https://github.com/zhongkechen/async-durable-execution/issues"
34
+ Source = "https://github.com/zhongkechen/async-durable-execution"
35
+
36
+ [tool.hatch.build.targets.sdist.force-include]
37
+ "../../LICENSE" = "LICENSE"
38
+ "../../NOTICE" = "NOTICE"
39
+ "../../VERSION.py" = "VERSION.py"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/async_durable_execution"]
43
+
44
+ [tool.hatch.build.targets.wheel.force-include]
45
+ "../../LICENSE" = "LICENSE"
46
+ "../../NOTICE" = "NOTICE"
47
+ "../../VERSION.py" = "VERSION.py"
48
+
49
+ [tool.hatch.version]
50
+ path = "../../VERSION.py"
51
+
52
+ [tool.coverage.run]
53
+ source_pkgs = ["async_durable_execution"]
54
+ branch = true
55
+ parallel = true
56
+ omit = ["src/async_durable_execution/__about__.py"]
57
+
58
+ [tool.coverage.paths]
59
+ async_durable_execution = [
60
+ "src/async_durable_execution",
61
+ "*/async-durable-execution/src/async_durable_execution",
62
+ ]
63
+
64
+ [tool.coverage.report]
65
+ exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
66
+
67
+ [tool.ruff]
68
+ line-length = 88
69
+ target-version = "py310"
70
+
71
+ [tool.ruff.lint]
72
+ preview = true
73
+ select = []
74
+
75
+ [tool.ruff.lint.isort]
76
+ known-first-party = ["async_durable_execution"]
77
+ force-single-line = false
78
+ lines-after-imports = 2
79
+
80
+ [tool.ruff.lint.per-file-ignores]
81
+ "tests_async_durable_execution/**" = [
82
+ "ARG001",
83
+ "ARG002",
84
+ "ARG005",
85
+ "S101",
86
+ "PLR2004",
87
+ "PLR6301",
88
+ "SIM117",
89
+ "TRY301",
90
+ ]
@@ -0,0 +1,33 @@
1
+ # SPDX-FileCopyrightText: 2025-present Amazon.com, Inc. or its affiliates.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from importlib.metadata import PackageNotFoundError, version
5
+ from pathlib import Path
6
+
7
+
8
+ _DIST_NAME = "async-durable-execution"
9
+
10
+
11
+ def _read_repo_version() -> str:
12
+ version_file = _find_version_file()
13
+ if version_file is None:
14
+ return "0.0.0"
15
+
16
+ namespace: dict[str, str] = {}
17
+ exec(version_file.read_text(encoding="utf-8"), namespace)
18
+ return namespace["__version__"]
19
+
20
+
21
+ def _find_version_file() -> Path | None:
22
+ current = Path(__file__).resolve()
23
+ for parent in current.parents:
24
+ candidate = parent / "VERSION.py"
25
+ if candidate.exists():
26
+ return candidate
27
+ return None
28
+
29
+
30
+ try:
31
+ __version__ = version(_DIST_NAME)
32
+ except PackageNotFoundError:
33
+ __version__ = _read_repo_version()
@@ -0,0 +1,132 @@
1
+ """AWS Lambda Durable Executions Python SDK."""
2
+
3
+ # Package metadata
4
+ from .__about__ import __version__
5
+
6
+ # Main context - used in every durable function
7
+ # Helper decorators - commonly used for step functions
8
+ # Concurrency
9
+ from .models import (
10
+ BatchItem,
11
+ BatchItemStatus,
12
+ BatchResult,
13
+ CompletionReason,
14
+ InvocationStatus,
15
+ OperationType,
16
+ OperationStatus,
17
+ )
18
+ from .config import (
19
+ CallbackConfig,
20
+ CompletionConfig,
21
+ MapConfig,
22
+ NestingType,
23
+ ParallelBranch,
24
+ ParallelConfig,
25
+ RetryPresets,
26
+ RetryStrategyBuilder,
27
+ StepConfig,
28
+ StepSemantics,
29
+ WaitStrategyBuilder,
30
+ WaitForCallbackConfig,
31
+ WaitForConditionConfig,
32
+ WithRetryConfig,
33
+ )
34
+ from .context import (
35
+ get_current_context,
36
+ )
37
+ from .operation.with_retry import with_retry
38
+ from .operation.map import map
39
+ from .operation.wait_for_condition import (
40
+ wait_for_condition,
41
+ WaitForConditionCheckContext,
42
+ )
43
+ from .operation.invoke import invoke
44
+ from .operation.parallel import parallel, durable_parallel_branch
45
+ from .operation.callback import (
46
+ create_callback,
47
+ durable_wait_for_callback,
48
+ wait_for_callback,
49
+ WaitForCallbackContext,
50
+ Callback,
51
+ )
52
+ from .operation.child import durable_child_context, run_in_child_context, DurableContext
53
+ from .operation.step import step, durable_step, StepContext, get_attempt
54
+ from .models import (
55
+ ErrorObject,
56
+ OperationIdentifier,
57
+ RetryDecision,
58
+ WaitDecision,
59
+ WaitForConditionDecision,
60
+ )
61
+
62
+ # Most common exceptions - users need to handle these exceptions
63
+ from .exceptions import (
64
+ DurableExecutionsError,
65
+ InvocationError,
66
+ ValidationError,
67
+ )
68
+
69
+ # Core decorator - used in every durable function
70
+ from .execution import durable_execution
71
+ from .operation.wait import wait
72
+ from .plugin import DurableInstrumentationPlugin
73
+ from .serdes import JsonSerDes, SerDes, SerDesContext
74
+
75
+ __all__ = [
76
+ "BatchItem",
77
+ "BatchItemStatus",
78
+ "BatchResult",
79
+ "Callback",
80
+ "CallbackConfig",
81
+ "CompletionConfig",
82
+ "CompletionReason",
83
+ "DurableContext",
84
+ "DurableInstrumentationPlugin",
85
+ "DurableExecutionsError",
86
+ "ErrorObject",
87
+ "InvocationError",
88
+ "JsonSerDes",
89
+ "MapConfig",
90
+ "NestingType",
91
+ "OperationIdentifier",
92
+ "ParallelBranch",
93
+ "ParallelConfig",
94
+ "RetryDecision",
95
+ "RetryPresets",
96
+ "RetryStrategyBuilder",
97
+ "SerDes",
98
+ "SerDesContext",
99
+ "StepConfig",
100
+ "StepContext",
101
+ "StepSemantics",
102
+ "ValidationError",
103
+ "WaitForCallbackConfig",
104
+ "WaitForCallbackContext",
105
+ "WaitDecision",
106
+ "WaitForConditionCheckContext",
107
+ "WaitForConditionConfig",
108
+ "WaitForConditionDecision",
109
+ "WaitStrategyBuilder",
110
+ "WithRetryConfig",
111
+ "__version__",
112
+ "create_callback",
113
+ "durable_execution",
114
+ "durable_parallel_branch",
115
+ "durable_step",
116
+ "durable_wait_for_callback",
117
+ "get_attempt",
118
+ "get_current_context",
119
+ "invoke",
120
+ "map",
121
+ "parallel",
122
+ "run_in_child_context",
123
+ "step",
124
+ "wait",
125
+ "wait_for_callback",
126
+ "wait_for_condition",
127
+ "with_retry",
128
+ "InvocationStatus",
129
+ "OperationType",
130
+ "OperationStatus",
131
+ "durable_child_context",
132
+ ]
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import inspect
5
+ from collections.abc import Awaitable, Callable
6
+ from typing import TYPE_CHECKING, TypeVar, cast
7
+
8
+ from .context import reset_current_context, set_current_context
9
+ from .exceptions import ValidationError
10
+
11
+ if TYPE_CHECKING:
12
+ from . import DurableContext
13
+
14
+ T = TypeVar("T")
15
+ _CONTEXT_PARAM_NAMES = {
16
+ "context",
17
+ "ctx",
18
+ "child_context",
19
+ "child_ctx",
20
+ "durable_context",
21
+ "durable_ctx",
22
+ }
23
+
24
+
25
+ def is_async_callable(func: Callable[..., object]) -> bool:
26
+ if inspect.iscoroutinefunction(func):
27
+ return True
28
+ if isinstance(func, functools.partial):
29
+ return is_async_callable(func.func)
30
+
31
+ call = getattr(func, "__call__", None)
32
+ return call is not None and inspect.iscoroutinefunction(call)
33
+
34
+
35
+ def get_callable_name(
36
+ func: Callable[..., object],
37
+ *,
38
+ include_original_name: bool = True,
39
+ ) -> str | None:
40
+ if isinstance(func, functools.partial):
41
+ return get_callable_name(
42
+ func.func,
43
+ include_original_name=include_original_name,
44
+ )
45
+
46
+ if include_original_name:
47
+ original_name = getattr(func, "_original_name", None)
48
+ if original_name is not None:
49
+ return original_name
50
+
51
+ if inspect.isfunction(func) or inspect.ismethod(func):
52
+ return getattr(func, "__name__", None)
53
+
54
+ return None
55
+
56
+
57
+ def assert_async_callable(
58
+ func: Callable[..., object],
59
+ *,
60
+ label: str = "func",
61
+ ) -> None:
62
+ if is_async_callable(func):
63
+ return
64
+
65
+ name = get_callable_name(func)
66
+ if name is None:
67
+ name = type(func).__name__
68
+
69
+ msg = (
70
+ f"`{label}` must be an async function. "
71
+ f"Non-async callables are no longer supported: {name}."
72
+ )
73
+ raise ValidationError(msg)
74
+
75
+
76
+ async def invoke_callable(func: Callable[..., Awaitable[T]], *args, **kwargs) -> T:
77
+ assert_async_callable(func)
78
+ return await func(*args, **kwargs)
79
+
80
+
81
+ async def invoke_user_callable(
82
+ context: DurableContext,
83
+ func: Callable[..., Awaitable[T]],
84
+ *args,
85
+ **kwargs,
86
+ ) -> T:
87
+ token = set_current_context(context)
88
+ try:
89
+ return await invoke_callable(
90
+ func,
91
+ *args,
92
+ **kwargs,
93
+ )
94
+ finally:
95
+ reset_current_context(token)