aws-durable-execution-sdk-python-testing 1.2.1__tar.gz → 1.3.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 (72) hide show
  1. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/.gitignore +9 -7
  2. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/PKG-INFO +50 -58
  3. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/README.md +47 -53
  4. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/__about__.py +1 -1
  5. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/checkpoint/core.py +171 -0
  6. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/checkpoint/effects.py +61 -0
  7. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/checkpoint/processor.py +180 -0
  8. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/processors/base.py +40 -14
  9. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/processors/callback.py +5 -2
  10. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/processors/context.py +6 -0
  11. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/processors/execution.py +4 -9
  12. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/checkpoint/processors/invoke.py +139 -0
  13. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/processors/step.py +15 -21
  14. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/processors/wait.py +10 -16
  15. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/checkpoint/transformer.py +201 -0
  16. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/validators/checkpoint.py +65 -8
  17. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/invoke.py +117 -0
  18. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/child_dispatcher.py +828 -0
  19. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/cli.py +48 -1
  20. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/client.py +21 -10
  21. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/clock.py +69 -0
  22. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/exceptions.py +14 -0
  23. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/execution.py +978 -0
  24. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/executor.py +2698 -0
  25. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/invoker.py +611 -0
  26. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/model.py +81 -73
  27. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/observer.py +156 -0
  28. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/runner.py +407 -30
  29. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/scheduler.py +38 -13
  30. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/stores/filesystem.py +10 -2
  31. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/stores/sqlite.py +18 -6
  32. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/threads.py +157 -0
  33. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/token.py +11 -2
  34. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/web/handlers.py +33 -7
  35. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/web/serialization.py +1 -2
  36. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/web/server.py +1 -1
  37. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/worker/__init__.py +9 -0
  38. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/worker/checkpoint_tasks.py +119 -0
  39. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/worker/execution_worker.py +117 -0
  40. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/worker/lane.py +118 -0
  41. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/worker/registry.py +124 -0
  42. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/worker/status.py +27 -0
  43. aws_durable_execution_sdk_python_testing-1.3.0/aws_durable_execution_sdk_python_testing/worker/task.py +57 -0
  44. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/pyproject.toml +17 -5
  45. aws_durable_execution_sdk_python_testing-1.2.1/aws_durable_execution_sdk_python_testing/checkpoint/processor.py +0 -101
  46. aws_durable_execution_sdk_python_testing-1.2.1/aws_durable_execution_sdk_python_testing/checkpoint/transformer.py +0 -104
  47. aws_durable_execution_sdk_python_testing-1.2.1/aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/invoke.py +0 -56
  48. aws_durable_execution_sdk_python_testing-1.2.1/aws_durable_execution_sdk_python_testing/checkpoint/validators/transitions.py +0 -66
  49. aws_durable_execution_sdk_python_testing-1.2.1/aws_durable_execution_sdk_python_testing/execution.py +0 -444
  50. aws_durable_execution_sdk_python_testing-1.2.1/aws_durable_execution_sdk_python_testing/executor.py +0 -1234
  51. aws_durable_execution_sdk_python_testing-1.2.1/aws_durable_execution_sdk_python_testing/invoker.py +0 -340
  52. aws_durable_execution_sdk_python_testing-1.2.1/aws_durable_execution_sdk_python_testing/observer.py +0 -144
  53. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/LICENSE +0 -0
  54. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/NOTICE +0 -0
  55. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/__init__.py +0 -0
  56. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/__init__.py +0 -0
  57. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/processors/__init__.py +0 -0
  58. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/validators/__init__.py +0 -0
  59. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/__init__.py +0 -0
  60. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/callback.py +0 -0
  61. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/context.py +0 -0
  62. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/execution.py +0 -0
  63. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/step.py +0 -0
  64. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/wait.py +0 -0
  65. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/py.typed +0 -0
  66. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/stores/__init__.py +0 -0
  67. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/stores/base.py +0 -0
  68. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/stores/memory.py +0 -0
  69. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/web/__init__.py +0 -0
  70. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/web/errors.py +0 -0
  71. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/web/models.py +0 -0
  72. {aws_durable_execution_sdk_python_testing-1.2.1 → aws_durable_execution_sdk_python_testing-1.3.0}/aws_durable_execution_sdk_python_testing/web/routes.py +0 -0
