aws-durable-execution-sdk-python-testing 1.0.0__py3-none-any.whl

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 (52) hide show
  1. aws_durable_execution_sdk_python_testing/__about__.py +4 -0
  2. aws_durable_execution_sdk_python_testing/__init__.py +20 -0
  3. aws_durable_execution_sdk_python_testing/checkpoint/__init__.py +1 -0
  4. aws_durable_execution_sdk_python_testing/checkpoint/processor.py +101 -0
  5. aws_durable_execution_sdk_python_testing/checkpoint/processors/__init__.py +1 -0
  6. aws_durable_execution_sdk_python_testing/checkpoint/processors/base.py +199 -0
  7. aws_durable_execution_sdk_python_testing/checkpoint/processors/callback.py +89 -0
  8. aws_durable_execution_sdk_python_testing/checkpoint/processors/context.py +59 -0
  9. aws_durable_execution_sdk_python_testing/checkpoint/processors/execution.py +52 -0
  10. aws_durable_execution_sdk_python_testing/checkpoint/processors/step.py +124 -0
  11. aws_durable_execution_sdk_python_testing/checkpoint/processors/wait.py +95 -0
  12. aws_durable_execution_sdk_python_testing/checkpoint/transformer.py +104 -0
  13. aws_durable_execution_sdk_python_testing/checkpoint/validators/__init__.py +1 -0
  14. aws_durable_execution_sdk_python_testing/checkpoint/validators/checkpoint.py +242 -0
  15. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/__init__.py +1 -0
  16. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/callback.py +45 -0
  17. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/context.py +73 -0
  18. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/execution.py +47 -0
  19. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/invoke.py +56 -0
  20. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/step.py +106 -0
  21. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/wait.py +54 -0
  22. aws_durable_execution_sdk_python_testing/checkpoint/validators/transitions.py +66 -0
  23. aws_durable_execution_sdk_python_testing/cli.py +501 -0
  24. aws_durable_execution_sdk_python_testing/client.py +50 -0
  25. aws_durable_execution_sdk_python_testing/exceptions.py +288 -0
  26. aws_durable_execution_sdk_python_testing/execution.py +440 -0
  27. aws_durable_execution_sdk_python_testing/executor.py +1214 -0
  28. aws_durable_execution_sdk_python_testing/invoker.py +327 -0
  29. aws_durable_execution_sdk_python_testing/model.py +3267 -0
  30. aws_durable_execution_sdk_python_testing/observer.py +144 -0
  31. aws_durable_execution_sdk_python_testing/py.typed +1 -0
  32. aws_durable_execution_sdk_python_testing/runner.py +1185 -0
  33. aws_durable_execution_sdk_python_testing/scheduler.py +246 -0
  34. aws_durable_execution_sdk_python_testing/stores/__init__.py +1 -0
  35. aws_durable_execution_sdk_python_testing/stores/base.py +147 -0
  36. aws_durable_execution_sdk_python_testing/stores/filesystem.py +104 -0
  37. aws_durable_execution_sdk_python_testing/stores/memory.py +38 -0
  38. aws_durable_execution_sdk_python_testing/stores/sqlite.py +283 -0
  39. aws_durable_execution_sdk_python_testing/token.py +49 -0
  40. aws_durable_execution_sdk_python_testing/web/__init__.py +1 -0
  41. aws_durable_execution_sdk_python_testing/web/errors.py +8 -0
  42. aws_durable_execution_sdk_python_testing/web/handlers.py +807 -0
  43. aws_durable_execution_sdk_python_testing/web/models.py +266 -0
  44. aws_durable_execution_sdk_python_testing/web/routes.py +684 -0
  45. aws_durable_execution_sdk_python_testing/web/serialization.py +245 -0
  46. aws_durable_execution_sdk_python_testing/web/server.py +243 -0
  47. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/METADATA +221 -0
  48. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/RECORD +52 -0
  49. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/WHEEL +4 -0
  50. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/entry_points.txt +2 -0
  51. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/licenses/LICENSE +175 -0
  52. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/licenses/NOTICE +1 -0
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2025-present Amazon.com, Inc. or its affiliates.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ __version__ = "1.0.0"
@@ -0,0 +1,20 @@
1
+ """DurableExecutionsPythonTestingLibrary module."""
2
+
3
+ from aws_durable_execution_sdk_python_testing.runner import (
4
+ DurableChildContextTestRunner,
5
+ DurableFunctionCloudTestRunner,
6
+ DurableFunctionTestResult,
7
+ DurableFunctionTestRunner,
8
+ WebRunner,
9
+ WebRunnerConfig,
10
+ )
11
+
12
+
13
+ __all__ = [
14
+ "DurableChildContextTestRunner",
15
+ "DurableFunctionCloudTestRunner",
16
+ "DurableFunctionTestResult",
17
+ "DurableFunctionTestRunner",
18
+ "WebRunner",
19
+ "WebRunnerConfig",
20
+ ]
@@ -0,0 +1 @@
1
+ """Checkpoint processing module for handling OperationUpdate transformations."""
@@ -0,0 +1,101 @@
1
+ """Main checkpoint processor that orchestrates operation transformations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from aws_durable_execution_sdk_python.lambda_service import (
8
+ CheckpointOutput,
9
+ CheckpointUpdatedExecutionState,
10
+ OperationUpdate,
11
+ StateOutput,
12
+ )
13
+
14
+ from aws_durable_execution_sdk_python_testing.checkpoint.transformer import (
15
+ OperationTransformer,
16
+ )
17
+ from aws_durable_execution_sdk_python_testing.checkpoint.validators.checkpoint import (
18
+ CheckpointValidator,
19
+ )
20
+ from aws_durable_execution_sdk_python_testing.exceptions import (
21
+ InvalidParameterValueException,
22
+ )
23
+ from aws_durable_execution_sdk_python_testing.observer import ExecutionNotifier
24
+ from aws_durable_execution_sdk_python_testing.token import CheckpointToken
25
+
26
+
27
+ if TYPE_CHECKING:
28
+ from aws_durable_execution_sdk_python_testing.execution import Execution
29
+ from aws_durable_execution_sdk_python_testing.scheduler import Scheduler
30
+ from aws_durable_execution_sdk_python_testing.stores.base import ExecutionStore
31
+
32
+
33
+ class CheckpointProcessor:
34
+ """Handle OperationUpdate transformations and execution state updates."""
35
+
36
+ def __init__(self, store: ExecutionStore, scheduler: Scheduler):
37
+ self._store = store
38
+ self._scheduler = scheduler
39
+ self._notifier = ExecutionNotifier()
40
+ self._transformer = OperationTransformer()
41
+
42
+ def add_execution_observer(self, observer) -> None:
43
+ """Add observer for execution events."""
44
+ self._notifier.add_observer(observer)
45
+
46
+ def process_checkpoint(
47
+ self,
48
+ checkpoint_token: str,
49
+ updates: list[OperationUpdate],
50
+ client_token: str | None, # noqa: ARG002
51
+ ) -> CheckpointOutput:
52
+ """Process checkpoint updates and return result with updated execution state."""
53
+ # 1. Get current execution state
54
+ token: CheckpointToken = CheckpointToken.from_str(checkpoint_token)
55
+ execution: Execution = self._store.load(token.execution_arn)
56
+
57
+ # 2. Validate checkpoint token
58
+ if execution.is_complete or token.token_sequence != execution.token_sequence:
59
+ msg: str = "Invalid checkpoint token"
60
+
61
+ raise InvalidParameterValueException(msg)
62
+
63
+ # 3. Validate all updates, state transitions are valid, sizes etc.
64
+ CheckpointValidator.validate_input(updates, execution)
65
+
66
+ # 4. Transform OperationUpdate -> Operation and schedule future replays
67
+ updated_operations, all_updates = self._transformer.process_updates(
68
+ updates=updates,
69
+ current_operations=execution.operations,
70
+ notifier=self._notifier,
71
+ execution_arn=token.execution_arn,
72
+ )
73
+
74
+ # 5. Generate a new checkpoint token and save updated operations
75
+ new_checkpoint_token = execution.get_new_checkpoint_token()
76
+ execution.operations = updated_operations
77
+ execution.updates.extend(all_updates)
78
+ self._store.update(execution)
79
+
80
+ # 6. Return checkpoint result
81
+ return CheckpointOutput(
82
+ checkpoint_token=new_checkpoint_token,
83
+ new_execution_state=CheckpointUpdatedExecutionState(
84
+ operations=execution.get_navigable_operations(), next_marker=None
85
+ ),
86
+ )
87
+
88
+ def get_execution_state(
89
+ self,
90
+ checkpoint_token: str,
91
+ next_marker: str, # noqa: ARG002
92
+ max_items: int = 1000, # noqa: ARG002
93
+ ) -> StateOutput:
94
+ """Get current execution state."""
95
+ token: CheckpointToken = CheckpointToken.from_str(checkpoint_token)
96
+ execution: Execution = self._store.load(token.execution_arn)
97
+
98
+ # TODO: paging when size or max
99
+ return StateOutput(
100
+ operations=execution.get_navigable_operations(), next_marker=None
101
+ )
@@ -0,0 +1 @@
1
+ """Checkpoint processors module."""
@@ -0,0 +1,199 @@
1
+ """Base processor class for operation transformations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime
6
+ from datetime import timedelta
7
+ from typing import TYPE_CHECKING
8
+
9
+ from aws_durable_execution_sdk_python.lambda_service import (
10
+ CallbackDetails,
11
+ ChainedInvokeDetails,
12
+ ContextDetails,
13
+ ExecutionDetails,
14
+ Operation,
15
+ OperationAction,
16
+ OperationStatus,
17
+ OperationType,
18
+ OperationUpdate,
19
+ StepDetails,
20
+ WaitDetails,
21
+ )
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from aws_durable_execution_sdk_python_testing.observer import ExecutionNotifier
26
+
27
+
28
+ class OperationProcessor:
29
+ """Base class for processing OperationUpdate to Operation transformations."""
30
+
31
+ def process(
32
+ self,
33
+ update: OperationUpdate,
34
+ current_op: Operation | None,
35
+ notifier: ExecutionNotifier,
36
+ execution_arn: str,
37
+ ) -> Operation | None:
38
+ """Process an operation update and return the transformed operation."""
39
+ raise NotImplementedError
40
+
41
+ def _get_start_time(
42
+ self, current_operation: Operation | None
43
+ ) -> datetime.datetime | None:
44
+ start_time: datetime.datetime | None = (
45
+ current_operation.start_timestamp
46
+ if current_operation
47
+ else datetime.datetime.now(tz=datetime.UTC)
48
+ )
49
+ return start_time
50
+
51
+ def _get_end_time(
52
+ self, current_operation: Operation | None, status: OperationStatus
53
+ ) -> datetime.datetime | None:
54
+ """Get end timestamp for operation based on current state and status."""
55
+ if current_operation and current_operation.end_timestamp:
56
+ return current_operation.end_timestamp
57
+ if status in {
58
+ OperationStatus.SUCCEEDED,
59
+ OperationStatus.FAILED,
60
+ OperationStatus.CANCELLED,
61
+ OperationStatus.TIMED_OUT,
62
+ OperationStatus.STOPPED,
63
+ }:
64
+ return datetime.datetime.now(tz=datetime.UTC)
65
+ return None
66
+
67
+ def _create_execution_details(
68
+ self, update: OperationUpdate
69
+ ) -> ExecutionDetails | None:
70
+ """Create ExecutionDetails from OperationUpdate."""
71
+ return (
72
+ ExecutionDetails(input_payload=update.payload)
73
+ if update.operation_type == OperationType.EXECUTION
74
+ else None
75
+ )
76
+
77
+ def _create_context_details(self, update: OperationUpdate) -> ContextDetails | None:
78
+ """Create ContextDetails from OperationUpdate."""
79
+ return (
80
+ ContextDetails(
81
+ result=update.payload,
82
+ error=update.error,
83
+ replay_children=update.context_options.replay_children
84
+ if update.context_options
85
+ else False,
86
+ )
87
+ if update.operation_type == OperationType.CONTEXT
88
+ else None
89
+ )
90
+
91
+ def _create_step_details(
92
+ self,
93
+ update: OperationUpdate,
94
+ current_operation: Operation | None = None,
95
+ ) -> StepDetails | None:
96
+ """Create StepDetails from OperationUpdate.
97
+
98
+ Automatically increments attempt count for RETRY, SUCCEED, and FAIL actions.
99
+ """
100
+
101
+ attempt: int = 0
102
+ next_attempt_timestamp: datetime.datetime | None = None
103
+
104
+ if update.operation_type is OperationType.STEP:
105
+ if current_operation and current_operation.step_details:
106
+ attempt = current_operation.step_details.attempt
107
+ next_attempt_timestamp = (
108
+ current_operation.step_details.next_attempt_timestamp
109
+ )
110
+ # Increment attempt for RETRY, SUCCEED, and FAIL actions
111
+ if update.action in {
112
+ OperationAction.RETRY,
113
+ OperationAction.SUCCEED,
114
+ OperationAction.FAIL,
115
+ }:
116
+ attempt += 1
117
+ return StepDetails(
118
+ attempt=attempt,
119
+ next_attempt_timestamp=next_attempt_timestamp,
120
+ result=update.payload,
121
+ error=update.error,
122
+ )
123
+
124
+ return None
125
+
126
+ def _create_callback_details(
127
+ self, update: OperationUpdate
128
+ ) -> CallbackDetails | None:
129
+ """Create CallbackDetails from OperationUpdate."""
130
+ return (
131
+ CallbackDetails(
132
+ callback_id="placeholder", result=update.payload, error=update.error
133
+ )
134
+ if update.operation_type == OperationType.CALLBACK
135
+ else None
136
+ )
137
+
138
+ def _create_invoke_details(
139
+ self, update: OperationUpdate
140
+ ) -> ChainedInvokeDetails | None:
141
+ """Create ChainedInvokeDetails from OperationUpdate."""
142
+ if (
143
+ update.operation_type == OperationType.CHAINED_INVOKE
144
+ and update.chained_invoke_options
145
+ ):
146
+ return ChainedInvokeDetails(result=update.payload, error=update.error)
147
+ return None
148
+
149
+ def _translate_update_to_operation(
150
+ self,
151
+ update: OperationUpdate,
152
+ current_operation: Operation | None,
153
+ status: OperationStatus,
154
+ ) -> Operation:
155
+ """Transform OperationUpdate to Operation, always creating new Operation."""
156
+ start_time: datetime.datetime | None = self._get_start_time(current_operation)
157
+ end_time: datetime.datetime | None = self._get_end_time(
158
+ current_operation, status
159
+ )
160
+
161
+ execution_details = self._create_execution_details(update)
162
+ context_details = self._create_context_details(update)
163
+ step_details = self._create_step_details(update, current_operation)
164
+ callback_details = self._create_callback_details(update)
165
+ invoke_details = self._create_invoke_details(update)
166
+ wait_details = self._create_wait_details(update, current_operation)
167
+
168
+ return Operation(
169
+ operation_id=update.operation_id,
170
+ parent_id=update.parent_id,
171
+ name=update.name,
172
+ start_timestamp=start_time,
173
+ end_timestamp=end_time,
174
+ operation_type=update.operation_type,
175
+ status=status,
176
+ sub_type=update.sub_type,
177
+ execution_details=execution_details,
178
+ context_details=context_details,
179
+ step_details=step_details,
180
+ callback_details=callback_details,
181
+ chained_invoke_details=invoke_details,
182
+ wait_details=wait_details,
183
+ )
184
+
185
+ def _create_wait_details(
186
+ self, update: OperationUpdate, current_operation: Operation | None
187
+ ) -> WaitDetails | None:
188
+ """Create WaitDetails from OperationUpdate."""
189
+ if update.operation_type == OperationType.WAIT and update.wait_options:
190
+ if current_operation and current_operation.wait_details:
191
+ scheduled_end_timestamp = (
192
+ current_operation.wait_details.scheduled_end_timestamp
193
+ )
194
+ else:
195
+ scheduled_end_timestamp = datetime.datetime.now(
196
+ tz=datetime.UTC
197
+ ) + timedelta(seconds=update.wait_options.wait_seconds)
198
+ return WaitDetails(scheduled_end_timestamp=scheduled_end_timestamp)
199
+ return None
@@ -0,0 +1,89 @@
1
+ """Callback operation processor for handling CALLBACK operation updates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime
6
+ from typing import TYPE_CHECKING
7
+
8
+ from aws_durable_execution_sdk_python.lambda_service import (
9
+ Operation,
10
+ OperationAction,
11
+ OperationStatus,
12
+ OperationUpdate,
13
+ CallbackDetails,
14
+ OperationType,
15
+ CallbackOptions,
16
+ )
17
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.base import (
18
+ OperationProcessor,
19
+ )
20
+ from aws_durable_execution_sdk_python_testing.exceptions import (
21
+ InvalidParameterValueException,
22
+ )
23
+ from aws_durable_execution_sdk_python_testing.token import CallbackToken
24
+
25
+ if TYPE_CHECKING:
26
+ from aws_durable_execution_sdk_python_testing.observer import ExecutionNotifier
27
+
28
+
29
+ class CallbackProcessor(OperationProcessor):
30
+ """Processes CALLBACK operation updates with activity scheduling."""
31
+
32
+ def process(
33
+ self,
34
+ update: OperationUpdate,
35
+ current_op: Operation | None,
36
+ notifier: ExecutionNotifier, # noqa: ARG002
37
+ execution_arn: str, # noqa: ARG002
38
+ ) -> Operation:
39
+ """Process CALLBACK operation update with scheduler integration for activities."""
40
+ match update.action:
41
+ case OperationAction.START:
42
+ callback_token: CallbackToken = CallbackToken(
43
+ execution_arn=execution_arn,
44
+ operation_id=update.operation_id,
45
+ )
46
+
47
+ callback_id: str = callback_token.to_str()
48
+
49
+ callback_details: CallbackDetails | None = (
50
+ CallbackDetails(
51
+ callback_id=callback_id,
52
+ result=update.payload,
53
+ error=update.error,
54
+ )
55
+ if update.operation_type == OperationType.CALLBACK
56
+ else None
57
+ )
58
+
59
+ status: OperationStatus = OperationStatus.STARTED
60
+
61
+ start_time: datetime.datetime | None = self._get_start_time(current_op)
62
+
63
+ end_time: datetime.datetime | None = self._get_end_time(
64
+ current_op, status
65
+ )
66
+
67
+ operation: Operation = Operation(
68
+ operation_id=update.operation_id,
69
+ parent_id=update.parent_id,
70
+ name=update.name,
71
+ start_timestamp=start_time,
72
+ end_timestamp=end_time,
73
+ operation_type=update.operation_type,
74
+ status=status,
75
+ sub_type=update.sub_type,
76
+ callback_details=callback_details,
77
+ )
78
+ callback_options: CallbackOptions | None = update.callback_options
79
+
80
+ notifier.notify_callback_created(
81
+ execution_arn=execution_arn,
82
+ operation_id=update.operation_id,
83
+ callback_options=callback_options,
84
+ callback_token=callback_token,
85
+ )
86
+ return operation
87
+ case _:
88
+ msg: str = "Invalid action for CALLBACK operation."
89
+ raise InvalidParameterValueException(msg)
@@ -0,0 +1,59 @@
1
+ """Context operation processor for handling CONTEXT operation updates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from aws_durable_execution_sdk_python.lambda_service import (
8
+ Operation,
9
+ OperationAction,
10
+ OperationStatus,
11
+ OperationUpdate,
12
+ )
13
+
14
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.base import (
15
+ OperationProcessor,
16
+ )
17
+ from aws_durable_execution_sdk_python_testing.exceptions import (
18
+ InvalidParameterValueException,
19
+ )
20
+
21
+
22
+ if TYPE_CHECKING:
23
+ from aws_durable_execution_sdk_python_testing.observer import ExecutionNotifier
24
+
25
+
26
+ class ContextProcessor(OperationProcessor):
27
+ """Processes CONTEXT operation updates for execution context management."""
28
+
29
+ def process(
30
+ self,
31
+ update: OperationUpdate,
32
+ current_op: Operation | None,
33
+ notifier: ExecutionNotifier, # noqa: ARG002
34
+ execution_arn: str, # noqa: ARG002
35
+ ) -> Operation:
36
+ """Process CONTEXT operation update for context state transitions."""
37
+ match update.action:
38
+ case OperationAction.START:
39
+ # TODO: check for "Cannot start a CONTEXT operation that already exists."
40
+ return self._translate_update_to_operation(
41
+ update=update,
42
+ current_operation=current_op,
43
+ status=OperationStatus.STARTED,
44
+ )
45
+ case OperationAction.SUCCEED:
46
+ return self._translate_update_to_operation(
47
+ update=update,
48
+ current_operation=current_op,
49
+ status=OperationStatus.SUCCEEDED,
50
+ )
51
+ case OperationAction.FAIL:
52
+ return self._translate_update_to_operation(
53
+ update=update,
54
+ current_operation=current_op,
55
+ status=OperationStatus.FAILED,
56
+ )
57
+ case _:
58
+ msg: str = "Invalid action for CONTEXT operation."
59
+ raise InvalidParameterValueException(msg)
@@ -0,0 +1,52 @@
1
+ """Execution operation processor for handling EXECUTION operation updates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from aws_durable_execution_sdk_python.lambda_service import (
8
+ ErrorObject,
9
+ Operation,
10
+ OperationAction,
11
+ OperationUpdate,
12
+ )
13
+
14
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.base import (
15
+ OperationProcessor,
16
+ )
17
+
18
+
19
+ if TYPE_CHECKING:
20
+ from aws_durable_execution_sdk_python_testing.observer import ExecutionNotifier
21
+
22
+
23
+ class ExecutionProcessor(OperationProcessor):
24
+ """Processes EXECUTION operation updates for workflow completion."""
25
+
26
+ def process(
27
+ self,
28
+ update: OperationUpdate,
29
+ current_op: Operation | None, # noqa: ARG002
30
+ notifier: ExecutionNotifier,
31
+ execution_arn: str,
32
+ ) -> Operation | None:
33
+ """Process EXECUTION operation update for workflow completion/failure."""
34
+ match update.action:
35
+ case OperationAction.SUCCEED:
36
+ notifier.notify_completed(
37
+ execution_arn=execution_arn, result=update.payload
38
+ )
39
+ case _:
40
+ # intentional. actual service will fail any EXECUTION update that is not SUCCEED.
41
+ error = (
42
+ update.error
43
+ if update.error
44
+ else ErrorObject.from_message(
45
+ "There is no error details but EXECUTION checkpoint action is not SUCCEED."
46
+ )
47
+ )
48
+ # All EXECUTION failures go through normal fail path
49
+ # Timeout/Stop status is set by executor based on the operation that caused it
50
+ notifier.notify_failed(execution_arn=execution_arn, error=error)
51
+ # TODO: Svc doesn't actually create checkpoint for EXECUTION. might have to for localrunner though.
52
+ return None
@@ -0,0 +1,124 @@
1
+ """Step operation processor for handling STEP operation updates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import UTC, datetime, timedelta
6
+ from typing import TYPE_CHECKING
7
+
8
+ from aws_durable_execution_sdk_python.lambda_service import (
9
+ Operation,
10
+ OperationAction,
11
+ OperationStatus,
12
+ OperationUpdate,
13
+ StepDetails,
14
+ )
15
+
16
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.base import (
17
+ OperationProcessor,
18
+ )
19
+ from aws_durable_execution_sdk_python_testing.exceptions import (
20
+ InvalidParameterValueException,
21
+ )
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from aws_durable_execution_sdk_python_testing.observer import ExecutionNotifier
26
+
27
+
28
+ class StepProcessor(OperationProcessor):
29
+ """Processes STEP operation updates with retry scheduling."""
30
+
31
+ def process(
32
+ self,
33
+ update: OperationUpdate,
34
+ current_op: Operation | None,
35
+ notifier: ExecutionNotifier,
36
+ execution_arn: str,
37
+ ) -> Operation:
38
+ """Process STEP operation update with scheduler integration for retries."""
39
+ match update.action:
40
+ case OperationAction.START:
41
+ return self._translate_update_to_operation(
42
+ update=update,
43
+ current_operation=current_op,
44
+ status=OperationStatus.STARTED,
45
+ )
46
+ case OperationAction.RETRY:
47
+ # set Status=PENDING, next attempt time, attempt count + 1
48
+ delay = (
49
+ update.step_options.next_attempt_delay_seconds
50
+ if update.step_options
51
+ else 0
52
+ )
53
+ next_attempt_time = datetime.now(UTC) + timedelta(seconds=delay)
54
+
55
+ # Build new step_details with incremented attempt
56
+ current_attempt = (
57
+ current_op.step_details.attempt
58
+ if current_op and current_op.step_details
59
+ else 0
60
+ )
61
+ new_step_details = StepDetails(
62
+ attempt=current_attempt + 1,
63
+ next_attempt_timestamp=next_attempt_time,
64
+ result=(
65
+ current_op.step_details.result
66
+ if current_op and current_op.step_details
67
+ else None
68
+ ),
69
+ error=(
70
+ current_op.step_details.error
71
+ if current_op and current_op.step_details
72
+ else None
73
+ ),
74
+ )
75
+
76
+ # Create new operation with updated step_details
77
+ retry_operation = Operation(
78
+ operation_id=update.operation_id,
79
+ operation_type=update.operation_type,
80
+ status=OperationStatus.PENDING,
81
+ parent_id=update.parent_id,
82
+ name=update.name,
83
+ start_timestamp=(
84
+ current_op.start_timestamp if current_op else datetime.now(UTC)
85
+ ),
86
+ end_timestamp=None,
87
+ sub_type=update.sub_type,
88
+ execution_details=current_op.execution_details
89
+ if current_op
90
+ else None,
91
+ context_details=current_op.context_details if current_op else None,
92
+ step_details=new_step_details,
93
+ wait_details=current_op.wait_details if current_op else None,
94
+ callback_details=current_op.callback_details
95
+ if current_op
96
+ else None,
97
+ chained_invoke_details=current_op.chained_invoke_details
98
+ if current_op
99
+ else None,
100
+ )
101
+
102
+ # Schedule step retry timer to fire after delay
103
+ notifier.notify_step_retry_scheduled(
104
+ execution_arn=execution_arn,
105
+ operation_id=update.operation_id,
106
+ delay=delay,
107
+ )
108
+ return retry_operation
109
+ case OperationAction.SUCCEED:
110
+ return self._translate_update_to_operation(
111
+ update=update,
112
+ current_operation=current_op,
113
+ status=OperationStatus.SUCCEEDED,
114
+ )
115
+ case OperationAction.FAIL:
116
+ return self._translate_update_to_operation(
117
+ update=update,
118
+ current_operation=current_op,
119
+ status=OperationStatus.FAILED,
120
+ )
121
+ case _:
122
+ msg: str = "Invalid action for STEP operation."
123
+
124
+ raise InvalidParameterValueException(msg)