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,95 @@
1
+ """Wait operation processor for handling WAIT operation updates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ from datetime import UTC, datetime, timedelta
8
+ from typing import TYPE_CHECKING
9
+
10
+ from aws_durable_execution_sdk_python.lambda_service import (
11
+ Operation,
12
+ OperationAction,
13
+ OperationStatus,
14
+ OperationUpdate,
15
+ WaitDetails,
16
+ )
17
+
18
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.base import (
19
+ OperationProcessor,
20
+ )
21
+ from aws_durable_execution_sdk_python_testing.exceptions import (
22
+ InvalidParameterValueException,
23
+ )
24
+
25
+
26
+ if TYPE_CHECKING:
27
+ from aws_durable_execution_sdk_python_testing.observer import ExecutionNotifier
28
+
29
+
30
+ class WaitProcessor(OperationProcessor):
31
+ """Processes WAIT operation updates with timer scheduling."""
32
+
33
+ def process(
34
+ self,
35
+ update: OperationUpdate,
36
+ current_op: Operation | None,
37
+ notifier: ExecutionNotifier,
38
+ execution_arn: str,
39
+ ) -> Operation:
40
+ """Process WAIT operation update with scheduler integration for timers."""
41
+ match update.action:
42
+ case OperationAction.START:
43
+ wait_seconds = (
44
+ update.wait_options.wait_seconds if update.wait_options else 0
45
+ )
46
+ time_scale = float(os.getenv("DURABLE_EXECUTION_TIME_SCALE", "1.0"))
47
+ logging.info("Using DURABLE_EXECUTION_TIME_SCALE: %f", time_scale)
48
+ scaled_wait_seconds = wait_seconds * time_scale
49
+
50
+ scheduled_end_timestamp = datetime.now(UTC) + timedelta(
51
+ seconds=scaled_wait_seconds
52
+ )
53
+
54
+ # Create WaitDetails with scheduled timestamp
55
+ wait_details = WaitDetails(
56
+ scheduled_end_timestamp=scheduled_end_timestamp
57
+ )
58
+
59
+ # Create new operation with wait details
60
+ wait_operation = Operation(
61
+ operation_id=update.operation_id,
62
+ operation_type=update.operation_type,
63
+ status=OperationStatus.STARTED,
64
+ parent_id=update.parent_id,
65
+ name=update.name,
66
+ start_timestamp=datetime.now(UTC),
67
+ end_timestamp=None,
68
+ sub_type=update.sub_type,
69
+ execution_details=None,
70
+ context_details=None,
71
+ step_details=None,
72
+ wait_details=wait_details,
73
+ callback_details=None,
74
+ chained_invoke_details=None,
75
+ )
76
+
77
+ # Schedule wait timer to complete after delay
78
+ notifier.notify_wait_timer_scheduled(
79
+ execution_arn=execution_arn,
80
+ operation_id=update.operation_id,
81
+ delay=scaled_wait_seconds,
82
+ )
83
+ return wait_operation
84
+ case OperationAction.CANCEL:
85
+ # TODO: need to cancel the WAIT in the executor
86
+ # TODO: increase sequence id
87
+ return self._translate_update_to_operation(
88
+ update=update,
89
+ current_operation=current_op,
90
+ status=OperationStatus.CANCELLED,
91
+ )
92
+ case _:
93
+ msg: str = "Invalid action for WAIT operation."
94
+
95
+ raise InvalidParameterValueException(msg)
@@ -0,0 +1,104 @@
1
+ """Operation transformer for converting OperationUpdates to Operations."""
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
+ OperationType,
10
+ OperationUpdate,
11
+ )
12
+
13
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.callback import (
14
+ CallbackProcessor,
15
+ )
16
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.context import (
17
+ ContextProcessor,
18
+ )
19
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.execution import (
20
+ ExecutionProcessor,
21
+ )
22
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.step import (
23
+ StepProcessor,
24
+ )
25
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.wait import (
26
+ WaitProcessor,
27
+ )
28
+ from aws_durable_execution_sdk_python_testing.exceptions import (
29
+ InvalidParameterValueException,
30
+ )
31
+
32
+
33
+ if TYPE_CHECKING:
34
+ from collections.abc import MutableMapping
35
+
36
+ from aws_durable_execution_sdk_python_testing.checkpoint.processors.base import (
37
+ OperationProcessor,
38
+ )
39
+
40
+ from typing import ClassVar
41
+
42
+
43
+ class OperationTransformer:
44
+ """Transforms OperationUpdates to Operations while maintaining order and triggering scheduler actions."""
45
+
46
+ _DEFAULT_PROCESSORS: ClassVar[dict[OperationType, OperationProcessor]] = {
47
+ OperationType.STEP: StepProcessor(),
48
+ OperationType.WAIT: WaitProcessor(),
49
+ OperationType.CONTEXT: ContextProcessor(),
50
+ OperationType.CALLBACK: CallbackProcessor(),
51
+ OperationType.EXECUTION: ExecutionProcessor(),
52
+ }
53
+
54
+ def __init__(
55
+ self,
56
+ processors: MutableMapping[OperationType, OperationProcessor] | None = None,
57
+ ):
58
+ self.processors = processors if processors else self._DEFAULT_PROCESSORS
59
+
60
+ def process_updates(
61
+ self,
62
+ updates: list[OperationUpdate],
63
+ current_operations: list[Operation],
64
+ notifier,
65
+ execution_arn: str,
66
+ ) -> tuple[list[Operation], list[OperationUpdate]]:
67
+ """Transform updates maintaining operation order and return (operations, updates)."""
68
+ op_map = {op.operation_id: op for op in current_operations}
69
+
70
+ # Start with copy of current operations list
71
+ result_operations = current_operations.copy()
72
+
73
+ for update in updates:
74
+ processor = self.processors.get(update.operation_type)
75
+ if processor:
76
+ current_op = op_map.get(update.operation_id)
77
+ updated_op = processor.process(
78
+ update=update,
79
+ current_op=current_op,
80
+ notifier=notifier,
81
+ execution_arn=execution_arn,
82
+ )
83
+
84
+ if updated_op is not None:
85
+ if update.operation_id in op_map:
86
+ # Update existing operation in-place
87
+ for i, op in enumerate(result_operations): # pragma: no branch
88
+ # no branch coverage because result_operation empty not reachable here
89
+ if op.operation_id == update.operation_id:
90
+ result_operations[i] = updated_op
91
+ break
92
+ else:
93
+ # Append new operation to end
94
+ result_operations.append(updated_op)
95
+
96
+ # Update map for future lookups
97
+ op_map[update.operation_id] = updated_op
98
+ else:
99
+ msg: str = (
100
+ f"Checkpoint for {update.operation_type} is not implemented yet."
101
+ )
102
+ raise InvalidParameterValueException(msg)
103
+
104
+ return result_operations, updates
@@ -0,0 +1 @@
1
+ """Checkpoint validation module."""
@@ -0,0 +1,242 @@
1
+ """Main checkpoint input validator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import TYPE_CHECKING
7
+
8
+ from aws_durable_execution_sdk_python.lambda_service import (
9
+ OperationAction,
10
+ OperationType,
11
+ OperationUpdate,
12
+ )
13
+
14
+ from aws_durable_execution_sdk_python_testing.checkpoint.validators.operations.callback import (
15
+ CallbackOperationValidator,
16
+ )
17
+ from aws_durable_execution_sdk_python_testing.checkpoint.validators.operations.context import (
18
+ ContextOperationValidator,
19
+ )
20
+ from aws_durable_execution_sdk_python_testing.checkpoint.validators.operations.execution import (
21
+ ExecutionOperationValidator,
22
+ )
23
+ from aws_durable_execution_sdk_python_testing.checkpoint.validators.operations.invoke import (
24
+ ChainedInvokeOperationValidator,
25
+ )
26
+ from aws_durable_execution_sdk_python_testing.checkpoint.validators.operations.step import (
27
+ StepOperationValidator,
28
+ )
29
+ from aws_durable_execution_sdk_python_testing.checkpoint.validators.operations.wait import (
30
+ WaitOperationValidator,
31
+ )
32
+ from aws_durable_execution_sdk_python_testing.checkpoint.validators.transitions import (
33
+ ValidActionsByOperationTypeValidator,
34
+ )
35
+ from aws_durable_execution_sdk_python_testing.exceptions import (
36
+ InvalidParameterValueException,
37
+ )
38
+
39
+
40
+ if TYPE_CHECKING:
41
+ from collections.abc import MutableMapping
42
+
43
+ from aws_durable_execution_sdk_python_testing.execution import Execution
44
+
45
+ MAX_ERROR_PAYLOAD_SIZE_BYTES = 32768
46
+
47
+
48
+ class CheckpointValidator:
49
+ """Validates checkpoint input based on current state."""
50
+
51
+ @staticmethod
52
+ def validate_input(updates: list[OperationUpdate], execution: Execution) -> None:
53
+ """Perform validation on the given input based on the current state."""
54
+ if not updates:
55
+ return
56
+
57
+ CheckpointValidator._validate_conflicting_execution_update(updates)
58
+ CheckpointValidator._validate_parent_id_and_duplicate_id(updates, execution)
59
+
60
+ for update in updates:
61
+ CheckpointValidator._validate_operation_update(update, execution)
62
+
63
+ @staticmethod
64
+ def _validate_conflicting_execution_update(updates: list[OperationUpdate]) -> None:
65
+ """Validate that there are no conflicting execution updates."""
66
+ execution_updates = [
67
+ update
68
+ for update in updates
69
+ if update.operation_type == OperationType.EXECUTION
70
+ ]
71
+
72
+ if len(execution_updates) > 1:
73
+ msg_multiple_exec: str = "Cannot checkpoint multiple EXECUTION updates."
74
+
75
+ raise InvalidParameterValueException(msg_multiple_exec)
76
+
77
+ if execution_updates and updates[-1].operation_type != OperationType.EXECUTION:
78
+ msg_exec_last: str = "EXECUTION checkpoint must be the last update."
79
+
80
+ raise InvalidParameterValueException(msg_exec_last)
81
+
82
+ @staticmethod
83
+ def _validate_operation_update(
84
+ update: OperationUpdate, execution: Execution
85
+ ) -> None:
86
+ """Validate a single operation update."""
87
+ CheckpointValidator._validate_inconsistent_operation_metadata(update, execution)
88
+ CheckpointValidator._validate_payload_sizes(update)
89
+ ValidActionsByOperationTypeValidator.validate(
90
+ update.operation_type, update.action
91
+ )
92
+ CheckpointValidator._validate_operation_status_transition(update, execution)
93
+
94
+ @staticmethod
95
+ def _validate_payload_sizes(update: OperationUpdate) -> None:
96
+ """Validate that operation payload sizes are not too large."""
97
+ if update.error is not None:
98
+ payload = json.dumps(update.error.to_dict())
99
+ if len(payload) > MAX_ERROR_PAYLOAD_SIZE_BYTES:
100
+ msg: str = f"Error object size must be less than {MAX_ERROR_PAYLOAD_SIZE_BYTES} bytes."
101
+ raise InvalidParameterValueException(msg)
102
+
103
+ @staticmethod
104
+ def _validate_operation_status_transition(
105
+ update: OperationUpdate, execution: Execution
106
+ ) -> None:
107
+ """Validate that the operation status transition is valid."""
108
+ current_state = None
109
+ for operation in execution.operations:
110
+ if operation.operation_id == update.operation_id:
111
+ current_state = operation
112
+ break
113
+
114
+ match update.operation_type:
115
+ case OperationType.STEP:
116
+ StepOperationValidator.validate(current_state, update)
117
+ case OperationType.CONTEXT:
118
+ ContextOperationValidator.validate(current_state, update)
119
+ case OperationType.WAIT:
120
+ WaitOperationValidator.validate(current_state, update)
121
+ case OperationType.CALLBACK:
122
+ CallbackOperationValidator.validate(current_state, update)
123
+ case OperationType.CHAINED_INVOKE:
124
+ ChainedInvokeOperationValidator.validate(current_state, update)
125
+ case OperationType.EXECUTION:
126
+ ExecutionOperationValidator.validate(update)
127
+ case _: # pragma: no cover
128
+ msg: str = "Invalid operation type."
129
+
130
+ raise InvalidParameterValueException(msg)
131
+
132
+ @staticmethod
133
+ def _validate_inconsistent_operation_metadata(
134
+ update: OperationUpdate, execution: Execution
135
+ ) -> None:
136
+ """Validate that operation metadata is consistent with existing operation."""
137
+ current_state = None
138
+ for operation in execution.operations:
139
+ if operation.operation_id == update.operation_id:
140
+ current_state = operation
141
+ break
142
+
143
+ if current_state is not None:
144
+ if (
145
+ update.operation_type is not None
146
+ and update.operation_type != current_state.operation_type
147
+ ):
148
+ msg: str = "Inconsistent operation type."
149
+ raise InvalidParameterValueException(msg)
150
+
151
+ if (
152
+ update.sub_type is not None
153
+ and update.sub_type != current_state.sub_type
154
+ ):
155
+ msg_subtype: str = "Inconsistent operation subtype."
156
+ raise InvalidParameterValueException(msg_subtype)
157
+
158
+ if update.name is not None and update.name != current_state.name:
159
+ msg_name: str = "Inconsistent operation name."
160
+ raise InvalidParameterValueException(msg_name)
161
+
162
+ if (
163
+ update.parent_id is not None
164
+ and update.parent_id != current_state.parent_id
165
+ ):
166
+ msg_parent: str = "Inconsistent parent operation id."
167
+ raise InvalidParameterValueException(msg_parent)
168
+
169
+ @staticmethod
170
+ def _validate_parent_id_and_duplicate_id(
171
+ updates: list[OperationUpdate], execution: Execution
172
+ ) -> None:
173
+ """Validate parent IDs and check for duplicate operation IDs.
174
+
175
+ Validate that any provided parentId is valid, and also validate no duplicate operation is being
176
+ updated at the same time (unless it is a STEP/CONTEXT starting + performing one more non-START action).
177
+ """
178
+ operations_started: MutableMapping[str, OperationUpdate] = {}
179
+ last_updates_seen: MutableMapping[str, OperationUpdate] = {}
180
+
181
+ for update in updates:
182
+ if CheckpointValidator._is_invalid_duplicate_update(
183
+ update, last_updates_seen
184
+ ):
185
+ msg_duplicate: str = (
186
+ "Cannot checkpoint multiple operations with the same ID."
187
+ )
188
+ raise InvalidParameterValueException(msg_duplicate)
189
+
190
+ if not CheckpointValidator._is_valid_parent_for_update(
191
+ execution, update, operations_started
192
+ ):
193
+ msg_parent: str = "Invalid parent operation id."
194
+ raise InvalidParameterValueException(msg_parent)
195
+
196
+ if update.action == OperationAction.START:
197
+ operations_started[update.operation_id] = update
198
+
199
+ last_updates_seen[update.operation_id] = update
200
+
201
+ @staticmethod
202
+ def _is_invalid_duplicate_update(
203
+ update: OperationUpdate, last_updates_seen: MutableMapping[str, OperationUpdate]
204
+ ) -> bool:
205
+ """Check if this is an invalid duplicate update."""
206
+ last_update = last_updates_seen.get(update.operation_id)
207
+ if last_update is None:
208
+ return False
209
+
210
+ if last_update.operation_type in (OperationType.STEP, OperationType.CONTEXT):
211
+ # Allow duplicate for STEP/CONTEXT if last was START and current is not START
212
+ allow_duplicate = (
213
+ last_update.action == OperationAction.START
214
+ and update.action != OperationAction.START
215
+ )
216
+ return not allow_duplicate
217
+
218
+ return True
219
+
220
+ @staticmethod
221
+ def _is_valid_parent_for_update(
222
+ execution: Execution,
223
+ update: OperationUpdate,
224
+ operations_started: MutableMapping[str, OperationUpdate],
225
+ ) -> bool:
226
+ """Check if the parent ID is valid for the update."""
227
+ parent_id = update.parent_id
228
+
229
+ if parent_id is None:
230
+ return True
231
+
232
+ # Check if parent is in operations started in this batch
233
+ if parent_id in operations_started:
234
+ parent_update = operations_started[parent_id]
235
+ return parent_update.operation_type == OperationType.CONTEXT
236
+
237
+ # Check if parent exists in current execution state
238
+ for operation in execution.operations:
239
+ if operation.operation_id == parent_id:
240
+ return operation.operation_type == OperationType.CONTEXT
241
+
242
+ return False
@@ -0,0 +1 @@
1
+ """Operation-specific validators."""
@@ -0,0 +1,45 @@
1
+ """Callback operation validator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from aws_durable_execution_sdk_python.lambda_service import (
6
+ Operation,
7
+ OperationAction,
8
+ OperationStatus,
9
+ OperationUpdate,
10
+ )
11
+
12
+ from aws_durable_execution_sdk_python_testing.exceptions import (
13
+ InvalidParameterValueException,
14
+ )
15
+
16
+
17
+ VALID_ACTIONS_FOR_CALLBACK = frozenset(
18
+ [
19
+ OperationAction.START,
20
+ ]
21
+ )
22
+
23
+
24
+ class CallbackOperationValidator:
25
+ """Validates CALLBACK operation transitions."""
26
+
27
+ _ALLOWED_STATUS_TO_CANCEL = frozenset(
28
+ [
29
+ OperationStatus.STARTED,
30
+ ]
31
+ )
32
+
33
+ @staticmethod
34
+ def validate(current_state: Operation | None, update: OperationUpdate) -> None:
35
+ """Validate CALLBACK operation update."""
36
+ match update.action:
37
+ case OperationAction.START:
38
+ if current_state is not None:
39
+ msg_callback_exists: str = (
40
+ "Cannot start a CALLBACK that already exist."
41
+ )
42
+ raise InvalidParameterValueException(msg_callback_exists)
43
+ case _:
44
+ msg: str = "Invalid action for CALLBACK operation."
45
+ raise InvalidParameterValueException(msg)
@@ -0,0 +1,73 @@
1
+ """Context operation validator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from aws_durable_execution_sdk_python.lambda_service import (
6
+ Operation,
7
+ OperationAction,
8
+ OperationStatus,
9
+ OperationUpdate,
10
+ )
11
+
12
+ from aws_durable_execution_sdk_python_testing.exceptions import (
13
+ InvalidParameterValueException,
14
+ )
15
+
16
+
17
+ VALID_ACTIONS_FOR_CONTEXT = frozenset(
18
+ [
19
+ OperationAction.START,
20
+ OperationAction.FAIL,
21
+ OperationAction.SUCCEED,
22
+ ]
23
+ )
24
+
25
+
26
+ class ContextOperationValidator:
27
+ """Validates CONTEXT operation transitions."""
28
+
29
+ _ALLOWED_STATUS_TO_CLOSE = frozenset(
30
+ [
31
+ OperationStatus.STARTED,
32
+ ]
33
+ )
34
+
35
+ @staticmethod
36
+ def validate(current_state: Operation | None, update: OperationUpdate) -> None:
37
+ """Validate CONTEXT operation update."""
38
+ match update.action:
39
+ case OperationAction.START:
40
+ if current_state is not None:
41
+ msg_context_exists: str = (
42
+ "Cannot start a CONTEXT that already exist."
43
+ )
44
+
45
+ raise InvalidParameterValueException(msg_context_exists)
46
+ case OperationAction.FAIL | OperationAction.SUCCEED:
47
+ if (
48
+ current_state is not None
49
+ and current_state.status
50
+ not in ContextOperationValidator._ALLOWED_STATUS_TO_CLOSE
51
+ ):
52
+ msg_context_close: str = "Invalid current CONTEXT state to close."
53
+
54
+ raise InvalidParameterValueException(msg_context_close)
55
+ if update.action == OperationAction.FAIL and update.payload is not None:
56
+ msg_context_fail_payload: str = (
57
+ "Cannot provide a Payload for FAIL action."
58
+ )
59
+
60
+ raise InvalidParameterValueException(msg_context_fail_payload)
61
+ if (
62
+ update.action == OperationAction.SUCCEED
63
+ and update.error is not None
64
+ ):
65
+ msg_context_succeed_error: str = (
66
+ "Cannot provide an Error for SUCCEED action."
67
+ )
68
+
69
+ raise InvalidParameterValueException(msg_context_succeed_error)
70
+ case _:
71
+ msg_context_invalid: str = "Invalid CONTEXT action."
72
+
73
+ raise InvalidParameterValueException(msg_context_invalid)
@@ -0,0 +1,47 @@
1
+ """Execution operation validator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from aws_durable_execution_sdk_python.lambda_service import (
6
+ OperationAction,
7
+ OperationUpdate,
8
+ )
9
+
10
+ from aws_durable_execution_sdk_python_testing.exceptions import (
11
+ InvalidParameterValueException,
12
+ )
13
+
14
+
15
+ VALID_ACTIONS_FOR_EXECUTION = frozenset(
16
+ [
17
+ OperationAction.SUCCEED,
18
+ OperationAction.FAIL,
19
+ ]
20
+ )
21
+
22
+
23
+ class ExecutionOperationValidator:
24
+ """Validates EXECUTION operation transitions."""
25
+
26
+ @staticmethod
27
+ def validate(update: OperationUpdate) -> None:
28
+ """Validate EXECUTION operation update."""
29
+ match update.action:
30
+ case OperationAction.SUCCEED:
31
+ if update.error is not None:
32
+ msg_exec_succeed_error: str = (
33
+ "Cannot provide an Error for SUCCEED action."
34
+ )
35
+
36
+ raise InvalidParameterValueException(msg_exec_succeed_error)
37
+ case OperationAction.FAIL:
38
+ if update.payload is not None:
39
+ msg_exec_fail_payload: str = (
40
+ "Cannot provide a Payload for FAIL action."
41
+ )
42
+
43
+ raise InvalidParameterValueException(msg_exec_fail_payload)
44
+ case _:
45
+ msg_exec_invalid: str = "Invalid EXECUTION action."
46
+
47
+ raise InvalidParameterValueException(msg_exec_invalid)
@@ -0,0 +1,56 @@
1
+ """Invoke operation validator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from aws_durable_execution_sdk_python.lambda_service import (
6
+ Operation,
7
+ OperationAction,
8
+ OperationStatus,
9
+ OperationUpdate,
10
+ )
11
+
12
+ from aws_durable_execution_sdk_python_testing.exceptions import (
13
+ InvalidParameterValueException,
14
+ )
15
+
16
+
17
+ VALID_ACTIONS_FOR_INVOKE = frozenset(
18
+ [
19
+ OperationAction.START,
20
+ OperationAction.CANCEL,
21
+ ]
22
+ )
23
+
24
+
25
+ class ChainedInvokeOperationValidator:
26
+ """Validates INVOKE operation transitions."""
27
+
28
+ _ALLOWED_STATUS_TO_CANCEL = frozenset(
29
+ [
30
+ OperationStatus.STARTED,
31
+ ]
32
+ )
33
+
34
+ @staticmethod
35
+ def validate(current_state: Operation | None, update: OperationUpdate) -> None:
36
+ """Validate INVOKE operation update."""
37
+ match update.action:
38
+ case OperationAction.START:
39
+ if current_state is not None:
40
+ msg_invoke_exists: str = (
41
+ "Cannot start an INVOKE that already exist."
42
+ )
43
+
44
+ raise InvalidParameterValueException(msg_invoke_exists)
45
+ case OperationAction.CANCEL:
46
+ if (
47
+ current_state is None
48
+ or current_state.status
49
+ not in ChainedInvokeOperationValidator._ALLOWED_STATUS_TO_CANCEL
50
+ ):
51
+ msg_invoke_cancel: str = "Cannot cancel an INVOKE that does not exist or has already completed."
52
+ raise InvalidParameterValueException(msg_invoke_cancel)
53
+ case _:
54
+ msg_invoke_invalid: str = "Invalid INVOKE action."
55
+
56
+ raise InvalidParameterValueException(msg_invoke_invalid)