@@ -9,11 +9,13 @@ __pycache__/
9
9
  *$py.class
10
10
  *.egg-info/
11
11
 
12
+ *.coverage
12
13
  /.coverage
13
14
  /.coverage.*
14
15
  /.cache
15
16
  /.pytest_cache
16
17
  /.mypy_cache
18
+ /.vscode
17
19
 
18
20
  /doc/_apidoc/
19
21
  /build
@@ -25,13 +27,13 @@ __pycache__/
25
27
 
26
28
  dist/
27
29
 
28
- .vscode/
29
- .kiro/
30
30
  .idea
31
- .env
32
- .env*
33
31
 
34
- .durable_executions
32
+ .kiro/
35
33
 
36
- durable-executions.db*
37
- .coverage
34
+ **/build/
35
+ **/*.zip
36
+ .aws-sam/
37
+ **/template.generated.json
38
+
39
+ .env
@@ -1,14 +1,12 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: aws-durable-execution-sdk-python-testing
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Summary: AWS Durable Execution Testing SDK for Python
5
5
  Project-URL: Documentation, https://github.com/aws/aws-durable-execution-sdk-python-testing#readme
6
6
  Project-URL: Issues, https://github.com/aws/aws-durable-execution-sdk-python-testing/issues
7
7
  Project-URL: Source, https://github.com/aws/aws-durable-execution-sdk-python-testing
8
8
  Author-email: AWS durable-execution-dev <durable-execution-dev@amazon.com>
9
9
  License-Expression: Apache-2.0
10
- License-File: LICENSE
11
- License-File: NOTICE
12
10
  Classifier: Development Status :: 4 - Beta
13
11
  Classifier: Programming Language :: Python
14
12
  Classifier: Programming Language :: Python :: 3.11
@@ -18,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.14
18
16
  Classifier: Programming Language :: Python :: Implementation :: CPython
19
17
  Classifier: Programming Language :: Python :: Implementation :: PyPy
20
18
  Requires-Python: >=3.11
21
- Requires-Dist: aws-durable-execution-sdk-python>=1.0.0
19
+ Requires-Dist: aws-durable-execution-sdk-python>=2.1.0
22
20
  Requires-Dist: boto3>=1.42.1
23
21
  Description-Content-Type: text/markdown
24
22
 
@@ -36,6 +34,7 @@ Description-Content-Type: text/markdown
36
34
 
