localstack-core 4.10.1.dev7__py3-none-any.whl → 4.11.2.dev14__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.
- localstack/aws/api/acm/__init__.py +122 -122
- localstack/aws/api/apigateway/__init__.py +604 -561
- localstack/aws/api/cloudcontrol/__init__.py +63 -63
- localstack/aws/api/cloudformation/__init__.py +1201 -969
- localstack/aws/api/cloudwatch/__init__.py +375 -375
- localstack/aws/api/config/__init__.py +784 -786
- localstack/aws/api/dynamodb/__init__.py +753 -759
- localstack/aws/api/dynamodbstreams/__init__.py +74 -74
- localstack/aws/api/ec2/__init__.py +10062 -8826
- localstack/aws/api/es/__init__.py +453 -453
- localstack/aws/api/events/__init__.py +552 -552
- localstack/aws/api/firehose/__init__.py +541 -543
- localstack/aws/api/iam/__init__.py +866 -572
- localstack/aws/api/kinesis/__init__.py +235 -147
- localstack/aws/api/kms/__init__.py +341 -336
- localstack/aws/api/lambda_/__init__.py +974 -621
- localstack/aws/api/logs/__init__.py +988 -675
- localstack/aws/api/opensearch/__init__.py +903 -785
- localstack/aws/api/pipes/__init__.py +336 -336
- localstack/aws/api/redshift/__init__.py +1257 -1166
- localstack/aws/api/resource_groups/__init__.py +175 -175
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +103 -67
- localstack/aws/api/route53/__init__.py +296 -254
- localstack/aws/api/route53resolver/__init__.py +397 -396
- localstack/aws/api/s3/__init__.py +1412 -1349
- localstack/aws/api/s3control/__init__.py +594 -594
- localstack/aws/api/scheduler/__init__.py +118 -118
- localstack/aws/api/secretsmanager/__init__.py +221 -216
- localstack/aws/api/ses/__init__.py +227 -227
- localstack/aws/api/sns/__init__.py +115 -115
- localstack/aws/api/sqs/__init__.py +100 -100
- localstack/aws/api/ssm/__init__.py +1977 -1971
- localstack/aws/api/stepfunctions/__init__.py +375 -333
- localstack/aws/api/sts/__init__.py +142 -66
- localstack/aws/api/support/__init__.py +112 -112
- localstack/aws/api/swf/__init__.py +378 -386
- localstack/aws/api/transcribe/__init__.py +425 -425
- localstack/aws/handlers/logging.py +8 -4
- localstack/aws/handlers/service.py +22 -3
- localstack/aws/protocol/parser.py +1 -1
- localstack/aws/protocol/serializer.py +1 -1
- localstack/aws/scaffold.py +15 -17
- localstack/cli/localstack.py +6 -1
- localstack/deprecations.py +0 -6
- localstack/dev/kubernetes/__main__.py +38 -3
- localstack/services/acm/provider.py +4 -0
- localstack/services/apigateway/helpers.py +5 -9
- localstack/services/apigateway/legacy/provider.py +60 -24
- localstack/services/apigateway/patches.py +0 -9
- localstack/services/cloudformation/engine/template_preparer.py +6 -2
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +12 -0
- localstack/services/cloudformation/provider.py +2 -2
- localstack/services/cloudformation/v2/provider.py +6 -6
- localstack/services/cloudwatch/provider.py +10 -3
- localstack/services/cloudwatch/provider_v2.py +6 -3
- localstack/services/configservice/provider.py +5 -1
- localstack/services/dynamodb/provider.py +1 -0
- localstack/services/dynamodb/v2/provider.py +1 -0
- localstack/services/dynamodbstreams/provider.py +6 -0
- localstack/services/dynamodbstreams/v2/provider.py +6 -0
- localstack/services/ec2/provider.py +6 -0
- localstack/services/es/provider.py +6 -0
- localstack/services/events/provider.py +4 -0
- localstack/services/events/v1/provider.py +9 -0
- localstack/services/firehose/provider.py +5 -0
- localstack/services/iam/provider.py +4 -0
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +44 -24
- localstack/services/kms/provider.py +97 -16
- localstack/services/lambda_/api_utils.py +40 -21
- localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
- localstack/services/lambda_/invocation/assignment.py +4 -1
- localstack/services/lambda_/invocation/execution_environment.py +21 -2
- localstack/services/lambda_/invocation/lambda_models.py +27 -2
- localstack/services/lambda_/invocation/lambda_service.py +51 -3
- localstack/services/lambda_/invocation/models.py +9 -1
- localstack/services/lambda_/invocation/version_manager.py +18 -3
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +240 -96
- localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
- localstack/services/lambda_/runtimes.py +10 -3
- localstack/services/logs/provider.py +45 -19
- localstack/services/opensearch/provider.py +53 -3
- localstack/services/resource_groups/provider.py +5 -1
- localstack/services/resourcegroupstaggingapi/provider.py +6 -1
- localstack/services/s3/provider.py +29 -16
- localstack/services/s3/utils.py +35 -14
- localstack/services/s3control/provider.py +101 -2
- localstack/services/s3control/validation.py +50 -0
- localstack/services/sns/constants.py +3 -1
- localstack/services/sns/publisher.py +15 -6
- localstack/services/sns/v2/models.py +30 -1
- localstack/services/sns/v2/provider.py +794 -31
- localstack/services/sns/v2/utils.py +20 -0
- localstack/services/sqs/models.py +37 -10
- localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
- localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
- localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
- localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
- localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
- localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
- localstack/services/stepfunctions/asl/eval/environment.py +30 -22
- localstack/services/stepfunctions/asl/eval/states.py +1 -1
- localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
- localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
- localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
- localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
- localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
- localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +170 -22
- localstack/services/stepfunctions/backend/execution.py +6 -6
- localstack/services/stepfunctions/backend/execution_worker.py +5 -5
- localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
- localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
- localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
- localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
- localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
- localstack/services/stepfunctions/provider.py +78 -27
- localstack/services/stepfunctions/test_state/mock_config.py +47 -0
- localstack/testing/pytest/fixtures.py +28 -0
- localstack/testing/snapshots/transformer_utility.py +7 -0
- localstack/testing/testselection/matching.py +0 -1
- localstack/utils/analytics/publisher.py +37 -155
- localstack/utils/analytics/service_request_aggregator.py +6 -4
- localstack/utils/aws/arns.py +7 -0
- localstack/utils/aws/client_types.py +0 -8
- localstack/utils/batching.py +258 -0
- localstack/utils/catalog/catalog_loader.py +111 -3
- localstack/utils/collections.py +23 -11
- localstack/utils/crypto.py +109 -0
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/METADATA +7 -6
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/RECORD +149 -141
- localstack_core-4.11.2.dev14.dist-info/plux.json +1 -0
- localstack/services/stepfunctions/mocking/__init__.py +0 -0
- localstack/utils/batch_policy.py +0 -124
- localstack_core-4.10.1.dev7.dist-info/plux.json +0 -1
- /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,7 @@ from botocore.utils import InvalidArnException
|
|
|
6
6
|
|
|
7
7
|
from localstack.aws.api.sns import InvalidParameterException
|
|
8
8
|
from localstack.services.sns.constants import E164_REGEX, VALID_SUBSCRIPTION_ATTR_NAME
|
|
9
|
+
from localstack.services.sns.v2.models import SnsStore, SnsSubscription
|
|
9
10
|
from localstack.utils.aws.arns import ArnData, parse_arn
|
|
10
11
|
from localstack.utils.strings import short_uid, to_bytes, to_str
|
|
11
12
|
|
|
@@ -103,6 +104,14 @@ def create_subscription_arn(topic_arn: str) -> str:
|
|
|
103
104
|
return f"{topic_arn}:{uuid4()}"
|
|
104
105
|
|
|
105
106
|
|
|
107
|
+
def create_platform_endpoint_arn(
|
|
108
|
+
platform_application_arn: str,
|
|
109
|
+
) -> str:
|
|
110
|
+
# This is the format of an Endpoint Arn
|
|
111
|
+
# arn:aws:sns:us-west-2:1234567890:endpoint/GCM/MyApplication/12345678-abcd-9012-efgh-345678901234
|
|
112
|
+
return f"{platform_application_arn.replace('app', 'endpoint', 1)}/{uuid4()}"
|
|
113
|
+
|
|
114
|
+
|
|
106
115
|
def encode_subscription_token_with_region(region: str) -> str:
|
|
107
116
|
"""
|
|
108
117
|
Create a 64 characters Subscription Token with the region encoded
|
|
@@ -128,3 +137,14 @@ def get_region_from_subscription_token(token: str) -> str:
|
|
|
128
137
|
return bytes.fromhex(region).decode("utf-8")
|
|
129
138
|
except (IndexError, ValueError, TypeError, UnicodeDecodeError):
|
|
130
139
|
raise InvalidParameterException("Invalid parameter: Token")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_topic_subscriptions(store: SnsStore, topic_arn: str) -> list[SnsSubscription]:
|
|
143
|
+
# TODO: delete this once the legacy v1 implementation has been removed
|
|
144
|
+
if hasattr(store, "topic_subscriptions"):
|
|
145
|
+
sub_arns = store.topic_subscriptions.get(topic_arn, [])
|
|
146
|
+
else:
|
|
147
|
+
sub_arns: list[str] = store.topics[topic_arn].get("subscriptions", [])
|
|
148
|
+
|
|
149
|
+
subscriptions = [store.subscriptions[k] for k in sub_arns if k in store.subscriptions]
|
|
150
|
+
return subscriptions
|
|
@@ -314,7 +314,8 @@ class SqsQueue:
|
|
|
314
314
|
purge_timestamp: float | None
|
|
315
315
|
|
|
316
316
|
delayed: set[SqsMessage]
|
|
317
|
-
|
|
317
|
+
# Simulating an ordered set in python. Only the keys are used and of interest.
|
|
318
|
+
inflight: dict[SqsMessage, None]
|
|
318
319
|
receipts: dict[str, SqsMessage]
|
|
319
320
|
|
|
320
321
|
def __init__(self, name: str, region: str, account_id: str, attributes=None, tags=None) -> None:
|
|
@@ -326,7 +327,7 @@ class SqsQueue:
|
|
|
326
327
|
self.tags = tags or {}
|
|
327
328
|
|
|
328
329
|
self.delayed = set()
|
|
329
|
-
self.inflight =
|
|
330
|
+
self.inflight = {}
|
|
330
331
|
self.receipts = {}
|
|
331
332
|
|
|
332
333
|
self.attributes = self.default_attributes()
|
|
@@ -513,7 +514,7 @@ class SqsQueue:
|
|
|
513
514
|
)
|
|
514
515
|
# Terminating the visibility timeout for a message
|
|
515
516
|
# https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html#terminating-message-visibility-timeout
|
|
516
|
-
self.inflight
|
|
517
|
+
del self.inflight[standard_message]
|
|
517
518
|
self._put_message(standard_message)
|
|
518
519
|
|
|
519
520
|
def remove(self, receipt_handle: str):
|
|
@@ -606,9 +607,17 @@ class SqsQueue:
|
|
|
606
607
|
standard_message,
|
|
607
608
|
self.arn,
|
|
608
609
|
)
|
|
609
|
-
self.inflight
|
|
610
|
+
del self.inflight[standard_message]
|
|
610
611
|
self._put_message(standard_message)
|
|
611
612
|
|
|
613
|
+
def add_inflight_message(self, message: SqsMessage):
|
|
614
|
+
"""
|
|
615
|
+
We are simulating an ordered set with a dict. When a value is added, it is added as key to the dict, which
|
|
616
|
+
is all we need. Hence all "values" in this ordered set are None
|
|
617
|
+
:param message: The message to put in flight
|
|
618
|
+
"""
|
|
619
|
+
self.inflight[message] = None
|
|
620
|
+
|
|
612
621
|
def enqueue_delayed_messages(self):
|
|
613
622
|
if not self.delayed:
|
|
614
623
|
return
|
|
@@ -779,7 +788,6 @@ class SqsQueue:
|
|
|
779
788
|
|
|
780
789
|
class StandardQueue(SqsQueue):
|
|
781
790
|
visible: InterruptiblePriorityQueue[SqsMessage]
|
|
782
|
-
inflight: set[SqsMessage]
|
|
783
791
|
|
|
784
792
|
def __init__(self, name: str, region: str, account_id: str, attributes=None, tags=None) -> None:
|
|
785
793
|
super().__init__(name, region, account_id, attributes, tags)
|
|
@@ -923,13 +931,13 @@ class StandardQueue(SqsQueue):
|
|
|
923
931
|
if message.visibility_timeout == 0:
|
|
924
932
|
self.visible.put_nowait(message)
|
|
925
933
|
else:
|
|
926
|
-
self.
|
|
934
|
+
self.add_inflight_message(message)
|
|
927
935
|
|
|
928
936
|
return result
|
|
929
937
|
|
|
930
938
|
def _on_remove_message(self, message: SqsMessage):
|
|
931
939
|
try:
|
|
932
|
-
self.inflight
|
|
940
|
+
del self.inflight[message]
|
|
933
941
|
except KeyError:
|
|
934
942
|
# this likely means the message was removed with an expired receipt handle unfortunately this
|
|
935
943
|
# means we need to scan the queue for the element and remove it from there, and then re-heapify
|
|
@@ -1149,6 +1157,26 @@ class FifoQueue(SqsQueue):
|
|
|
1149
1157
|
elif previously_empty:
|
|
1150
1158
|
self.message_group_queue.put_nowait(message_group)
|
|
1151
1159
|
|
|
1160
|
+
def requeue_inflight_messages(self):
|
|
1161
|
+
if not self.inflight:
|
|
1162
|
+
return
|
|
1163
|
+
|
|
1164
|
+
with self.mutex:
|
|
1165
|
+
messages = list(self.inflight)
|
|
1166
|
+
for standard_message in messages:
|
|
1167
|
+
# in fifo, an invisible message blocks potentially visible messages afterwards
|
|
1168
|
+
# this can happen for example if multiple message of the same group are received at once, then one
|
|
1169
|
+
# message of this batch has its visibility timeout extended
|
|
1170
|
+
if not standard_message.is_visible:
|
|
1171
|
+
return
|
|
1172
|
+
LOG.debug(
|
|
1173
|
+
"re-queueing inflight messages %s into queue %s",
|
|
1174
|
+
standard_message,
|
|
1175
|
+
self.arn,
|
|
1176
|
+
)
|
|
1177
|
+
del self.inflight[standard_message]
|
|
1178
|
+
self._put_message(standard_message)
|
|
1179
|
+
|
|
1152
1180
|
def remove_expired_messages(self):
|
|
1153
1181
|
with self.mutex:
|
|
1154
1182
|
retention_period = self.message_retention_period
|
|
@@ -1278,8 +1306,7 @@ class FifoQueue(SqsQueue):
|
|
|
1278
1306
|
if message.visibility_timeout == 0:
|
|
1279
1307
|
self._put_message(message)
|
|
1280
1308
|
else:
|
|
1281
|
-
self.
|
|
1282
|
-
|
|
1309
|
+
self.add_inflight_message(message)
|
|
1283
1310
|
return result
|
|
1284
1311
|
|
|
1285
1312
|
def _on_remove_message(self, message: SqsMessage):
|
|
@@ -1288,7 +1315,7 @@ class FifoQueue(SqsQueue):
|
|
|
1288
1315
|
|
|
1289
1316
|
with self.mutex:
|
|
1290
1317
|
try:
|
|
1291
|
-
self.inflight
|
|
1318
|
+
del self.inflight[message]
|
|
1292
1319
|
except KeyError:
|
|
1293
1320
|
# in FIFO queues, this should not happen, as expired receipt handles cannot be used to
|
|
1294
1321
|
# delete a message.
|
|
@@ -18,7 +18,7 @@ class ResultPath(EvalComponent):
|
|
|
18
18
|
def _eval_body(self, env: Environment) -> None:
|
|
19
19
|
state_input = env.states.get_input()
|
|
20
20
|
|
|
21
|
-
# Discard task output if there is one, and set the output
|
|
21
|
+
# Discard task output if there is one, and set the output to be the state's input.
|
|
22
22
|
if self.result_path_src is None:
|
|
23
23
|
env.stack.clear()
|
|
24
24
|
env.stack.append(state_input)
|
|
@@ -251,7 +251,6 @@ class ExecutionState(CommonStateField, abc.ABC):
|
|
|
251
251
|
)
|
|
252
252
|
error_output = self._construct_error_output_value(failure_event=failure_event)
|
|
253
253
|
env.states.set_error_output(error_output)
|
|
254
|
-
env.states.set_result(error_output)
|
|
255
254
|
|
|
256
255
|
if self.retry:
|
|
257
256
|
retry_outcome: RetryOutcome = self._handle_retry(
|
localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py
CHANGED
|
@@ -311,7 +311,6 @@ class StateMap(ExecutionState):
|
|
|
311
311
|
failure_event: FailureEvent = self._from_error(env=env, ex=ex)
|
|
312
312
|
error_output = self._construct_error_output_value(failure_event=failure_event)
|
|
313
313
|
env.states.set_error_output(error_output)
|
|
314
|
-
env.states.set_result(error_output)
|
|
315
314
|
|
|
316
315
|
if self.retry:
|
|
317
316
|
retry_outcome: RetryOutcome = self._handle_retry(
|
|
@@ -6,13 +6,13 @@ from localstack.aws.api.lambda_ import InvocationResponse
|
|
|
6
6
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import (
|
|
7
7
|
StateCredentials,
|
|
8
8
|
)
|
|
9
|
-
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.
|
|
10
|
-
|
|
9
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.local_mock_eval_utils import (
|
|
10
|
+
eval_local_mocked_response,
|
|
11
11
|
)
|
|
12
12
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
13
13
|
from localstack.services.stepfunctions.asl.utils.boto_client import boto_client_for
|
|
14
14
|
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
15
|
-
from localstack.services.stepfunctions.
|
|
15
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockedResponse
|
|
16
16
|
from localstack.utils.collections import select_from_typed_dict
|
|
17
17
|
from localstack.utils.strings import to_bytes
|
|
18
18
|
|
|
@@ -42,9 +42,9 @@ def _from_payload(payload_streaming_body: IO[bytes]) -> Any | str:
|
|
|
42
42
|
return decoded_data
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def
|
|
46
|
-
mocked_response:
|
|
47
|
-
|
|
45
|
+
def _local_mocked_invoke_lambda_function(env: Environment) -> InvocationResponse:
|
|
46
|
+
mocked_response: LocalMockedResponse = env.get_current_local_mocked_response()
|
|
47
|
+
eval_local_mocked_response(env=env, mocked_response=mocked_response)
|
|
48
48
|
invocation_resp: InvocationResponse = env.stack.pop()
|
|
49
49
|
return invocation_resp
|
|
50
50
|
|
|
@@ -68,8 +68,8 @@ def _invoke_lambda_function(
|
|
|
68
68
|
def execute_lambda_function_integration(
|
|
69
69
|
env: Environment, parameters: dict, region: str, state_credentials: StateCredentials
|
|
70
70
|
) -> None:
|
|
71
|
-
if env.
|
|
72
|
-
invocation_response: InvocationResponse =
|
|
71
|
+
if env.is_local_mocked_mode():
|
|
72
|
+
invocation_response: InvocationResponse = _local_mocked_invoke_lambda_function(env=env)
|
|
73
73
|
else:
|
|
74
74
|
invocation_response: InvocationResponse = _invoke_lambda_function(
|
|
75
75
|
parameters=parameters, region=region, state_credentials=state_credentials
|
|
@@ -10,14 +10,16 @@ from localstack.services.stepfunctions.asl.component.common.error_name.failure_e
|
|
|
10
10
|
)
|
|
11
11
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
12
12
|
from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails
|
|
13
|
-
from localstack.services.stepfunctions.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import (
|
|
14
|
+
LocalMockedResponse,
|
|
15
|
+
LocalMockedResponseReturn,
|
|
16
|
+
LocalMockedResponseThrow,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def _eval_mocked_response_throw(
|
|
20
|
+
def _eval_mocked_response_throw(
|
|
21
|
+
env: Environment, mocked_response: LocalMockedResponseThrow
|
|
22
|
+
) -> None:
|
|
21
23
|
task_failed_event_details = TaskFailedEventDetails(
|
|
22
24
|
error=mocked_response.error, cause=mocked_response.cause
|
|
23
25
|
)
|
|
@@ -31,15 +33,17 @@ def _eval_mocked_response_throw(env: Environment, mocked_response: MockedRespons
|
|
|
31
33
|
raise FailureEventException(failure_event=failure_event)
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
def _eval_mocked_response_return(
|
|
36
|
+
def _eval_mocked_response_return(
|
|
37
|
+
env: Environment, mocked_response: LocalMockedResponseReturn
|
|
38
|
+
) -> None:
|
|
35
39
|
payload_copy = copy.deepcopy(mocked_response.payload)
|
|
36
40
|
env.stack.append(payload_copy)
|
|
37
41
|
|
|
38
42
|
|
|
39
|
-
def
|
|
40
|
-
if isinstance(mocked_response,
|
|
43
|
+
def eval_local_mocked_response(env: Environment, mocked_response: LocalMockedResponse) -> None:
|
|
44
|
+
if isinstance(mocked_response, LocalMockedResponseReturn):
|
|
41
45
|
_eval_mocked_response_return(env=env, mocked_response=mocked_response)
|
|
42
|
-
elif isinstance(mocked_response,
|
|
46
|
+
elif isinstance(mocked_response, LocalMockedResponseThrow):
|
|
43
47
|
_eval_mocked_response_throw(env=env, mocked_response=mocked_response)
|
|
44
48
|
else:
|
|
45
49
|
raise RuntimeError(f"Invalid MockedResponse type '{type(mocked_response)}'")
|
|
@@ -33,8 +33,8 @@ from localstack.services.stepfunctions.asl.component.common.error_name.states_er
|
|
|
33
33
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import (
|
|
34
34
|
StateCredentials,
|
|
35
35
|
)
|
|
36
|
-
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.
|
|
37
|
-
|
|
36
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.local_mock_eval_utils import (
|
|
37
|
+
eval_local_mocked_response,
|
|
38
38
|
)
|
|
39
39
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.resource import (
|
|
40
40
|
ResourceRuntimePart,
|
|
@@ -47,7 +47,7 @@ from localstack.services.stepfunctions.asl.component.state.state_props import St
|
|
|
47
47
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
48
48
|
from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails
|
|
49
49
|
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
50
|
-
from localstack.services.stepfunctions.
|
|
50
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockedResponse
|
|
51
51
|
from localstack.services.stepfunctions.quotas import is_within_size_quota
|
|
52
52
|
from localstack.utils.strings import camel_to_snake_case, snake_to_camel_case, to_bytes, to_str
|
|
53
53
|
|
|
@@ -356,9 +356,9 @@ class StateTaskService(StateTask, abc.ABC):
|
|
|
356
356
|
normalised_parameters = copy.deepcopy(raw_parameters)
|
|
357
357
|
self._normalise_parameters(normalised_parameters)
|
|
358
358
|
|
|
359
|
-
if env.
|
|
360
|
-
mocked_response:
|
|
361
|
-
|
|
359
|
+
if env.is_local_mocked_mode():
|
|
360
|
+
mocked_response: LocalMockedResponse = env.get_current_local_mocked_response()
|
|
361
|
+
eval_local_mocked_response(env=env, mocked_response=mocked_response)
|
|
362
362
|
else:
|
|
363
363
|
self._eval_service_task(
|
|
364
364
|
env=env,
|
|
@@ -346,7 +346,7 @@ class StateTaskServiceCallback(StateTaskService, abc.ABC):
|
|
|
346
346
|
)
|
|
347
347
|
),
|
|
348
348
|
)
|
|
349
|
-
if not env.
|
|
349
|
+
if not env.is_local_mocked_mode() and not env.is_test_state_mocked_mode():
|
|
350
350
|
self._eval_integration_pattern(
|
|
351
351
|
env=env,
|
|
352
352
|
resource_runtime_part=resource_runtime_part,
|
|
@@ -7,6 +7,9 @@ from localstack.services.stepfunctions.asl.component.common.error_name.failure_e
|
|
|
7
7
|
FailureEventException,
|
|
8
8
|
)
|
|
9
9
|
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.state.state_continue_with import (
|
|
11
|
+
ContinueWithEnd,
|
|
12
|
+
)
|
|
10
13
|
from localstack.services.stepfunctions.asl.component.state.state_fail.cause_decl import CauseDecl
|
|
11
14
|
from localstack.services.stepfunctions.asl.component.state.state_fail.error_decl import ErrorDecl
|
|
12
15
|
from localstack.services.stepfunctions.asl.component.state.state_props import StateProps
|
|
@@ -27,6 +30,7 @@ class StateFail(CommonStateField):
|
|
|
27
30
|
super().from_state_props(state_props)
|
|
28
31
|
self.cause = state_props.get(CauseDecl)
|
|
29
32
|
self.error = state_props.get(ErrorDecl)
|
|
33
|
+
self.continue_with = ContinueWithEnd()
|
|
30
34
|
|
|
31
35
|
def _eval_state(self, env: Environment) -> None:
|
|
32
36
|
task_failed_event_details = TaskFailedEventDetails()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import copy
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent
|
|
6
|
+
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
7
|
+
from localstack.services.stepfunctions.asl.component.state.state_continue_with import (
|
|
8
|
+
ContinueWithNext,
|
|
9
|
+
)
|
|
10
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
11
|
+
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
12
|
+
from localstack.services.stepfunctions.backend.test_state.test_state_mock import (
|
|
13
|
+
TestStateResponseReturn,
|
|
14
|
+
TestStateResponseThrow,
|
|
15
|
+
eval_mocked_response_throw,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T", bound=CommonStateField)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MockedBaseState(Generic[T], abc.ABC):
|
|
22
|
+
is_single_state: bool
|
|
23
|
+
_wrapped: T
|
|
24
|
+
|
|
25
|
+
def __init__(self, wrapped: T):
|
|
26
|
+
super().__init__()
|
|
27
|
+
self._wrapped = wrapped
|
|
28
|
+
self.apply_patches()
|
|
29
|
+
|
|
30
|
+
def apply_patches(self):
|
|
31
|
+
self._apply_patches()
|
|
32
|
+
|
|
33
|
+
original_eval_body = self._wrapped._eval_body
|
|
34
|
+
self._wrapped._eval_body = self.wrap_with_post_return(
|
|
35
|
+
original_eval_body, self.stop_execution
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def _apply_patches(self): ...
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def wrap(cls, state: T, is_single_state: bool = False) -> T:
|
|
43
|
+
cls.is_single_state = is_single_state
|
|
44
|
+
cls._wrapped = state
|
|
45
|
+
return cls(state)._wrapped
|
|
46
|
+
|
|
47
|
+
def __getattr__(self, attr: str):
|
|
48
|
+
return getattr(self._wrapped, attr)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def before_mock(self, env: TestStateEnvironment):
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def do_mock(self, env: TestStateEnvironment):
|
|
56
|
+
mocked_response = env.mock.get_next_result()
|
|
57
|
+
if not mocked_response:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
if isinstance(mocked_response, TestStateResponseThrow):
|
|
61
|
+
eval_mocked_response_throw(env, mocked_response)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
if isinstance(mocked_response, TestStateResponseReturn):
|
|
65
|
+
result_copy = copy.deepcopy(mocked_response.payload)
|
|
66
|
+
env.stack.append(result_copy)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def after_mock(self, env: TestStateEnvironment):
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def wrap_with_mock(cls, original_method):
|
|
74
|
+
def wrapper(env: TestStateEnvironment, *args, **kwargs):
|
|
75
|
+
if not env.mock.is_mocked():
|
|
76
|
+
original_method(env, *args, **kwargs)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
cls.before_mock(env)
|
|
80
|
+
try:
|
|
81
|
+
cls.do_mock(env)
|
|
82
|
+
finally:
|
|
83
|
+
cls.after_mock(env)
|
|
84
|
+
|
|
85
|
+
return wrapper
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def wrap_with_post_return(method, post_return_fn):
|
|
89
|
+
def wrapper(env: TestStateEnvironment, *args, **kwargs):
|
|
90
|
+
try:
|
|
91
|
+
method(env, *args, **kwargs)
|
|
92
|
+
finally:
|
|
93
|
+
post_return_fn(env)
|
|
94
|
+
|
|
95
|
+
return wrapper
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _eval_with_inspect(component: EvalComponent, key: str):
|
|
99
|
+
if not component:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
eval_body_fn = component._eval_body
|
|
103
|
+
|
|
104
|
+
def _update(env: TestStateEnvironment, *args, **kwargs):
|
|
105
|
+
# if inspectionData already populated, don't execute again
|
|
106
|
+
if key in env.inspection_data:
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
eval_body_fn(env, *args, **kwargs)
|
|
110
|
+
result = env.stack[-1]
|
|
111
|
+
env.inspection_data[key] = to_json_str(result)
|
|
112
|
+
|
|
113
|
+
component._eval_body = MockedBaseState.wrap_with_post_return(eval_body_fn, _update)
|
|
114
|
+
|
|
115
|
+
def stop_execution(self, env: TestStateEnvironment):
|
|
116
|
+
if isinstance(self._wrapped.continue_with, ContinueWithNext):
|
|
117
|
+
if next_state := self._wrapped.continue_with.next_state:
|
|
118
|
+
env.set_choice_selected(next_state.name)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
2
|
+
from localstack.services.stepfunctions.asl.component.state.state_choice.state_choice import (
|
|
3
|
+
StateChoice,
|
|
4
|
+
)
|
|
5
|
+
from localstack.services.stepfunctions.asl.component.state.state_continue_with import (
|
|
6
|
+
ContinueWithEnd,
|
|
7
|
+
)
|
|
8
|
+
from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail
|
|
9
|
+
from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import (
|
|
11
|
+
StateSucceed,
|
|
12
|
+
)
|
|
13
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import (
|
|
14
|
+
MockedBaseState,
|
|
15
|
+
)
|
|
16
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MockedCommonState(MockedBaseState[CommonStateField]):
|
|
20
|
+
def add_inspection_data(self, env: TestStateEnvironment):
|
|
21
|
+
state = self._wrapped
|
|
22
|
+
|
|
23
|
+
if not isinstance(state, StatePass):
|
|
24
|
+
if not self.is_single_state:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
if "afterInputPath" not in env.inspection_data:
|
|
28
|
+
env.inspection_data["afterInputPath"] = env.states.get_input()
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# If not a terminal state, only populate inspection data from pre-processor.
|
|
32
|
+
if not isinstance(self._wrapped.continue_with, ContinueWithEnd):
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if state.result:
|
|
36
|
+
# TODO: investigate interactions between these inspectionData field types.
|
|
37
|
+
# i.e parity tests shows that if "Result" is defined, 'afterInputPath' and 'afterParameters'
|
|
38
|
+
# cannot be present in the inspection data.
|
|
39
|
+
env.inspection_data.pop("afterInputPath", None)
|
|
40
|
+
env.inspection_data.pop("afterParameters", None)
|
|
41
|
+
|
|
42
|
+
if "afterResultSelector" not in env.inspection_data:
|
|
43
|
+
env.inspection_data["afterResultSelector"] = state.result.result_obj
|
|
44
|
+
|
|
45
|
+
if "afterResultPath" not in env.inspection_data:
|
|
46
|
+
env.inspection_data["afterResultPath"] = env.inspection_data.get(
|
|
47
|
+
"afterResultSelector", env.states.get_input()
|
|
48
|
+
)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
if "afterInputPath" not in env.inspection_data:
|
|
52
|
+
env.inspection_data["afterInputPath"] = env.states.get_input()
|
|
53
|
+
|
|
54
|
+
if "afterParameters" not in env.inspection_data:
|
|
55
|
+
env.inspection_data["afterParameters"] = env.inspection_data.get(
|
|
56
|
+
"afterInputPath", env.states.get_input()
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if "afterResultSelector" not in env.inspection_data:
|
|
60
|
+
env.inspection_data["afterResultSelector"] = env.inspection_data["afterParameters"]
|
|
61
|
+
|
|
62
|
+
if "afterResultPath" not in env.inspection_data:
|
|
63
|
+
env.inspection_data["afterResultPath"] = env.inspection_data.get(
|
|
64
|
+
"afterResultSelector", env.states.get_input()
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def _apply_patches(self):
|
|
68
|
+
if not isinstance(self._wrapped, (StatePass, StateFail, StateChoice, StateSucceed)):
|
|
69
|
+
raise ValueError("Needs to be a Pass, Fail, Choice, or Succeed state.")
|
|
70
|
+
|
|
71
|
+
original_eval_body = self.wrap_with_mock(self._wrapped._eval_body)
|
|
72
|
+
|
|
73
|
+
def mock_eval_execution(env: TestStateEnvironment):
|
|
74
|
+
original_eval_body(env)
|
|
75
|
+
env.set_choice_selected(env.next_state_name)
|
|
76
|
+
|
|
77
|
+
mock_eval_execution = self.wrap_with_post_return(
|
|
78
|
+
method=mock_eval_execution,
|
|
79
|
+
post_return_fn=self.add_inspection_data,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self._wrapped._eval_body = mock_eval_execution
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
from localstack.services.stepfunctions.asl.component.common.catch.catcher_outcome import (
|
|
5
|
+
CatcherOutcomeCaught,
|
|
6
|
+
)
|
|
7
|
+
from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import (
|
|
8
|
+
FailureEvent,
|
|
9
|
+
)
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.common.query_language import (
|
|
11
|
+
QueryLanguageMode,
|
|
12
|
+
)
|
|
13
|
+
from localstack.services.stepfunctions.asl.component.common.retry.retrier_decl import RetrierDecl
|
|
14
|
+
from localstack.services.stepfunctions.asl.component.common.retry.retrier_outcome import (
|
|
15
|
+
RetrierOutcome,
|
|
16
|
+
)
|
|
17
|
+
from localstack.services.stepfunctions.asl.component.common.retry.retry_outcome import RetryOutcome
|
|
18
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.execute_state import (
|
|
19
|
+
ExecutionState,
|
|
20
|
+
)
|
|
21
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import (
|
|
22
|
+
MockedBaseState,
|
|
23
|
+
)
|
|
24
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
25
|
+
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MockedStateExecution(MockedBaseState[ExecutionState]):
|
|
29
|
+
def add_inspection_data(self, env: TestStateEnvironment):
|
|
30
|
+
if self._wrapped.query_language.query_language_mode == QueryLanguageMode.JSONPath:
|
|
31
|
+
if "afterResultSelector" not in env.inspection_data:
|
|
32
|
+
# HACK: A DistributedItemProcessorEvalInput is added to the stack and never popped off
|
|
33
|
+
# during an error case. So we need to check the inspected value is correct before
|
|
34
|
+
# adding it to our inspectionData.
|
|
35
|
+
if isinstance(env.stack[-1], (dict, str, int, float)):
|
|
36
|
+
env.inspection_data["afterResultSelector"] = to_json_str(env.stack[-1])
|
|
37
|
+
|
|
38
|
+
if catch := self._wrapped.catch:
|
|
39
|
+
for ind, catcher in enumerate(catch.catchers):
|
|
40
|
+
original_fn = catcher._eval_body
|
|
41
|
+
catcher._eval_body = self.with_catch_state_id(original_fn, ind)
|
|
42
|
+
|
|
43
|
+
if retry := self._wrapped.retry:
|
|
44
|
+
for ind, retrier in enumerate(retry.retriers):
|
|
45
|
+
original_fn = retrier._eval_body
|
|
46
|
+
retrier._eval_body = self.with_retry_state_id(retrier, ind)
|
|
47
|
+
|
|
48
|
+
def _apply_patches(self):
|
|
49
|
+
if not isinstance(self._wrapped, ExecutionState):
|
|
50
|
+
raise ValueError("Can only apply MockedStateExecution patches to an ExecutionState")
|
|
51
|
+
state = self._wrapped
|
|
52
|
+
|
|
53
|
+
if state.query_language.query_language_mode == QueryLanguageMode.JSONPath:
|
|
54
|
+
self._eval_with_inspect(self._wrapped.input_path, "afterInputPath")
|
|
55
|
+
self._eval_with_inspect(self._wrapped.result_path, "afterResultPath")
|
|
56
|
+
|
|
57
|
+
self._eval_with_inspect(self._wrapped.result_selector, "afterResultSelector")
|
|
58
|
+
original_eval_execution = self._wrapped._eval_execution
|
|
59
|
+
|
|
60
|
+
if self._wrapped.catch:
|
|
61
|
+
original_fn = self._wrapped._handle_catch
|
|
62
|
+
self._wrapped._handle_catch = partial(self._handle_catch, original_fn)
|
|
63
|
+
|
|
64
|
+
if self._wrapped.retry:
|
|
65
|
+
original_fn = self._wrapped._handle_retry
|
|
66
|
+
self._wrapped._handle_retry = partial(self._handle_retry, original_fn)
|
|
67
|
+
|
|
68
|
+
self._wrapped._eval_execution = self.wrap_with_post_return(
|
|
69
|
+
method=original_eval_execution,
|
|
70
|
+
post_return_fn=self.add_inspection_data,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def with_catch_state_id(
|
|
75
|
+
original_eval_body: Callable[[TestStateEnvironment], None], state_id: int
|
|
76
|
+
) -> Callable[[TestStateEnvironment], None]:
|
|
77
|
+
def _wrapped(env: TestStateEnvironment):
|
|
78
|
+
original_eval_body(env)
|
|
79
|
+
|
|
80
|
+
if isinstance(env.stack[-1], CatcherOutcomeCaught):
|
|
81
|
+
if not (error_details := env.inspection_data.get("errorDetails")):
|
|
82
|
+
error_details = env.inspection_data["errorDetails"] = {}
|
|
83
|
+
|
|
84
|
+
error_details["catchIndex"] = state_id
|
|
85
|
+
|
|
86
|
+
return _wrapped
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def with_retry_state_id(
|
|
90
|
+
retrier: RetrierDecl, state_id: int
|
|
91
|
+
) -> Callable[[TestStateEnvironment], None]:
|
|
92
|
+
original_retrier_eval_body = retrier._eval_body
|
|
93
|
+
|
|
94
|
+
def _wrapped(env: TestStateEnvironment):
|
|
95
|
+
if (retry_count := env.mock._state_configuration.get("retrierRetryCount", 0)) > 0:
|
|
96
|
+
retrier.max_attempts._store_attempt_number(env, retry_count - 1)
|
|
97
|
+
|
|
98
|
+
original_retrier_eval_body(env)
|
|
99
|
+
|
|
100
|
+
if not (error_details := env.inspection_data.get("errorDetails")):
|
|
101
|
+
error_details = env.inspection_data["errorDetails"] = {}
|
|
102
|
+
|
|
103
|
+
error_details["retryIndex"] = state_id
|
|
104
|
+
if env.stack[-1] == RetrierOutcome.Executed:
|
|
105
|
+
# TODO(gregfurman): Ideally, retryBackoffIntervalSeconds should be written to inspectionData
|
|
106
|
+
# within the retrier.backoff_rate decleration (perhaps at _access_next_multiplier).
|
|
107
|
+
rate = retrier.backoff_rate.rate
|
|
108
|
+
interval = retrier.interval_seconds.seconds
|
|
109
|
+
error_details["retryBackoffIntervalSeconds"] = int(interval * (rate**retry_count))
|
|
110
|
+
|
|
111
|
+
return _wrapped
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _handle_catch(
|
|
115
|
+
original_handle_catch: Callable[[TestStateEnvironment, FailureEvent], None],
|
|
116
|
+
env: TestStateEnvironment,
|
|
117
|
+
failure_event: FailureEvent,
|
|
118
|
+
) -> None:
|
|
119
|
+
original_handle_catch(env, failure_event)
|
|
120
|
+
|
|
121
|
+
spec: dict[str, str] = ExecutionState._construct_error_output_value(failure_event)
|
|
122
|
+
error, cause = spec.get("Error"), spec.get("Cause")
|
|
123
|
+
|
|
124
|
+
env.set_caught_error(env.next_state_name, error, cause)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _handle_retry(
|
|
128
|
+
original_handle_retry: Callable[[TestStateEnvironment, FailureEvent], RetryOutcome],
|
|
129
|
+
env: TestStateEnvironment,
|
|
130
|
+
failure_event: FailureEvent,
|
|
131
|
+
) -> RetryOutcome:
|
|
132
|
+
res = original_handle_retry(env, failure_event)
|
|
133
|
+
|
|
134
|
+
spec: dict[str, str] = ExecutionState._construct_error_output_value(failure_event)
|
|
135
|
+
error, cause = spec.get("Error"), spec.get("Cause")
|
|
136
|
+
|
|
137
|
+
if res == RetryOutcome.CanRetry:
|
|
138
|
+
env.set_retriable_error(error, cause)
|
|
139
|
+
return res
|