localstack-core 4.10.1.dev42__py3-none-any.whl → 4.12.1.dev18__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.
Potentially problematic release.
This version of localstack-core might be problematic. Click here for more details.
- localstack/aws/api/apigateway/__init__.py +42 -0
- localstack/aws/api/cloudformation/__init__.py +161 -0
- localstack/aws/api/ec2/__init__.py +1178 -12
- localstack/aws/api/iam/__init__.py +228 -0
- localstack/aws/api/kms/__init__.py +1 -0
- localstack/aws/api/lambda_/__init__.py +1034 -66
- localstack/aws/api/logs/__init__.py +500 -0
- localstack/aws/api/opensearch/__init__.py +100 -0
- localstack/aws/api/redshift/__init__.py +69 -0
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +36 -0
- localstack/aws/api/route53/__init__.py +45 -0
- localstack/aws/api/route53resolver/__init__.py +1 -0
- localstack/aws/api/s3/__init__.py +64 -0
- localstack/aws/api/s3control/__init__.py +19 -0
- localstack/aws/api/secretsmanager/__init__.py +37 -23
- localstack/aws/api/stepfunctions/__init__.py +52 -10
- localstack/aws/api/sts/__init__.py +52 -0
- localstack/aws/connect.py +35 -15
- localstack/aws/handlers/logging.py +8 -4
- localstack/aws/handlers/service.py +11 -2
- localstack/aws/protocol/serializer.py +1 -1
- localstack/config.py +8 -0
- localstack/constants.py +3 -0
- localstack/deprecations.py +0 -6
- localstack/dev/kubernetes/__main__.py +39 -14
- localstack/runtime/analytics.py +11 -0
- localstack/services/acm/provider.py +17 -1
- localstack/services/apigateway/legacy/provider.py +28 -15
- localstack/services/cloudformation/engine/template_preparer.py +6 -2
- localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +15 -1
- localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
- localstack/services/cloudformation/provider.py +26 -1
- localstack/services/cloudformation/provider_utils.py +20 -0
- localstack/services/cloudformation/resource_provider.py +5 -4
- localstack/services/cloudformation/scaffolding/__main__.py +94 -22
- localstack/services/cloudformation/v2/provider.py +41 -0
- 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 +16 -22
- localstack/services/kms/provider.py +4 -0
- localstack/services/lambda_/analytics.py +11 -2
- localstack/services/lambda_/api_utils.py +37 -20
- localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
- localstack/services/lambda_/invocation/assignment.py +4 -1
- localstack/services/lambda_/invocation/event_manager.py +15 -11
- localstack/services/lambda_/invocation/execution_environment.py +21 -2
- localstack/services/lambda_/invocation/lambda_models.py +31 -2
- localstack/services/lambda_/invocation/lambda_service.py +62 -3
- localstack/services/lambda_/invocation/models.py +9 -1
- localstack/services/lambda_/invocation/version_manager.py +18 -3
- localstack/services/lambda_/provider.py +307 -106
- localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
- localstack/services/lambda_/runtimes.py +3 -1
- localstack/services/logs/provider.py +9 -0
- localstack/services/opensearch/packages.py +34 -20
- localstack/services/opensearch/provider.py +53 -3
- localstack/services/resource_groups/provider.py +5 -1
- localstack/services/resourcegroupstaggingapi/provider.py +6 -1
- localstack/services/route53/provider.py +7 -0
- localstack/services/route53resolver/provider.py +5 -0
- localstack/services/s3/constants.py +5 -0
- localstack/services/s3/exceptions.py +9 -0
- localstack/services/s3/models.py +9 -1
- localstack/services/s3/provider.py +51 -43
- localstack/services/s3/utils.py +81 -15
- localstack/services/s3control/provider.py +107 -2
- localstack/services/s3control/validation.py +50 -0
- localstack/services/scheduler/provider.py +4 -2
- localstack/services/secretsmanager/provider.py +4 -0
- localstack/services/ses/provider.py +4 -0
- localstack/services/sns/constants.py +16 -1
- localstack/services/sns/provider.py +5 -0
- localstack/services/sns/publisher.py +15 -6
- localstack/services/sns/v2/models.py +9 -0
- localstack/services/sns/v2/provider.py +750 -19
- localstack/services/sns/v2/utils.py +12 -0
- localstack/services/sqs/constants.py +6 -0
- localstack/services/sqs/provider.py +9 -1
- localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
- localstack/services/ssm/provider.py +6 -0
- 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 +256 -22
- localstack/services/stepfunctions/backend/execution.py +10 -11
- 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 +83 -25
- localstack/services/stepfunctions/test_state/mock_config.py +47 -0
- localstack/services/sts/provider.py +7 -0
- localstack/services/support/provider.py +5 -1
- localstack/services/swf/provider.py +5 -1
- localstack/services/transcribe/provider.py +7 -0
- localstack/testing/aws/lambda_utils.py +1 -1
- localstack/testing/aws/util.py +2 -1
- localstack/testing/config.py +1 -0
- localstack/testing/pytest/fixtures.py +28 -0
- localstack/testing/snapshots/transformer_utility.py +5 -0
- 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 +2 -4
- localstack/utils/batching.py +258 -0
- localstack/utils/bootstrap.py +2 -2
- localstack/utils/catalog/catalog.py +3 -2
- localstack/utils/collections.py +23 -11
- localstack/utils/container_utils/container_client.py +22 -13
- localstack/utils/container_utils/docker_cmd_client.py +6 -6
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/METADATA +7 -7
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/RECORD +155 -146
- localstack_core-4.12.1.dev18.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.dev42.dist-info/plux.json +0 -1
- /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from localstack.services.stepfunctions.asl.component.common.error_name.states_error_name_type import (
|
|
2
|
+
StatesErrorNameType,
|
|
3
|
+
)
|
|
4
|
+
from localstack.services.stepfunctions.asl.component.common.query_language import (
|
|
5
|
+
QueryLanguageMode,
|
|
6
|
+
)
|
|
7
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.state_map import (
|
|
8
|
+
StateMap,
|
|
9
|
+
)
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import (
|
|
11
|
+
MockedBaseState,
|
|
12
|
+
)
|
|
13
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.execution import (
|
|
14
|
+
MockedStateExecution,
|
|
15
|
+
)
|
|
16
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
17
|
+
from localstack.services.stepfunctions.backend.test_state.test_state_mock import (
|
|
18
|
+
TestStateResponseThrow,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MockedStateMap(MockedBaseState[StateMap]):
|
|
23
|
+
def add_inspection_data(self, env: TestStateEnvironment):
|
|
24
|
+
if tolerated_failure_percentage := env.inspection_data.get("toleratedFailurePercentage"):
|
|
25
|
+
env.inspection_data["toleratedFailurePercentage"] = float(tolerated_failure_percentage)
|
|
26
|
+
|
|
27
|
+
if tolerated_failure_count := env.inspection_data.get("toleratedFailureCount"):
|
|
28
|
+
env.inspection_data["toleratedFailureCount"] = int(tolerated_failure_count)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def before_mock(cls, env: TestStateEnvironment):
|
|
32
|
+
if not env.mock or not env.mock._state_configuration:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if not cls._wrapped.catch and not cls._wrapped.retry:
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
if failure_count := env.mock._state_configuration.get("mapIterationFailureCount"):
|
|
39
|
+
max_failure_count = (
|
|
40
|
+
cls._wrapped.tolerated_failure_count_decl._eval_tolerated_failure_count(env)
|
|
41
|
+
)
|
|
42
|
+
if failure_count > max_failure_count:
|
|
43
|
+
error_response = TestStateResponseThrow(
|
|
44
|
+
error=StatesErrorNameType.StatesExceedToleratedFailureThreshold.to_name(),
|
|
45
|
+
cause="The specified tolerated failure threshold was exceeded",
|
|
46
|
+
)
|
|
47
|
+
env.mock.add_result(error_response)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
def _apply_patches(self):
|
|
51
|
+
self._wrapped = MockedStateExecution.wrap(self._wrapped)
|
|
52
|
+
|
|
53
|
+
if self._wrapped.query_language.query_language_mode == QueryLanguageMode.JSONPath:
|
|
54
|
+
self._eval_with_inspect(self._wrapped.items_path, "afterInputPath")
|
|
55
|
+
self._eval_with_inspect(self._wrapped.item_selector, "afterItemsSelector")
|
|
56
|
+
|
|
57
|
+
original_eval_max_concurrency = self._wrapped.max_concurrency_decl._eval_max_concurrency
|
|
58
|
+
original_iteration_component_eval_body = self._wrapped.iteration_component._eval_body
|
|
59
|
+
original_eval_execution = self._wrapped._eval_execution
|
|
60
|
+
|
|
61
|
+
# HACK(gregfurman): Ideally we should be using the "$$.Map.Item.Index" to access each item of the
|
|
62
|
+
# mocked result list. This is turning out to be quite complicated, so instead just patch the
|
|
63
|
+
# StateMap's max concurrency decleration to always eval to '1' -- making the map run in serial.
|
|
64
|
+
def mock_max_concurrency(env: TestStateEnvironment) -> int:
|
|
65
|
+
# always set concurrency to 1 but inspection data is accurate to original
|
|
66
|
+
env.inspection_data["maxConcurrency"] = original_eval_max_concurrency(env)
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
self._wrapped._eval_execution = self.wrap_with_post_return(
|
|
70
|
+
method=original_eval_execution,
|
|
71
|
+
post_return_fn=self.add_inspection_data,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
self._wrapped.max_concurrency_decl._eval_max_concurrency = mock_max_concurrency
|
|
75
|
+
self._wrapped.iteration_component._eval_body = self.wrap_with_mock(
|
|
76
|
+
original_iteration_component_eval_body
|
|
77
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from localstack.services.stepfunctions.asl.component.common.query_language import (
|
|
2
|
+
QueryLanguageMode,
|
|
3
|
+
)
|
|
4
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service import (
|
|
5
|
+
StateTaskService,
|
|
6
|
+
)
|
|
7
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.state_task import (
|
|
8
|
+
StateTask,
|
|
9
|
+
)
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import (
|
|
11
|
+
MockedBaseState,
|
|
12
|
+
)
|
|
13
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.execution import (
|
|
14
|
+
MockedStateExecution,
|
|
15
|
+
)
|
|
16
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
17
|
+
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MockedStateTask(MockedBaseState[StateTask]):
|
|
21
|
+
def add_inspection_data(self, env: TestStateEnvironment):
|
|
22
|
+
if self._wrapped.query_language.query_language_mode == QueryLanguageMode.JSONPath:
|
|
23
|
+
if "afterParameters" not in env.inspection_data:
|
|
24
|
+
env.inspection_data["afterParameters"] = to_json_str(env.states.get_input())
|
|
25
|
+
|
|
26
|
+
def _apply_patches(self):
|
|
27
|
+
self._wrapped = MockedStateExecution.wrap(self._wrapped)
|
|
28
|
+
|
|
29
|
+
if self._wrapped.query_language.query_language_mode == QueryLanguageMode.JSONPath:
|
|
30
|
+
self._eval_with_inspect(self._wrapped.parargs, "afterParameters")
|
|
31
|
+
|
|
32
|
+
if isinstance(self._wrapped, StateTaskService):
|
|
33
|
+
self._wrapped._eval_service_task = self.wrap_with_mock(self._wrapped._eval_service_task)
|
|
34
|
+
|
|
35
|
+
original_eval_execution = self._wrapped._eval_execution
|
|
36
|
+
|
|
37
|
+
def mock_eval_execution(env: TestStateEnvironment, *args, **kwargs):
|
|
38
|
+
original_eval_execution(env, *args, **kwargs)
|
|
39
|
+
result = to_json_str(env.stack[-1])
|
|
40
|
+
env.inspection_data["result"] = result
|
|
41
|
+
|
|
42
|
+
self._wrapped._eval_execution = self.wrap_with_post_return(
|
|
43
|
+
mock_eval_execution, self.add_inspection_data
|
|
44
|
+
)
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import copy
|
|
4
4
|
import logging
|
|
5
5
|
import threading
|
|
6
|
-
from typing import Any, Final, Optional
|
|
6
|
+
from typing import Any, Final, Optional, Self
|
|
7
7
|
|
|
8
8
|
from localstack.aws.api.stepfunctions import (
|
|
9
9
|
Arn,
|
|
@@ -34,7 +34,10 @@ from localstack.services.stepfunctions.asl.eval.program_state import (
|
|
|
34
34
|
from localstack.services.stepfunctions.asl.eval.states import ContextObjectData, States
|
|
35
35
|
from localstack.services.stepfunctions.asl.eval.variable_store import VariableStore
|
|
36
36
|
from localstack.services.stepfunctions.backend.activity import Activity
|
|
37
|
-
from localstack.services.stepfunctions.
|
|
37
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import (
|
|
38
|
+
LocalMockedResponse,
|
|
39
|
+
LocalMockTestCase,
|
|
40
|
+
)
|
|
38
41
|
|
|
39
42
|
LOG = logging.getLogger(__name__)
|
|
40
43
|
|
|
@@ -52,7 +55,7 @@ class Environment:
|
|
|
52
55
|
callback_pool_manager: CallbackPoolManager
|
|
53
56
|
map_run_record_pool_manager: MapRunRecordPoolManager
|
|
54
57
|
activity_store: Final[dict[Arn, Activity]]
|
|
55
|
-
|
|
58
|
+
local_mock_test_case: LocalMockTestCase | None = None
|
|
56
59
|
|
|
57
60
|
_frames: Final[list[Environment]]
|
|
58
61
|
_is_frame: bool = False
|
|
@@ -71,7 +74,7 @@ class Environment:
|
|
|
71
74
|
cloud_watch_logging_session: CloudWatchLoggingSession | None,
|
|
72
75
|
activity_store: dict[Arn, Activity],
|
|
73
76
|
variable_store: VariableStore | None = None,
|
|
74
|
-
|
|
77
|
+
local_mock_test_case: LocalMockTestCase | None = None,
|
|
75
78
|
):
|
|
76
79
|
super().__init__()
|
|
77
80
|
self._state_mutex = threading.RLock()
|
|
@@ -89,7 +92,7 @@ class Environment:
|
|
|
89
92
|
|
|
90
93
|
self.activity_store = activity_store
|
|
91
94
|
|
|
92
|
-
self.
|
|
95
|
+
self.local_mock_test_case = local_mock_test_case
|
|
93
96
|
|
|
94
97
|
self._frames = []
|
|
95
98
|
self._is_frame = False
|
|
@@ -101,9 +104,9 @@ class Environment:
|
|
|
101
104
|
|
|
102
105
|
@classmethod
|
|
103
106
|
def as_frame_of(
|
|
104
|
-
cls, env:
|
|
105
|
-
) ->
|
|
106
|
-
return
|
|
107
|
+
cls, env: Self, event_history_frame_cache: EventHistoryContext | None = None
|
|
108
|
+
) -> Self:
|
|
109
|
+
return cls.as_inner_frame_of(
|
|
107
110
|
env=env,
|
|
108
111
|
variable_store=env.variable_store,
|
|
109
112
|
event_history_frame_cache=event_history_frame_cache,
|
|
@@ -112,10 +115,10 @@ class Environment:
|
|
|
112
115
|
@classmethod
|
|
113
116
|
def as_inner_frame_of(
|
|
114
117
|
cls,
|
|
115
|
-
env:
|
|
118
|
+
env: Self,
|
|
116
119
|
variable_store: VariableStore,
|
|
117
120
|
event_history_frame_cache: EventHistoryContext | None = None,
|
|
118
|
-
) ->
|
|
121
|
+
) -> Self:
|
|
119
122
|
# Construct the frame's context object data.
|
|
120
123
|
context = ContextObjectData(
|
|
121
124
|
Execution=env.states.context_object.context_object_data["Execution"],
|
|
@@ -138,8 +141,8 @@ class Environment:
|
|
|
138
141
|
cloud_watch_logging_session=env.cloud_watch_logging_session,
|
|
139
142
|
activity_store=env.activity_store,
|
|
140
143
|
variable_store=variable_store,
|
|
141
|
-
mock_test_case=env.mock_test_case,
|
|
142
144
|
)
|
|
145
|
+
frame.local_mock_test_case = env.local_mock_test_case
|
|
143
146
|
frame._is_frame = True
|
|
144
147
|
frame.event_manager = env.event_manager
|
|
145
148
|
if "State" in env.states.context_object.context_object_data:
|
|
@@ -267,32 +270,37 @@ class Environment:
|
|
|
267
270
|
def is_standard_workflow(self) -> bool:
|
|
268
271
|
return self.execution_type == StateMachineType.STANDARD
|
|
269
272
|
|
|
270
|
-
def
|
|
273
|
+
def is_test_state_mocked_mode(self) -> bool:
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
def is_local_mocked_mode(self) -> bool:
|
|
271
277
|
"""
|
|
272
|
-
Returns True if
|
|
273
|
-
|
|
274
|
-
|
|
278
|
+
Returns True if:
|
|
279
|
+
- the state machine is running in Step Functions Local mode
|
|
280
|
+
- the current state has a defined Local mock configuration in the target environment or frame
|
|
281
|
+
|
|
282
|
+
Otherwise, returns False.
|
|
275
283
|
"""
|
|
276
284
|
return (
|
|
277
|
-
self.
|
|
278
|
-
and self.next_state_name in self.
|
|
285
|
+
self.local_mock_test_case is not None
|
|
286
|
+
and self.next_state_name in self.local_mock_test_case.state_mocked_responses
|
|
279
287
|
)
|
|
280
288
|
|
|
281
|
-
def
|
|
282
|
-
if not self.
|
|
289
|
+
def get_current_local_mocked_response(self) -> LocalMockedResponse:
|
|
290
|
+
if not self.is_local_mocked_mode():
|
|
283
291
|
raise RuntimeError(
|
|
284
292
|
"Cannot retrieve mocked response: execution is not operating in mocked mode"
|
|
285
293
|
)
|
|
286
294
|
state_name = self.next_state_name
|
|
287
|
-
state_mocked_responses: Optional = self.
|
|
295
|
+
state_mocked_responses: Optional = self.local_mock_test_case.state_mocked_responses.get(
|
|
288
296
|
state_name
|
|
289
297
|
)
|
|
290
298
|
if state_mocked_responses is None:
|
|
291
|
-
raise RuntimeError(f"No mocked response definition for state '{state_name}'")
|
|
299
|
+
raise RuntimeError(f"No Local mocked response definition for state '{state_name}'")
|
|
292
300
|
retry_count = self.states.context_object.context_object_data["State"]["RetryCount"]
|
|
293
301
|
if len(state_mocked_responses.mocked_responses) <= retry_count:
|
|
294
302
|
raise RuntimeError(
|
|
295
|
-
f"No mocked response definition for state '{state_name}' "
|
|
303
|
+
f"No Local mocked response definition for state '{state_name}' "
|
|
296
304
|
f"and retry number '{retry_count}'"
|
|
297
305
|
)
|
|
298
306
|
return state_mocked_responses.mocked_responses[retry_count]
|
|
@@ -82,7 +82,7 @@ class States:
|
|
|
82
82
|
context_object: Final[ContextObject]
|
|
83
83
|
|
|
84
84
|
def __init__(self, context: ContextObjectData):
|
|
85
|
-
input_value = context
|
|
85
|
+
input_value = context.get("Execution", {}).get("Input", {})
|
|
86
86
|
self._states_data = StatesData(input=input_value, context=context)
|
|
87
87
|
self.context_object = ContextObject(context_object=context)
|
|
88
88
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Self
|
|
4
|
+
|
|
3
5
|
from localstack.aws.api.stepfunctions import Arn, InspectionData, StateMachineType
|
|
4
6
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
5
7
|
from localstack.services.stepfunctions.asl.eval.evaluation_details import AWSExecutionDetails
|
|
@@ -14,14 +16,18 @@ from localstack.services.stepfunctions.asl.eval.program_state import (
|
|
|
14
16
|
)
|
|
15
17
|
from localstack.services.stepfunctions.asl.eval.states import ContextObjectData
|
|
16
18
|
from localstack.services.stepfunctions.asl.eval.test_state.program_state import (
|
|
19
|
+
ProgramCaughtError,
|
|
17
20
|
ProgramChoiceSelected,
|
|
21
|
+
ProgramRetriable,
|
|
18
22
|
)
|
|
19
23
|
from localstack.services.stepfunctions.asl.eval.variable_store import VariableStore
|
|
20
24
|
from localstack.services.stepfunctions.backend.activity import Activity
|
|
25
|
+
from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
class TestStateEnvironment(Environment):
|
|
24
29
|
inspection_data: InspectionData
|
|
30
|
+
mock: TestStateMock
|
|
25
31
|
|
|
26
32
|
def __init__(
|
|
27
33
|
self,
|
|
@@ -31,6 +37,8 @@ class TestStateEnvironment(Environment):
|
|
|
31
37
|
event_history_context: EventHistoryContext,
|
|
32
38
|
activity_store: dict[Arn, Activity],
|
|
33
39
|
cloud_watch_logging_session: CloudWatchLoggingSession | None = None,
|
|
40
|
+
variable_store: VariableStore | None = None,
|
|
41
|
+
mock: TestStateMock | None = None,
|
|
34
42
|
):
|
|
35
43
|
super().__init__(
|
|
36
44
|
aws_execution_details=aws_execution_details,
|
|
@@ -39,30 +47,43 @@ class TestStateEnvironment(Environment):
|
|
|
39
47
|
event_history_context=event_history_context,
|
|
40
48
|
cloud_watch_logging_session=cloud_watch_logging_session,
|
|
41
49
|
activity_store=activity_store,
|
|
50
|
+
variable_store=variable_store,
|
|
42
51
|
)
|
|
43
52
|
self.inspection_data = InspectionData()
|
|
53
|
+
self.mock = mock
|
|
54
|
+
|
|
55
|
+
def is_test_state_mocked_mode(self) -> bool:
|
|
56
|
+
return self.mock.is_mocked()
|
|
44
57
|
|
|
58
|
+
@classmethod
|
|
45
59
|
def as_frame_of(
|
|
46
60
|
cls,
|
|
47
|
-
env:
|
|
61
|
+
env: Self,
|
|
48
62
|
event_history_frame_cache: EventHistoryContext | None = None,
|
|
49
|
-
) ->
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return frame
|
|
63
|
+
) -> Self:
|
|
64
|
+
if (mocked_context := env.mock.get_context()) is not None:
|
|
65
|
+
env.states.context_object.context_object_data = mocked_context
|
|
53
66
|
|
|
67
|
+
return cls.as_inner_frame_of(
|
|
68
|
+
env=env,
|
|
69
|
+
variable_store=env.variable_store,
|
|
70
|
+
event_history_frame_cache=event_history_frame_cache,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
54
74
|
def as_inner_frame_of(
|
|
55
75
|
cls,
|
|
56
|
-
env:
|
|
76
|
+
env: Self,
|
|
57
77
|
variable_store: VariableStore,
|
|
58
78
|
event_history_frame_cache: EventHistoryContext | None = None,
|
|
59
|
-
) ->
|
|
79
|
+
) -> Self:
|
|
60
80
|
frame = super().as_inner_frame_of(
|
|
61
81
|
env=env,
|
|
62
82
|
event_history_frame_cache=event_history_frame_cache,
|
|
63
83
|
variable_store=variable_store,
|
|
64
84
|
)
|
|
65
85
|
frame.inspection_data = env.inspection_data
|
|
86
|
+
frame.mock = env.mock
|
|
66
87
|
return frame
|
|
67
88
|
|
|
68
89
|
def set_choice_selected(self, next_state_name: str) -> None:
|
|
@@ -71,5 +92,24 @@ class TestStateEnvironment(Environment):
|
|
|
71
92
|
self._program_state = ProgramChoiceSelected(next_state_name=next_state_name)
|
|
72
93
|
self.program_state_event.set()
|
|
73
94
|
self.program_state_event.clear()
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
|
|
96
|
+
def set_caught_error(self, next_state_name: str, error: str, cause: str) -> None:
|
|
97
|
+
with self._state_mutex:
|
|
98
|
+
if isinstance(self._program_state, ProgramRunning):
|
|
99
|
+
self._program_state = ProgramCaughtError(
|
|
100
|
+
next_state_name=next_state_name,
|
|
101
|
+
error=error,
|
|
102
|
+
cause=cause,
|
|
103
|
+
)
|
|
104
|
+
self.program_state_event.set()
|
|
105
|
+
self.program_state_event.clear()
|
|
106
|
+
|
|
107
|
+
def set_retriable_error(self, error: str, cause: str) -> None:
|
|
108
|
+
with self._state_mutex:
|
|
109
|
+
if isinstance(self._program_state, ProgramRunning):
|
|
110
|
+
self._program_state = ProgramRetriable(
|
|
111
|
+
error=error,
|
|
112
|
+
cause=cause,
|
|
113
|
+
)
|
|
114
|
+
self.program_state_event.set()
|
|
115
|
+
self.program_state_event.clear()
|
|
@@ -9,3 +9,25 @@ class ProgramChoiceSelected(ProgramState):
|
|
|
9
9
|
def __init__(self, next_state_name: str):
|
|
10
10
|
super().__init__()
|
|
11
11
|
self.next_state_name = next_state_name
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProgramCaughtError(ProgramState):
|
|
15
|
+
next_state_name: Final[str]
|
|
16
|
+
error: Final[str]
|
|
17
|
+
cause: Final[str]
|
|
18
|
+
|
|
19
|
+
def __init__(self, next_state_name: str, error: str, cause: str):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.next_state_name = next_state_name
|
|
22
|
+
self.error = error
|
|
23
|
+
self.cause = cause
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ProgramRetriable(ProgramState):
|
|
27
|
+
error: Final[str]
|
|
28
|
+
cause: Final[str]
|
|
29
|
+
|
|
30
|
+
def __init__(self, error: str, cause: str):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.error = error
|
|
33
|
+
self.cause = cause
|
|
@@ -33,7 +33,11 @@ _PATTERN_VARIABLE_REFERENCE = re.compile(
|
|
|
33
33
|
# allowing escapes
|
|
34
34
|
r"(?:\"(?:\\.|[^\"\\])*\"|\'(?:\\.|[^\'\\])*\')"
|
|
35
35
|
r"|"
|
|
36
|
-
# 3)
|
|
36
|
+
# 3) Non-capturing branch for bracket expressions [...]
|
|
37
|
+
# Consume these to avoid capturing $ inside them
|
|
38
|
+
r"(?:\[[^\[\]]*\])"
|
|
39
|
+
r"|"
|
|
40
|
+
# 4) Capturing branch for $$, $identifier[.prop…], or lone $
|
|
37
41
|
r"(\$\$|\$[A-Za-z0-9_$]+(?:\.[A-Za-z0-9_][A-Za-z0-9_$]*)*|\$)"
|
|
38
42
|
)
|
|
39
43
|
|