37
35
  - [Installation](#installation)
38
36
  - [Quick Start](#quick-start)
37
+ - [Testing functions that invoke other functions](#testing-functions-that-invoke-other-functions)
39
38
  - [Architecture](#architecture)
40
39
  - [Documentation](#documentation)
41
40
  - [Developer Guide](#developers)
@@ -135,67 +134,60 @@ def test_my_durable_functions():
135
134
  three_result: StepOperation = result.get_step("three")
136
135
  assert three_result.result == '"5 6"'
137
136
  ```
138
- ## Architecture
139
- ![Durable Functions Python Test Framework Architecture](/assets/dar-python-test-framework-architecture.svg)
140
-
141
- ## Event Flow
142
- ![Event Flow Sequence Diagram](/assets/dar-python-test-framework-event-flow.svg)
143
-
144
- 1. **DurableTestRunner** starts execution via **Executor**
145
- 2. **Executor** creates **Execution** and schedules initial invocation
146
- 3. During execution, checkpoints are processed by **CheckpointProcessor**
147
- 4. **Individual Processors** transform operation updates and may trigger events
148
- 5. **ExecutionNotifier** broadcasts events to **Executor** (observer)
149
- 6. **Executor** updates **Execution** state based on events
150
- 7. **Execution** completion triggers final event notifications
151
- 8. **DurableTestRunner** run() blocks until it receives completion event, and then returns `DurableFunctionTestResult`.
137
+ ## Testing functions that invoke other functions
152
138
 
153
- ## Major Components
139
+ A durable function can call another function with `context.invoke`. The
140
+ runner needs to know each target's kind: a durable target runs as a
141
+ child durable execution with its own history, while a plain (non-durable)
142
+ target is one invocation whose return value is the result. An unknown
143
+ target fails the operation with `ResourceNotFoundException`.
154
144
 
155
- ### Core Execution Flow
156
- - **DurableTestRunner** - Main entry point that orchestrates test execution
157
- - **Executor** - Manages execution lifecycle. Mutates Execution.
158
- - **Execution** - Represents the state and operations of a single durable execution
145
+ ### In-process runner
159
146
 
160
- ### Service Client Integration
161
- - **InMemoryServiceClient** - Replaces AWS Lambda service client for local testing. Injected into SDK via `DurableExecutionInvocationInputWithClient`
147
+ Register each target with the runner. `register_durable_function` marks a
148
+ durable target, and takes its execution timeout and retention;
149
+ `register_function` marks a plain one. The runner has one handler per
150
+ registered name, not versions: an invoke of `child:prod` runs the handler
151
+ registered as `child:prod` if there is one, otherwise the one registered
152
+ as `child`.
162
153
 
163
- ### Checkpoint Processing Pipeline
164
- - **CheckpointProcessor** - Orchestrates operation transformations and validation
165
- - **Individual Validators** - Validate operation updates and state transitions
166
- - **Individual Processors** - Transform operation updates into operations (step, wait, callback, context, execution)
167
-
168
- ### Execution status changes (Observer Pattern)
169
- - **ExecutionNotifier** - Notifies observers of execution events
170
- - **ExecutionObserver** - Interface for receiving execution lifecycle events
171
- - **Executor** implements `ExecutionObserver` to handle completion events
172
-
173
- ## Component Relationships
154
+ ```python
155
+ from aws_durable_execution_sdk_python.context import DurableContext
156
+ from aws_durable_execution_sdk_python.execution import durable_execution
157
+ from aws_durable_execution_sdk_python_testing.runner import DurableFunctionTestRunner
174
158
 
175
- ### 1. DurableTestRunner → Executor → Execution
176
- - **DurableTestRunner** serves as the main API entry point and sets up all components
177
- - **Executor** manages the execution lifecycle, handling invocations and state transitions
178
- - **Execution** maintains the state of operations and completion status
159
+ @durable_execution
160
+ def process_payment(event: dict, context: DurableContext) -> dict:
161
+ return {"charged": event["amount"]}
179
162
 
180
- ### 2. Service Client Injection
181
- - **DurableTestRunner** creates **InMemoryServiceClient** with **CheckpointProcessor**
182
- - **InProcessInvoker** injects the service client into SDK via `DurableExecutionInvocationInputWithClient`
183
- - When durable functions call checkpoint operations, they're intercepted by **InMemoryServiceClient**
184
- - **InMemoryServiceClient** delegates to **CheckpointProcessor** for local processing
163
+ def lookup_price(event: dict, context) -> dict:
164
+ return {"price": 25}
185
165
 
186
- ### 3. CheckpointProcessor → Individual Validators → Individual Processors
187
- - **CheckpointProcessor** orchestrates the checkpoint processing pipeline
188
- - **Individual Validators** (CheckpointValidator, TransitionsValidator, and operation-specific validators) ensure operation updates are valid
189
- - **Individual Processors** (StepProcessor, WaitProcessor, etc.) transform `OperationUpdate` into `Operation`
166
+ @durable_execution
167
+ def place_order(event: dict, context: DurableContext) -> dict:
168
+ price = context.invoke("lookup-price", {"sku": event["sku"]}, name="price")
169
+ payment = context.invoke(
170
+ "process-payment", {"amount": price["price"]}, name="payment"
171
+ )
172
+ return {"sku": event["sku"], "charged": payment["charged"]}
173
+
174
+ def test_place_order_invokes_both_functions():
175
+ with DurableFunctionTestRunner(handler=place_order) as runner:
176
+ runner.register_durable_function(
177
+ "process-payment", process_payment, execution_timeout=60
178
+ )
179
+ runner.register_function("lookup-price", lookup_price)
180
+ result = runner.run(input='{"sku": "book-1"}')
181
+
182
+ assert result.result == '{"sku": "book-1", "charged": 25}'
183
+ assert result.get_invoke("payment").status.value == "SUCCEEDED"
184
+ ```
190
185
 
191
- ### 4. Observer Pattern Flow
192
- The observer pattern enables loose coupling between checkpoint processing and execution management:
186
+ ## Architecture
193
187
 
194
- 1. **CheckpointProcessor** processes operation updates
195
- 2. **Individual Processors** detect state changes (completion, failures, timer scheduling)
196
- 3. **ExecutionNotifier** broadcasts events to registered observers
197
- 4. **Executor** (as ExecutionObserver) receives notifications and updates **Execution** state
198
- 5. **Execution** complete_* methods finalize the execution state
188
+ See [docs/architecture.md](docs/architecture.md) for framework
189
+ internals. It covers the components, the worker model, the checkpoint
190
+ flow, pagination, and a map of the code.
199
191
 
200
192
 
201
193
  ## Documentation
@@ -220,4 +212,4 @@ tldr; use `hatch` and it will manage virtual envs and dependencies for you, so y
220
212
 
221
213
  ## License
222
214
 
223
- This project is licensed under the [Apache-2.0 License](LICENSE).
215
+ This project is licensed under the [Apache-2.0 License](https://github.com/aws/aws-durable-execution-sdk-python/blob/main/LICENSE).
@@ -12,6 +12,7 @@
12
12
 
13
13
  - [Installation](#installation)
14
14
  - [Quick Start](#quick-start)
15
+ - [Testing functions that invoke other functions](#testing-functions-that-invoke-other-functions)
15
16
  - [Architecture](#architecture)
16
17
  - [Documentation](#documentation)
17
18
  - [Developer Guide](#developers)
@@ -111,67 +112,60 @@ def test_my_durable_functions():
111
112
  three_result: StepOperation = result.get_step("three")
112
113
  assert three_result.result == '"5 6"'
113
114
  ```
114
- ## Architecture
115
- ![Durable Functions Python Test Framework Architecture](/assets/dar-python-test-framework-architecture.svg)
116
-
117
- ## Event Flow
118
- ![Event Flow Sequence Diagram](/assets/dar-python-test-framework-event-flow.svg)
119
-
120
- 1. **DurableTestRunner** starts execution via **Executor**
121
- 2. **Executor** creates **Execution** and schedules initial invocation
122
- 3. During execution, checkpoints are processed by **CheckpointProcessor**
123
- 4. **Individual Processors** transform operation updates and may trigger events
124
- 5. **ExecutionNotifier** broadcasts events to **Executor** (observer)
125
- 6. **Executor** updates **Execution** state based on events
126
- 7. **Execution** completion triggers final event notifications
127
- 8. **DurableTestRunner** run() blocks until it receives completion event, and then returns `DurableFunctionTestResult`.
115
+ ## Testing functions that invoke other functions
128
116
 
129
- ## Major Components
117
+ A durable function can call another function with `context.invoke`. The
118
+ runner needs to know each target's kind: a durable target runs as a
119
+ child durable execution with its own history, while a plain (non-durable)
120
+ target is one invocation whose return value is the result. An unknown
121
+ target fails the operation with `ResourceNotFoundException`.
130
122
 
131
- ### Core Execution Flow
132
- - **DurableTestRunner** - Main entry point that orchestrates test execution
133
- - **Executor** - Manages execution lifecycle. Mutates Execution.
134
- - **Execution** - Represents the state and operations of a single durable execution
123
+ ### In-process runner
135
124
 
136
- ### Service Client Integration
137
- - **InMemoryServiceClient** - Replaces AWS Lambda service client for local testing. Injected into SDK via `DurableExecutionInvocationInputWithClient`
125
+ Register each target with the runner. `register_durable_function` marks a
126
+ durable target, and takes its execution timeout and retention;
127
+ `register_function` marks a plain one. The runner has one handler per
128
+ registered name, not versions: an invoke of `child:prod` runs the handler
129
+ registered as `child:prod` if there is one, otherwise the one registered
130
+ as `child`.
138
131
 
139
- ### Checkpoint Processing Pipeline
140
- - **CheckpointProcessor** - Orchestrates operation transformations and validation
141
- - **Individual Validators** - Validate operation updates and state transitions
142
- - **Individual Processors** - Transform operation updates into operations (step, wait, callback, context, execution)
143
-
144
- ### Execution status changes (Observer Pattern)
145
- - **ExecutionNotifier** - Notifies observers of execution events
146
- - **ExecutionObserver** - Interface for receiving execution lifecycle events
147
- - **Executor** implements `ExecutionObserver` to handle completion events
148
-
149
- ## Component Relationships
132
+ ```python
133
+ from aws_durable_execution_sdk_python.context import DurableContext
134
+ from aws_durable_execution_sdk_python.execution import durable_execution
135
+ from aws_durable_execution_sdk_python_testing.runner import DurableFunctionTestRunner
150
136
 
151
- ### 1. DurableTestRunner → Executor → Execution
152
- - **DurableTestRunner** serves as the main API entry point and sets up all components
153
- - **Executor** manages the execution lifecycle, handling invocations and state transitions
154
- - **Execution** maintains the state of operations and completion status
137
+ @durable_execution
138
+ def process_payment(event: dict, context: DurableContext) -> dict:
139
+ return {"charged": event["amount"]}
155
140
 
156
- ### 2. Service Client Injection
157
- - **DurableTestRunner** creates **InMemoryServiceClient** with **CheckpointProcessor**
158
- - **InProcessInvoker** injects the service client into SDK via `DurableExecutionInvocationInputWithClient`
159
- - When durable functions call checkpoint operations, they're intercepted by **InMemoryServiceClient**
160
- - **InMemoryServiceClient** delegates to **CheckpointProcessor** for local processing
141
+ def lookup_price(event: dict, context) -> dict:
142
+ return {"price": 25}
161
143
 
162
- ### 3. CheckpointProcessor → Individual Validators → Individual Processors
163
- - **CheckpointProcessor** orchestrates the checkpoint processing pipeline
164
- - **Individual Validators** (CheckpointValidator, TransitionsValidator, and operation-specific validators) ensure operation updates are valid
165
- - **Individual Processors** (StepProcessor, WaitProcessor, etc.) transform `OperationUpdate` into `Operation`
144
+ @durable_execution
145
+ def place_order(event: dict, context: DurableContext) -> dict:
146
+ price = context.invoke("lookup-price", {"sku": event["sku"]}, name="price")
147
+ payment = context.invoke(
148
+ "process-payment", {"amount": price["price"]}, name="payment"
149
+ )
150
+ return {"sku": event["sku"], "charged": payment["charged"]}
151
+
152
+ def test_place_order_invokes_both_functions():
153
+ with DurableFunctionTestRunner(handler=place_order) as runner:
154
+ runner.register_durable_function(
155
+ "process-payment", process_payment, execution_timeout=60
156
+ )
157
+ runner.register_function("lookup-price", lookup_price)
158
+ result = runner.run(input='{"sku": "book-1"}')
159
+
160
+ assert result.result == '{"sku": "book-1", "charged": 25}'
161
+ assert result.get_invoke("payment").status.value == "SUCCEEDED"
162
+ ```
166
163
 
167
- ### 4. Observer Pattern Flow
168
- The observer pattern enables loose coupling between checkpoint processing and execution management:
164
+ ## Architecture
169
165
 
170
- 1. **CheckpointProcessor** processes operation updates
171
- 2. **Individual Processors** detect state changes (completion, failures, timer scheduling)
172
- 3. **ExecutionNotifier** broadcasts events to registered observers
173
- 4. **Executor** (as ExecutionObserver) receives notifications and updates **Execution** state
174
- 5. **Execution** complete_* methods finalize the execution state
166
+ See [docs/architecture.md](docs/architecture.md) for framework
167
+ internals. It covers the components, the worker model, the checkpoint
168
+ flow, pagination, and a map of the code.
175
169
 
176
170
 
177
171
  ## Documentation
@@ -196,4 +190,4 @@ tldr; use `hatch` and it will manage virtual envs and dependencies for you, so y
196
190
 
197
191
  ## License
198
192
 
199
- This project is licensed under the [Apache-2.0 License](LICENSE).
193
+ This project is licensed under the [Apache-2.0 License](https://github.com/aws/aws-durable-execution-sdk-python/blob/main/LICENSE).
@@ -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__ = "1.2.1"
4
+ __version__ = "1.3.0"
@@ -0,0 +1,171 @@
1
+ """The shared checkpoint write-transaction.
2
+
3
+ Both checkpoint entry points apply a batch of updates and compute the
4
+ delta of operations the handler has not yet seen. :class:`CheckpointCore`
5
+ holds that common logic so the two callers differ only in their gate,
6
+ locking, and response type.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import TYPE_CHECKING, NamedTuple
12
+
13
+ from aws_durable_execution_sdk_python_testing.checkpoint.validators.checkpoint import (
14
+ CheckpointValidator,
15
+ )
16
+ from aws_durable_execution_sdk_python_testing.execution import (
17
+ CheckpointIdempotencyRecord,
18
+ OperationPaginatorState,
19
+ )
20
+ from aws_durable_execution_sdk_python_testing.token import CheckpointToken
21
+
22
+ if TYPE_CHECKING:
23
+ from datetime import datetime
24
+
25
+ from aws_durable_execution_sdk_python.lambda_service import (
26
+ Operation,
27
+ OperationUpdate,
28
+ )
29
+
30
+ from aws_durable_execution_sdk_python_testing.checkpoint.effects import (
31
+ CheckpointEffect,
32
+ )
33
+ from aws_durable_execution_sdk_python_testing.checkpoint.transformer import (
34
+ CheckpointRequestDispatcher,
35
+ )
36
+ from aws_durable_execution_sdk_python_testing.execution import Execution
37
+
38
+
39
+ class CheckpointResult(NamedTuple):
40
+ """Outcome of applying a checkpoint: the new token, the operations to
41
+ return to the handler this round, and the lifecycle effects raised.
42
+
43
+ ``checkpoint_token`` is None when the execution is paused and the token
44
+ is withheld, telling the SDK this invocation may checkpoint no further.
45
+ """
46
+
47
+ checkpoint_token: str | None
48
+ operations: list[Operation]
49
+ effects: list[CheckpointEffect]
50
+
51
+
52
+ class CheckpointCore:
53
+ """The checkpoint write-transaction shared by both entry points."""
54
+
55
+ @staticmethod
56
+ def match_cached(
57
+ execution: Execution,
58
+ checkpoint_token: str,
59
+ client_token: str | None,
60
+ ) -> CheckpointIdempotencyRecord | None:
61
+ """Return the cached idempotency record when the incoming
62
+ ``(client_token, checkpoint_token)`` matches the last checkpoint,
63
+ else ``None``.
64
+
65
+ A missing ``client_token`` cannot replay: idempotency requires an
66
+ explicit client identifier. The caller builds its own response
67
+ type from the returned record.
68
+ """
69
+ if not client_token:
70
+ return None
71
+ cached: CheckpointIdempotencyRecord | None = execution.last_checkpoint
72
+ if cached is None:
73
+ return None
74
+ if (
75
+ cached.client_token != client_token
76
+ or cached.inbound_checkpoint_token != checkpoint_token
77
+ ):
78
+ return None
79
+ return cached
80
+
81
+ @staticmethod
82
+ def apply(
83
+ execution: Execution,
84
+ checkpoint_token: str,
85
+ updates: list[OperationUpdate],
86
+ client_token: str | None,
87
+ dispatcher: CheckpointRequestDispatcher,
88
+ now: datetime,
89
+ ) -> CheckpointResult:
90
+ """Apply ``updates`` to ``execution`` and compute the response delta.
91
+
92
+ Advances ``token_sequence`` exactly once, returns the full set of
93
+ operations the handler has not yet seen unless paused, advances
94
+ ``handler_seen_seq`` only for returned operations, and records an
95
+ idempotency entry for a byte-identical replay of a retried call
96
+ only when returning a checkpoint token.
97
+
98
+ The caller is responsible for the invocation gate, locking,
99
+ persistence, and applying the returned effects.
100
+
101
+ ``now`` is the checkpoint's single source of "now", resolved from
102
+ the execution's clock, so all timestamps stamped by this apply
103
+ advance with modeled time under a skip clock.
104
+
105
+ Before computing the delta, completes every wait and step retry
106
+ whose scheduled moment has passed, so those transitions are
107
+ delivered in this response and a suspended branch resumes within
108
+ the running invocation instead of waiting for the next one.
109
+ """
110
+ effects: list[CheckpointEffect] = []
111
+ if updates:
112
+ CheckpointValidator.validate_input(updates, execution)
113
+ effects = dispatcher.apply_updates(
114
+ execution=execution,
115
+ updates=updates,
116
+ client_token=client_token,
117
+ touch=execution.touch_operation,
118
+ now=now,
119
+ )
120
+
121
+ new_token_sequence: int = execution.advance_token_sequence()
122
+
123
+ # Pick up scheduler-due completions before pinning the paginator:
124
+ # each transition touches its operation, so it lands in this
125
+ # response's unseen delta and the handler sees it immediately.
126
+ execution.complete_due_operations(now)
127
+
128
+ paginator: OperationPaginatorState = OperationPaginatorState.pin(execution)
129
+ # The checkpoint response returns the full unseen delta in a single
130
+ # response. Advance handler_seen_seq to cover every returned op so
131
+ # the next delta carries only operations touched after this response.
132
+ # Paused checkpoints persist updates without delivering any state.
133
+ response_ops: list[Operation] = (
134
+ [] if execution.is_paused else paginator.unseen_operations()
135
+ )
136
+ if response_ops:
137
+ highest_delivered_seq: int = max(
138
+ execution.operation_last_touched_seq[op.operation_id]
139
+ for op in response_ops
140
+ )
141
+ paginator.advance_handler_seen(highest_delivered_seq)
142
+
143
+ new_token: str = CheckpointToken(
144
+ execution_arn=execution.durable_execution_arn,
145
+ token_sequence=new_token_sequence,
146
+ invocation_id=execution.current_invocation_id,
147
+ ).to_str()
148
+
149
+ # A paused execution registers this checkpoint's updates but withholds
150
+ # the token: a response without one tells the SDK this invocation may
151
+ # checkpoint no further, so it reports PENDING at its next checkpoint
152
+ # rather than continuing, and owes a re-invoke once resumed.
153
+ #
154
+ # Checkpoints that were interrupted by a pause are not added to the
155
+ # idempotency record. A retry of this checkpoint will fail rather than be
156
+ # answered again.
157
+
158
+ outbound_token: str | None = new_token
159
+ if execution.is_paused:
160
+ outbound_token = None
161
+ execution.defer_invocation()
162
+ else:
163
+ execution.last_checkpoint = CheckpointIdempotencyRecord(
164
+ client_token=client_token or "",
165
+ inbound_checkpoint_token=checkpoint_token,
166
+ outbound_checkpoint_token=outbound_token,
167
+ operations=list(response_ops),
168
+ next_marker=None,
169
+ )
170
+
171
+ return CheckpointResult(outbound_token, response_ops, effects)
@@ -0,0 +1,61 @@
1
+ """Lifecycle effects produced by applying checkpoint updates.
2
+
3
+ Applying a batch of operation updates can imply actions on the execution
4
+ beyond the state change itself: the execution completed, it failed, or a
5
+ callback was created and now needs a timeout. These are returned as data
6
+ so the caller can act on them after the write finishes, rather than
7
+ triggering them in the middle of it.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from typing import TYPE_CHECKING
14
+
15
+ if TYPE_CHECKING:
16
+ from aws_durable_execution_sdk_python.lambda_service import (
17
+ CallbackOptions,
18
+ ErrorObject,
19
+ )
20
+
21
+ from aws_durable_execution_sdk_python_testing.token import CallbackToken
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class Completed:
26
+ """The execution completed successfully with ``result``."""
27
+
28
+ execution_arn: str
29
+ result: str | None = None
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class Failed:
34
+ """The execution failed with ``error``."""
35
+
36
+ execution_arn: str
37
+ error: ErrorObject | None
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class CallbackCreated:
42
+ """A callback was created and its timeout (if any) must be scheduled."""
43
+
44
+ execution_arn: str
45
+ operation_id: str
46
+ callback_options: CallbackOptions | None
47
+ callback_token: CallbackToken
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class ChainedInvokeStarted:
52
+ """A chained invoke was accepted and its target must be dispatched."""
53
+
54
+ execution_arn: str
55
+ operation_id: str
56
+ function_name: str
57
+ tenant_id: str | None
58
+ payload: str | None
59
+
60
+
61
+ CheckpointEffect = Completed | Failed | CallbackCreated | ChainedInvokeStarted