async-durable-execution 0.1.0__tar.gz → 2.0.0a1__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.
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/.gitignore +10 -1
- async_durable_execution-2.0.0a1/NOTICE +8 -0
- async_durable_execution-2.0.0a1/PKG-INFO +146 -0
- async_durable_execution-2.0.0a1/README.md +125 -0
- async_durable_execution-0.1.0/src/async_durable_execution/__about__.py → async_durable_execution-2.0.0a1/VERSION.py +2 -1
- async_durable_execution-2.0.0a1/pyproject.toml +86 -0
- async_durable_execution-2.0.0a1/src/async_durable_execution/__about__.py +33 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/__init__.py +14 -6
- async_durable_execution-2.0.0a1/src/async_durable_execution/async_tools.py +49 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/concurrency/executor.py +55 -27
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/concurrency/models.py +4 -4
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/config.py +114 -20
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/context.py +179 -49
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/exceptions.py +100 -42
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/execution.py +111 -117
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/identifier.py +10 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/lambda_service.py +101 -2
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/logger.py +3 -3
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/base.py +2 -2
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/callback.py +10 -10
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/child.py +58 -38
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/invoke.py +8 -8
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/map.py +27 -11
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/parallel.py +36 -14
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/step.py +24 -14
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/wait.py +5 -5
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/wait_for_condition.py +20 -12
- async_durable_execution-2.0.0a1/src/async_durable_execution/plugin.py +409 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/retries.py +107 -2
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/serdes.py +3 -7
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/state.py +204 -45
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/suspend.py +1 -1
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/threading.py +1 -1
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/types.py +13 -6
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/waits.py +2 -2
- async_durable_execution-2.0.0a1/tests/async_tools_test.py +22 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/concurrency_test.py +337 -74
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/config_test.py +0 -11
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/context_test.py +400 -30
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/durable_executions_python_language_sdk_test.py +1 -1
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/e2e/checkpoint_response_int_test.py +21 -41
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/e2e/execution_int_test.py +13 -25
- async_durable_execution-2.0.0a1/tests/e2e/map_with_concurrent_waits_int_test.py +202 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/exceptions_test.py +145 -34
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/execution_test.py +631 -159
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/lambda_service_test.py +121 -14
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/logger_test.py +8 -2
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/callback_test.py +92 -34
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/child_test.py +349 -27
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/invoke_test.py +102 -38
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/map_test.py +430 -171
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/parallel_test.py +383 -174
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/step_test.py +113 -27
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/wait_for_condition_test.py +149 -40
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/wait_test.py +29 -11
- async_durable_execution-2.0.0a1/tests/plugin_test.py +783 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/state_test.py +933 -14
- async_durable_execution-2.0.0a1/tests/with_retry_test.py +410 -0
- async_durable_execution-0.1.0/.github/workflows/pypi.yml +0 -70
- async_durable_execution-0.1.0/PKG-INFO +0 -80
- async_durable_execution-0.1.0/README.md +0 -70
- async_durable_execution-0.1.0/pyproject.toml +0 -44
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/LICENSE +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/.gitignore +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/concurrency/__init__.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/operation/__init__.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/src/async_durable_execution/py.typed +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/__init__.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/e2e/__init__.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/__init__.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/operation/base_test.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/retries_test.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/serdes_test.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/suspend_test.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/test_helpers.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/threading_test.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/types_test.py +0 -0
- {async_durable_execution-0.1.0 → async_durable_execution-2.0.0a1}/tests/waits_test.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
|
|
@@ -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,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: async-durable-execution
|
|
3
|
+
Version: 2.0.0a1
|
|
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.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.42.1
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Async Durable Execution for Python
|
|
23
|
+
|
|
24
|
+
[](https://github.com/zhongkechen/async-durable-execution/actions/workflows/build.yml)
|
|
25
|
+
[](https://pypi.org/project/async-durable-execution)
|
|
26
|
+
[](https://pypi.org/project/async-durable-execution)
|
|
27
|
+
[](https://scorecard.dev/viewer/?uri=github.com/zhongkechen/async-durable-execution)
|
|
28
|
+
[](../../LICENSE)
|
|
29
|
+
|
|
30
|
+
-----
|
|
31
|
+
|
|
32
|
+
Build reliable, long-running AWS Lambda workflows with checkpointed steps, waits, callbacks, and parallel execution.
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
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 the examples, helpers, and package direction here prioritize `async def` handlers, steps, child contexts, callback submitters, and condition checks.
|
|
37
|
+
|
|
38
|
+
## ✨ Key Features
|
|
39
|
+
|
|
40
|
+
- **Async-first fork** - This fork prioritizes making `async def` workflows feel natural with durable functions
|
|
41
|
+
- **Automatic checkpointing** - Resume execution after Lambda pauses or restarts
|
|
42
|
+
- **Durable steps** - Run work with retry strategies and deterministic replay
|
|
43
|
+
- **Waits and callbacks** - Pause for time or external signals without blocking Lambda
|
|
44
|
+
- **Parallel and map operations** - Fan out work with configurable completion criteria
|
|
45
|
+
- **Child contexts** - Structure complex workflows into isolated subflows
|
|
46
|
+
- **Replay-safe logging** - Use `context.logger` for structured, de-duplicated logs
|
|
47
|
+
- **Local and cloud testing** - Validate workflows with the testing SDK
|
|
48
|
+
- **Async Python support** - Use `async def` for handlers, steps, child contexts, callback submitters, and wait-for-condition checks
|
|
49
|
+
|
|
50
|
+
## 📦 Packages
|
|
51
|
+
|
|
52
|
+
| Package | Description | Version |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `async-durable-execution` | Execution SDK for Lambda durable functions | [](https://pypi.org/project/async-durable-execution) |
|
|
55
|
+
| `async-durable-execution-runner` | Local/cloud test runner and pytest helpers | [](https://pypi.org/project/async-durable-execution-runner) |
|
|
56
|
+
| `async-durable-execution-examples` | Example durable functions and integration tests for local and cloud workflows | Shared repo version |
|
|
57
|
+
|
|
58
|
+
## 🚀 Quick Start
|
|
59
|
+
|
|
60
|
+
This fork recommends writing new durable workflows with async callables by default.
|
|
61
|
+
|
|
62
|
+
Install the execution SDK:
|
|
63
|
+
|
|
64
|
+
```console
|
|
65
|
+
pip install async-durable-execution
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Create a durable Lambda handler:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import asyncio
|
|
72
|
+
|
|
73
|
+
from async_durable_execution import (
|
|
74
|
+
DurableContext,
|
|
75
|
+
StepContext,
|
|
76
|
+
durable_execution,
|
|
77
|
+
durable_step,
|
|
78
|
+
)
|
|
79
|
+
from async_durable_execution.config import Duration
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@durable_step
|
|
83
|
+
async def validate_order(step_ctx: StepContext, order_id: str) -> dict:
|
|
84
|
+
await asyncio.sleep(0)
|
|
85
|
+
step_ctx.logger.info("Validating order", extra={"order_id": order_id})
|
|
86
|
+
return {"order_id": order_id, "valid": True}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@durable_execution
|
|
90
|
+
async def handler(event: dict, context: DurableContext) -> dict:
|
|
91
|
+
order_id = event["order_id"]
|
|
92
|
+
context.logger.info("Starting workflow", extra={"order_id": order_id})
|
|
93
|
+
|
|
94
|
+
validation = context.step(validate_order(order_id), name="validate_order")
|
|
95
|
+
if not validation["valid"]:
|
|
96
|
+
return {"status": "rejected", "order_id": order_id}
|
|
97
|
+
|
|
98
|
+
# simulate approval (real world: use wait_for_callback)
|
|
99
|
+
context.wait(duration=Duration.from_seconds(5), name="await_confirmation")
|
|
100
|
+
|
|
101
|
+
return {"status": "approved", "order_id": order_id}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Async callables are supported anywhere the SDK accepts user code, including `map()` item functions, `parallel()` branches, child contexts, callback submitters, and wait-for-condition checks. The public Durable APIs stay synchronous, so async work is awaited transparently for you:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import asyncio
|
|
108
|
+
|
|
109
|
+
from async_durable_execution import (
|
|
110
|
+
DurableContext,
|
|
111
|
+
StepContext,
|
|
112
|
+
durable_execution,
|
|
113
|
+
durable_step,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@durable_step
|
|
118
|
+
async def fetch_order(step_ctx: StepContext, order_id: str) -> dict:
|
|
119
|
+
await asyncio.sleep(0)
|
|
120
|
+
step_ctx.logger.info("Fetched order", extra={"order_id": order_id})
|
|
121
|
+
return {"order_id": order_id, "status": "ready"}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@durable_execution
|
|
125
|
+
async def handler(event: dict, context: DurableContext) -> dict:
|
|
126
|
+
order = context.step(fetch_order(event["order_id"]), name="fetch_order")
|
|
127
|
+
return {"order": order}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 📚 Documentation
|
|
131
|
+
|
|
132
|
+
The complete documentation for the AWS Durable Execution SDK for Python lives on the AWS Documentation site:
|
|
133
|
+
|
|
134
|
+
- **[AWS Durable Execution Documentation](https://docs.aws.amazon.com/durable-execution/)** - Concepts, getting started, core operations, advanced topics, and API reference
|
|
135
|
+
- **[AWS Lambda Durable Functions Guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)** - How durable functions work on Lambda
|
|
136
|
+
|
|
137
|
+
## 💬 Feedback & Support
|
|
138
|
+
|
|
139
|
+
- [Bug report](https://github.com/zhongkechen/async-durable-execution/issues/new?template=bug_report.yml)
|
|
140
|
+
- [Feature request](https://github.com/zhongkechen/async-durable-execution/issues/new?template=feature_request.yml)
|
|
141
|
+
- [Documentation feedback](https://github.com/zhongkechen/async-durable-execution/issues/new?template=documentation.yml)
|
|
142
|
+
- [Contributing guide](../../CONTRIBUTING.md)
|
|
143
|
+
|
|
144
|
+
## 📄 License
|
|
145
|
+
|
|
146
|
+
See the [LICENSE](../../LICENSE) file for our project's licensing.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Async Durable Execution for Python
|
|
2
|
+
|
|
3
|
+
[](https://github.com/zhongkechen/async-durable-execution/actions/workflows/build.yml)
|
|
4
|
+
[](https://pypi.org/project/async-durable-execution)
|
|
5
|
+
[](https://pypi.org/project/async-durable-execution)
|
|
6
|
+
[](https://scorecard.dev/viewer/?uri=github.com/zhongkechen/async-durable-execution)
|
|
7
|
+
[](../../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 the examples, helpers, and package direction here prioritize `async def` handlers, steps, child contexts, callback submitters, and condition checks.
|
|
16
|
+
|
|
17
|
+
## ✨ Key Features
|
|
18
|
+
|
|
19
|
+
- **Async-first fork** - This fork prioritizes making `async def` workflows feel natural with durable functions
|
|
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 `context.logger` for structured, de-duplicated logs
|
|
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 | [](https://pypi.org/project/async-durable-execution) |
|
|
34
|
+
| `async-durable-execution-runner` | Local/cloud test runner and pytest helpers | [](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 recommends writing new durable workflows with async callables by default.
|
|
40
|
+
|
|
41
|
+
Install the execution SDK:
|
|
42
|
+
|
|
43
|
+
```console
|
|
44
|
+
pip install async-durable-execution
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Create a durable Lambda handler:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import asyncio
|
|
51
|
+
|
|
52
|
+
from async_durable_execution import (
|
|
53
|
+
DurableContext,
|
|
54
|
+
StepContext,
|
|
55
|
+
durable_execution,
|
|
56
|
+
durable_step,
|
|
57
|
+
)
|
|
58
|
+
from async_durable_execution.config import Duration
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@durable_step
|
|
62
|
+
async def validate_order(step_ctx: StepContext, order_id: str) -> dict:
|
|
63
|
+
await asyncio.sleep(0)
|
|
64
|
+
step_ctx.logger.info("Validating order", extra={"order_id": order_id})
|
|
65
|
+
return {"order_id": order_id, "valid": True}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@durable_execution
|
|
69
|
+
async def handler(event: dict, context: DurableContext) -> dict:
|
|
70
|
+
order_id = event["order_id"]
|
|
71
|
+
context.logger.info("Starting workflow", extra={"order_id": order_id})
|
|
72
|
+
|
|
73
|
+
validation = context.step(validate_order(order_id), name="validate_order")
|
|
74
|
+
if not validation["valid"]:
|
|
75
|
+
return {"status": "rejected", "order_id": order_id}
|
|
76
|
+
|
|
77
|
+
# simulate approval (real world: use wait_for_callback)
|
|
78
|
+
context.wait(duration=Duration.from_seconds(5), name="await_confirmation")
|
|
79
|
+
|
|
80
|
+
return {"status": "approved", "order_id": order_id}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Async callables are supported anywhere the SDK accepts user code, including `map()` item functions, `parallel()` branches, child contexts, callback submitters, and wait-for-condition checks. The public Durable APIs stay synchronous, so async work is awaited transparently for you:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import asyncio
|
|
87
|
+
|
|
88
|
+
from async_durable_execution import (
|
|
89
|
+
DurableContext,
|
|
90
|
+
StepContext,
|
|
91
|
+
durable_execution,
|
|
92
|
+
durable_step,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@durable_step
|
|
97
|
+
async def fetch_order(step_ctx: StepContext, order_id: str) -> dict:
|
|
98
|
+
await asyncio.sleep(0)
|
|
99
|
+
step_ctx.logger.info("Fetched order", extra={"order_id": order_id})
|
|
100
|
+
return {"order_id": order_id, "status": "ready"}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@durable_execution
|
|
104
|
+
async def handler(event: dict, context: DurableContext) -> dict:
|
|
105
|
+
order = context.step(fetch_order(event["order_id"]), name="fetch_order")
|
|
106
|
+
return {"order": order}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 📚 Documentation
|
|
110
|
+
|
|
111
|
+
The complete documentation for the AWS Durable Execution SDK for Python lives on the AWS Documentation site:
|
|
112
|
+
|
|
113
|
+
- **[AWS Durable Execution Documentation](https://docs.aws.amazon.com/durable-execution/)** - Concepts, getting started, core operations, advanced topics, and API reference
|
|
114
|
+
- **[AWS Lambda Durable Functions Guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)** - How durable functions work on Lambda
|
|
115
|
+
|
|
116
|
+
## 💬 Feedback & Support
|
|
117
|
+
|
|
118
|
+
- [Bug report](https://github.com/zhongkechen/async-durable-execution/issues/new?template=bug_report.yml)
|
|
119
|
+
- [Feature request](https://github.com/zhongkechen/async-durable-execution/issues/new?template=feature_request.yml)
|
|
120
|
+
- [Documentation feedback](https://github.com/zhongkechen/async-durable-execution/issues/new?template=documentation.yml)
|
|
121
|
+
- [Contributing guide](../../CONTRIBUTING.md)
|
|
122
|
+
|
|
123
|
+
## 📄 License
|
|
124
|
+
|
|
125
|
+
See the [LICENSE](../../LICENSE) file for our project's licensing.
|
|
@@ -0,0 +1,86 @@
|
|
|
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.11"
|
|
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.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
23
|
+
"Programming Language :: Python :: Implementation :: PyPy",
|
|
24
|
+
]
|
|
25
|
+
dependencies = ["boto3>=1.42.1"]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Documentation = "https://github.com/zhongkechen/async-durable-execution#readme"
|
|
29
|
+
Issues = "https://github.com/zhongkechen/async-durable-execution/issues"
|
|
30
|
+
Source = "https://github.com/zhongkechen/async-durable-execution"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.sdist.force-include]
|
|
33
|
+
"../../LICENSE" = "LICENSE"
|
|
34
|
+
"../../NOTICE" = "NOTICE"
|
|
35
|
+
"../../VERSION.py" = "VERSION.py"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/async_durable_execution"]
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
41
|
+
"../../LICENSE" = "LICENSE"
|
|
42
|
+
"../../NOTICE" = "NOTICE"
|
|
43
|
+
"../../VERSION.py" = "VERSION.py"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.version]
|
|
46
|
+
path = "../../VERSION.py"
|
|
47
|
+
|
|
48
|
+
[tool.coverage.run]
|
|
49
|
+
source_pkgs = ["async_durable_execution"]
|
|
50
|
+
branch = true
|
|
51
|
+
parallel = true
|
|
52
|
+
omit = ["src/async_durable_execution/__about__.py"]
|
|
53
|
+
|
|
54
|
+
[tool.coverage.paths]
|
|
55
|
+
async_durable_execution = [
|
|
56
|
+
"src/async_durable_execution",
|
|
57
|
+
"*/async-durable-execution/src/async_durable_execution",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[tool.coverage.report]
|
|
61
|
+
exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
|
|
62
|
+
|
|
63
|
+
[tool.ruff]
|
|
64
|
+
line-length = 88
|
|
65
|
+
target-version = "py311"
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
preview = true
|
|
69
|
+
select = ["TID252"] # Enforce absolute imports (ban relative imports)
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint.isort]
|
|
72
|
+
known-first-party = ["async_durable_execution"]
|
|
73
|
+
force-single-line = false
|
|
74
|
+
lines-after-imports = 2
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint.per-file-ignores]
|
|
77
|
+
"tests/**" = [
|
|
78
|
+
"ARG001",
|
|
79
|
+
"ARG002",
|
|
80
|
+
"ARG005",
|
|
81
|
+
"S101",
|
|
82
|
+
"PLR2004",
|
|
83
|
+
"PLR6301",
|
|
84
|
+
"SIM117",
|
|
85
|
+
"TRY301",
|
|
86
|
+
]
|
|
@@ -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()
|
|
@@ -1,42 +1,50 @@
|
|
|
1
1
|
"""AWS Lambda Durable Executions Python SDK."""
|
|
2
2
|
|
|
3
3
|
# Package metadata
|
|
4
|
-
from .__about__ import __version__
|
|
4
|
+
from async_durable_execution.__about__ import __version__
|
|
5
5
|
|
|
6
6
|
# Main context - used in every durable function
|
|
7
7
|
# Helper decorators - commonly used for step functions
|
|
8
8
|
# Concurrency
|
|
9
|
-
from .concurrency.models import BatchResult
|
|
10
|
-
from .
|
|
9
|
+
from async_durable_execution.concurrency.models import BatchResult
|
|
10
|
+
from async_durable_execution.config import ParallelBranch
|
|
11
|
+
from async_durable_execution.context import (
|
|
11
12
|
DurableContext,
|
|
13
|
+
durable_parallel_branch,
|
|
12
14
|
durable_step,
|
|
13
15
|
durable_wait_for_callback,
|
|
14
16
|
durable_with_child_context,
|
|
15
17
|
)
|
|
16
18
|
|
|
17
19
|
# Most common exceptions - users need to handle these exceptions
|
|
18
|
-
from .exceptions import (
|
|
20
|
+
from async_durable_execution.exceptions import (
|
|
19
21
|
DurableExecutionsError,
|
|
20
22
|
InvocationError,
|
|
21
23
|
ValidationError,
|
|
22
24
|
)
|
|
23
25
|
|
|
24
26
|
# Core decorator - used in every durable function
|
|
25
|
-
from .execution import durable_execution
|
|
27
|
+
from async_durable_execution.execution import durable_execution
|
|
28
|
+
from async_durable_execution.retries import WithRetryConfig, with_retry
|
|
26
29
|
|
|
27
30
|
# Essential context types - passed to user functions
|
|
28
|
-
from .types import StepContext
|
|
31
|
+
from async_durable_execution.types import StepContext
|
|
32
|
+
|
|
29
33
|
|
|
30
34
|
__all__ = [
|
|
31
35
|
"BatchResult",
|
|
32
36
|
"DurableContext",
|
|
33
37
|
"DurableExecutionsError",
|
|
34
38
|
"InvocationError",
|
|
39
|
+
"ParallelBranch",
|
|
35
40
|
"StepContext",
|
|
36
41
|
"ValidationError",
|
|
42
|
+
"WithRetryConfig",
|
|
37
43
|
"__version__",
|
|
38
44
|
"durable_execution",
|
|
45
|
+
"durable_parallel_branch",
|
|
39
46
|
"durable_step",
|
|
40
47
|
"durable_wait_for_callback",
|
|
41
48
|
"durable_with_child_context",
|
|
49
|
+
"with_retry",
|
|
42
50
|
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import inspect
|
|
5
|
+
import queue
|
|
6
|
+
import threading
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from typing import Any, TypeVar, cast
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def resolve_awaitable(value: T | Awaitable[T]) -> T:
|
|
15
|
+
if inspect.isawaitable(value):
|
|
16
|
+
return run_awaitable(cast(Awaitable[T], value))
|
|
17
|
+
return value
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def invoke_callable(func: Callable[..., T | Awaitable[T]], *args, **kwargs) -> T:
|
|
21
|
+
return resolve_awaitable(func(*args, **kwargs))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def run_awaitable(awaitable: Awaitable[T]) -> T:
|
|
25
|
+
try:
|
|
26
|
+
asyncio.get_running_loop()
|
|
27
|
+
except RuntimeError:
|
|
28
|
+
return asyncio.run(awaitable)
|
|
29
|
+
|
|
30
|
+
return _run_awaitable_in_thread(awaitable)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _run_awaitable_in_thread(awaitable: Awaitable[T]) -> T:
|
|
34
|
+
result_queue: queue.Queue[tuple[bool, T | BaseException]] = queue.Queue(maxsize=1)
|
|
35
|
+
|
|
36
|
+
def runner() -> None:
|
|
37
|
+
try:
|
|
38
|
+
result_queue.put((True, asyncio.run(awaitable)))
|
|
39
|
+
except BaseException as exc: # noqa: BLE001
|
|
40
|
+
result_queue.put((False, exc))
|
|
41
|
+
|
|
42
|
+
thread = threading.Thread(target=runner, name="dex-async-user-code", daemon=True)
|
|
43
|
+
thread.start()
|
|
44
|
+
success, payload = result_queue.get()
|
|
45
|
+
thread.join()
|
|
46
|
+
|
|
47
|
+
if success:
|
|
48
|
+
return cast(T, payload)
|
|
49
|
+
raise cast(BaseException, payload)
|