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.
Potentially problematic release.
This version of localstack-core might be problematic. Click here for more details.
- 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
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import enum
|
|
2
2
|
from typing import Final
|
|
3
3
|
|
|
4
|
+
from antlr4.tree.Tree import ParseTree
|
|
5
|
+
|
|
4
6
|
from localstack.services.stepfunctions.asl.antlr.runtime.ASLParser import ASLParser
|
|
5
|
-
from localstack.services.stepfunctions.asl.
|
|
7
|
+
from localstack.services.stepfunctions.asl.antlt4utils.antlr4utils import (
|
|
8
|
+
is_production,
|
|
9
|
+
)
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.common.parargs import (
|
|
11
|
+
ArgumentsJSONataTemplateValueObject,
|
|
12
|
+
ArgumentsStringJSONata,
|
|
13
|
+
Parameters,
|
|
14
|
+
)
|
|
6
15
|
from localstack.services.stepfunctions.asl.component.common.path.input_path import InputPath
|
|
16
|
+
from localstack.services.stepfunctions.asl.component.common.path.items_path import ItemsPath
|
|
7
17
|
from localstack.services.stepfunctions.asl.component.common.path.result_path import ResultPath
|
|
8
18
|
from localstack.services.stepfunctions.asl.component.common.query_language import QueryLanguage
|
|
9
19
|
from localstack.services.stepfunctions.asl.component.common.result_selector import ResultSelector
|
|
@@ -11,13 +21,43 @@ from localstack.services.stepfunctions.asl.component.state.state import CommonSt
|
|
|
11
21
|
from localstack.services.stepfunctions.asl.component.state.state_choice.state_choice import (
|
|
12
22
|
StateChoice,
|
|
13
23
|
)
|
|
14
|
-
from localstack.services.stepfunctions.asl.component.state.state_execution.
|
|
15
|
-
|
|
24
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.max_concurrency import (
|
|
25
|
+
MaxConcurrency,
|
|
26
|
+
MaxConcurrencyJSONata,
|
|
27
|
+
MaxConcurrencyPath,
|
|
28
|
+
)
|
|
29
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.state_map import (
|
|
30
|
+
StateMap,
|
|
16
31
|
)
|
|
32
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.tolerated_failure import (
|
|
33
|
+
ToleratedFailureCountInt,
|
|
34
|
+
ToleratedFailureCountPath,
|
|
35
|
+
ToleratedFailureCountStringJSONata,
|
|
36
|
+
ToleratedFailurePercentage,
|
|
37
|
+
ToleratedFailurePercentagePath,
|
|
38
|
+
ToleratedFailurePercentageStringJSONata,
|
|
39
|
+
)
|
|
40
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.state_task import (
|
|
41
|
+
StateTask,
|
|
42
|
+
)
|
|
43
|
+
from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail
|
|
17
44
|
from localstack.services.stepfunctions.asl.component.state.state_pass.result import Result
|
|
45
|
+
from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass
|
|
46
|
+
from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import (
|
|
47
|
+
StateSucceed,
|
|
48
|
+
)
|
|
18
49
|
from localstack.services.stepfunctions.asl.component.test_state.program.test_state_program import (
|
|
19
50
|
TestStateProgram,
|
|
20
51
|
)
|
|
52
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.common import (
|
|
53
|
+
MockedCommonState,
|
|
54
|
+
)
|
|
55
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.map import (
|
|
56
|
+
MockedStateMap,
|
|
57
|
+
)
|
|
58
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.task import (
|
|
59
|
+
MockedStateTask,
|
|
60
|
+
)
|
|
21
61
|
from localstack.services.stepfunctions.asl.component.test_state.state.test_state_state_props import (
|
|
22
62
|
TestStateStateProps,
|
|
23
63
|
)
|
|
@@ -30,63 +70,96 @@ class InspectionDataKey(enum.Enum):
|
|
|
30
70
|
INPUT = "input"
|
|
31
71
|
AFTER_INPUT_PATH = "afterInputPath"
|
|
32
72
|
AFTER_PARAMETERS = "afterParameters"
|
|
73
|
+
AFTER_ARGUMENTS = "afterArguments"
|
|
33
74
|
RESULT = "result"
|
|
34
75
|
AFTER_RESULT_SELECTOR = "afterResultSelector"
|
|
35
76
|
AFTER_RESULT_PATH = "afterResultPath"
|
|
77
|
+
AFTER_ITEMS_PATH = "afterItemsPath"
|
|
36
78
|
REQUEST = "request"
|
|
37
79
|
RESPONSE = "response"
|
|
38
80
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
method(env, *args, **kwargs)
|
|
43
|
-
env.set_choice_selected(env.next_state_name)
|
|
44
|
-
|
|
45
|
-
return wrapper
|
|
81
|
+
MAX_CONCURRENCY = "maxConcurrency"
|
|
82
|
+
TOLERATED_FAILURE_COUNT = "toleratedFailureCount"
|
|
83
|
+
TOLERATED_FAILURE_PERCENTAGE = "toleratedFailurePercentage"
|
|
46
84
|
|
|
47
85
|
|
|
48
86
|
def _decorated_updates_inspection_data(method, inspection_data_key: InspectionDataKey):
|
|
49
87
|
def wrapper(env: TestStateEnvironment, *args, **kwargs):
|
|
50
88
|
method(env, *args, **kwargs)
|
|
51
|
-
result =
|
|
89
|
+
result = env.stack[-1]
|
|
90
|
+
if not isinstance(result, (int, float)):
|
|
91
|
+
result = to_json_str(result)
|
|
52
92
|
# We know that the enum value used here corresponds to a supported inspection data field by design.
|
|
53
93
|
env.inspection_data[inspection_data_key.value] = result # noqa
|
|
54
94
|
|
|
55
95
|
return wrapper
|
|
56
96
|
|
|
57
97
|
|
|
58
|
-
def _decorate_state_field(state_field: CommonStateField) -> None:
|
|
59
|
-
if isinstance(state_field,
|
|
60
|
-
state_field
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
98
|
+
def _decorate_state_field(state_field: CommonStateField, is_single_state: bool = False) -> None:
|
|
99
|
+
if isinstance(state_field, StateMap):
|
|
100
|
+
MockedStateMap.wrap(state_field, is_single_state)
|
|
101
|
+
elif isinstance(state_field, StateTask):
|
|
102
|
+
MockedStateTask.wrap(state_field, is_single_state)
|
|
103
|
+
elif isinstance(state_field, (StateChoice, StatePass, StateFail, StateSucceed)):
|
|
104
|
+
MockedCommonState.wrap(state_field, is_single_state)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def find_state(state_name: str, states: dict[str, CommonStateField]) -> CommonStateField | None:
|
|
108
|
+
if state_name in states:
|
|
109
|
+
return states[state_name]
|
|
110
|
+
|
|
111
|
+
for state in states.values():
|
|
112
|
+
if isinstance(state, StateMap):
|
|
113
|
+
found_state = find_state(state_name, state.iteration_component._states.states)
|
|
114
|
+
if found_state:
|
|
115
|
+
return found_state
|
|
72
116
|
|
|
73
117
|
|
|
74
118
|
class TestStatePreprocessor(Preprocessor):
|
|
75
|
-
STATE_NAME: Final[str] = "
|
|
119
|
+
STATE_NAME: Final[str] = "StateName"
|
|
120
|
+
_state_name_stack: list[str] = []
|
|
121
|
+
|
|
122
|
+
def to_test_state_program(
|
|
123
|
+
self, tree: ParseTree, state_name: str | None = None
|
|
124
|
+
) -> TestStateProgram:
|
|
125
|
+
if is_production(tree, ASLParser.RULE_state_machine):
|
|
126
|
+
# full definition passed in
|
|
127
|
+
program = self.visitState_machine(ctx=tree)
|
|
128
|
+
state_field = find_state(state_name, program.states.states)
|
|
129
|
+
_decorate_state_field(state_field, False)
|
|
130
|
+
return TestStateProgram(state_field)
|
|
131
|
+
|
|
132
|
+
if is_production(tree, ASLParser.RULE_state_decl_body):
|
|
133
|
+
# single state case
|
|
134
|
+
state_props = self.visitState_decl_body(ctx=tree)
|
|
135
|
+
state_field = self._common_state_field_of(state_props=state_props)
|
|
136
|
+
_decorate_state_field(state_field, True)
|
|
137
|
+
return TestStateProgram(state_field)
|
|
76
138
|
|
|
77
|
-
|
|
139
|
+
return super().visit(tree)
|
|
140
|
+
|
|
141
|
+
def visitState_decl(self, ctx: ASLParser.State_declContext) -> CommonStateField:
|
|
142
|
+
# if we are parsing a full state machine, we need to record the state_name prior to stepping
|
|
143
|
+
# into the state body definition.
|
|
144
|
+
state_name = self._inner_string_of(parser_rule_context=ctx.string_literal())
|
|
145
|
+
self._state_name_stack.append(state_name)
|
|
146
|
+
state_props: TestStateStateProps = self.visit(ctx.state_decl_body())
|
|
147
|
+
state_field = self._common_state_field_of(state_props=state_props)
|
|
148
|
+
return state_field
|
|
149
|
+
|
|
150
|
+
def visitState_decl_body(self, ctx: ASLParser.State_decl_bodyContext) -> TestStateStateProps:
|
|
78
151
|
self._open_query_language_scope(ctx)
|
|
79
152
|
state_props = TestStateStateProps()
|
|
80
|
-
state_props.name =
|
|
153
|
+
state_props.name = (
|
|
154
|
+
self._state_name_stack.pop(-1) if self._state_name_stack else self.STATE_NAME
|
|
155
|
+
)
|
|
81
156
|
for child in ctx.children:
|
|
82
157
|
cmp = self.visit(child)
|
|
83
158
|
state_props.add(cmp)
|
|
84
|
-
state_field = self._common_state_field_of(state_props=state_props)
|
|
85
159
|
if state_props.get(QueryLanguage) is None:
|
|
86
160
|
state_props.add(self._get_current_query_language())
|
|
87
|
-
_decorate_state_field(state_field)
|
|
88
161
|
self._close_query_language_scope()
|
|
89
|
-
return
|
|
162
|
+
return state_props
|
|
90
163
|
|
|
91
164
|
def visitInput_path_decl(self, ctx: ASLParser.Input_path_declContext) -> InputPath:
|
|
92
165
|
input_path: InputPath = super().visitInput_path_decl(ctx=ctx)
|
|
@@ -129,3 +202,121 @@ class TestStatePreprocessor(Preprocessor):
|
|
|
129
202
|
inspection_data_key=InspectionDataKey.RESULT, # noqa
|
|
130
203
|
)
|
|
131
204
|
return result
|
|
205
|
+
|
|
206
|
+
def visitMax_concurrency_int(self, ctx: ASLParser.Max_concurrency_intContext) -> MaxConcurrency:
|
|
207
|
+
max_concurrency: MaxConcurrency = super().visitMax_concurrency_int(ctx)
|
|
208
|
+
max_concurrency._eval_body = _decorated_updates_inspection_data(
|
|
209
|
+
method=max_concurrency._eval_body,
|
|
210
|
+
inspection_data_key=InspectionDataKey.MAX_CONCURRENCY, # noqa
|
|
211
|
+
)
|
|
212
|
+
return max_concurrency
|
|
213
|
+
|
|
214
|
+
def visitMax_concurrency_jsonata(
|
|
215
|
+
self, ctx: ASLParser.Max_concurrency_jsonataContext
|
|
216
|
+
) -> MaxConcurrencyJSONata:
|
|
217
|
+
max_concurrency_jsonata: MaxConcurrencyJSONata = super().visitMax_concurrency_jsonata(ctx)
|
|
218
|
+
max_concurrency_jsonata._eval_body = _decorated_updates_inspection_data(
|
|
219
|
+
method=max_concurrency_jsonata._eval_body,
|
|
220
|
+
inspection_data_key=InspectionDataKey.MAX_CONCURRENCY, # noqa
|
|
221
|
+
)
|
|
222
|
+
return max_concurrency_jsonata
|
|
223
|
+
|
|
224
|
+
def visitMax_concurrency_path(
|
|
225
|
+
self, ctx: ASLParser.Max_concurrency_declContext
|
|
226
|
+
) -> MaxConcurrencyPath:
|
|
227
|
+
max_concurrency_path: MaxConcurrencyPath = super().visitMax_concurrency_path(ctx)
|
|
228
|
+
max_concurrency_path._eval_body = _decorated_updates_inspection_data(
|
|
229
|
+
method=max_concurrency_path._eval_body,
|
|
230
|
+
inspection_data_key=InspectionDataKey.MAX_CONCURRENCY, # noqa
|
|
231
|
+
)
|
|
232
|
+
return max_concurrency_path
|
|
233
|
+
|
|
234
|
+
def visitTolerated_failure_count_int(self, ctx) -> ToleratedFailureCountInt:
|
|
235
|
+
tolerated_failure_count: ToleratedFailureCountInt = (
|
|
236
|
+
super().visitTolerated_failure_count_int(ctx)
|
|
237
|
+
)
|
|
238
|
+
tolerated_failure_count._eval_body = _decorated_updates_inspection_data(
|
|
239
|
+
method=tolerated_failure_count._eval_body,
|
|
240
|
+
inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_COUNT,
|
|
241
|
+
)
|
|
242
|
+
return tolerated_failure_count
|
|
243
|
+
|
|
244
|
+
def visitTolerated_failure_count_path(self, ctx) -> ToleratedFailureCountPath:
|
|
245
|
+
tolerated_failure_count_path: ToleratedFailureCountPath = (
|
|
246
|
+
super().visitTolerated_failure_count_path(ctx)
|
|
247
|
+
)
|
|
248
|
+
tolerated_failure_count_path._eval_body = _decorated_updates_inspection_data(
|
|
249
|
+
method=tolerated_failure_count_path._eval_body,
|
|
250
|
+
inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_COUNT,
|
|
251
|
+
)
|
|
252
|
+
return tolerated_failure_count_path
|
|
253
|
+
|
|
254
|
+
def visitTolerated_failure_count_string_jsonata(
|
|
255
|
+
self, ctx
|
|
256
|
+
) -> ToleratedFailureCountStringJSONata:
|
|
257
|
+
tolerated_failure_count_jsonata: ToleratedFailureCountStringJSONata = (
|
|
258
|
+
super().visitTolerated_failure_count_string_jsonata(ctx)
|
|
259
|
+
)
|
|
260
|
+
tolerated_failure_count_jsonata._eval_body = _decorated_updates_inspection_data(
|
|
261
|
+
method=tolerated_failure_count_jsonata._eval_body,
|
|
262
|
+
inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_COUNT,
|
|
263
|
+
)
|
|
264
|
+
return tolerated_failure_count_jsonata
|
|
265
|
+
|
|
266
|
+
def visitTolerated_failure_percentage_number(self, ctx) -> ToleratedFailurePercentage:
|
|
267
|
+
tolerated_failure_percentage: ToleratedFailurePercentage = (
|
|
268
|
+
super().visitTolerated_failure_percentage_number(ctx)
|
|
269
|
+
)
|
|
270
|
+
tolerated_failure_percentage._eval_body = _decorated_updates_inspection_data(
|
|
271
|
+
method=tolerated_failure_percentage._eval_body,
|
|
272
|
+
inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_PERCENTAGE,
|
|
273
|
+
)
|
|
274
|
+
return tolerated_failure_percentage
|
|
275
|
+
|
|
276
|
+
def visitTolerated_failure_percentage_path(self, ctx) -> ToleratedFailurePercentagePath:
|
|
277
|
+
tolerated_failure_percentage_path: ToleratedFailurePercentagePath = (
|
|
278
|
+
super().visitTolerated_failure_percentage_path(ctx)
|
|
279
|
+
)
|
|
280
|
+
tolerated_failure_percentage_path._eval_body = _decorated_updates_inspection_data(
|
|
281
|
+
method=tolerated_failure_percentage_path._eval_body,
|
|
282
|
+
inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_PERCENTAGE,
|
|
283
|
+
)
|
|
284
|
+
return tolerated_failure_percentage_path
|
|
285
|
+
|
|
286
|
+
def visitTolerated_failure_percentage_string_jsonata(
|
|
287
|
+
self, ctx
|
|
288
|
+
) -> ToleratedFailurePercentageStringJSONata:
|
|
289
|
+
tolerated_failure_percentage_jsonata: ToleratedFailurePercentageStringJSONata = (
|
|
290
|
+
super().visitTolerated_failure_percentage_string_jsonata(ctx)
|
|
291
|
+
)
|
|
292
|
+
tolerated_failure_percentage_jsonata._eval_body = _decorated_updates_inspection_data(
|
|
293
|
+
method=tolerated_failure_percentage_jsonata._eval_body,
|
|
294
|
+
inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_PERCENTAGE,
|
|
295
|
+
)
|
|
296
|
+
return tolerated_failure_percentage_jsonata
|
|
297
|
+
|
|
298
|
+
def visitItems_path_decl(self, ctx) -> ItemsPath:
|
|
299
|
+
items_path: ItemsPath = super().visitItems_path_decl(ctx)
|
|
300
|
+
items_path._eval_body = _decorated_updates_inspection_data(
|
|
301
|
+
method=items_path._eval_body,
|
|
302
|
+
inspection_data_key=InspectionDataKey.AFTER_ITEMS_PATH,
|
|
303
|
+
)
|
|
304
|
+
return items_path
|
|
305
|
+
|
|
306
|
+
def visitArguments_string_jsonata(self, ctx):
|
|
307
|
+
arguments: ArgumentsStringJSONata = super().visitArguments_string_jsonata(ctx)
|
|
308
|
+
arguments._eval_body = _decorated_updates_inspection_data(
|
|
309
|
+
method=arguments._eval_body,
|
|
310
|
+
inspection_data_key=InspectionDataKey.AFTER_ARGUMENTS,
|
|
311
|
+
)
|
|
312
|
+
return arguments
|
|
313
|
+
|
|
314
|
+
def visitArguments_jsonata_template_value_object(self, ctx):
|
|
315
|
+
arguments: ArgumentsJSONataTemplateValueObject = (
|
|
316
|
+
super().visitArguments_jsonata_template_value_object(ctx)
|
|
317
|
+
)
|
|
318
|
+
arguments._eval_body = _decorated_updates_inspection_data(
|
|
319
|
+
method=arguments._eval_body,
|
|
320
|
+
inspection_data_key=InspectionDataKey.AFTER_ARGUMENTS,
|
|
321
|
+
)
|
|
322
|
+
return arguments
|
|
@@ -1,12 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Final
|
|
2
3
|
|
|
4
|
+
# Botocore shape classes to drive validation
|
|
5
|
+
from botocore.model import (
|
|
6
|
+
ListShape,
|
|
7
|
+
MapShape,
|
|
8
|
+
Shape,
|
|
9
|
+
StringShape,
|
|
10
|
+
StructureShape,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from localstack.aws.api.stepfunctions import (
|
|
14
|
+
Definition,
|
|
15
|
+
MockInput,
|
|
16
|
+
MockResponseValidationMode,
|
|
17
|
+
StateName,
|
|
18
|
+
ValidationException,
|
|
19
|
+
)
|
|
3
20
|
from localstack.services.stepfunctions.asl.antlr.runtime.ASLParser import ASLParser
|
|
4
|
-
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
21
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service import (
|
|
22
|
+
StateTaskService,
|
|
23
|
+
)
|
|
24
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service_api_gateway import (
|
|
25
|
+
StateTaskServiceApiGateway,
|
|
8
26
|
)
|
|
9
27
|
from localstack.services.stepfunctions.asl.component.state.state_type import StateType
|
|
28
|
+
from localstack.services.stepfunctions.asl.component.test_state.program.test_state_program import (
|
|
29
|
+
TestStateProgram,
|
|
30
|
+
)
|
|
10
31
|
from localstack.services.stepfunctions.asl.parse.test_state.asl_parser import (
|
|
11
32
|
TestStateAmazonStateLanguageParser,
|
|
12
33
|
)
|
|
@@ -14,6 +35,11 @@ from localstack.services.stepfunctions.asl.static_analyser.static_analyser impor
|
|
|
14
35
|
|
|
15
36
|
|
|
16
37
|
class TestStateStaticAnalyser(StaticAnalyser):
|
|
38
|
+
state_name: StateName | None
|
|
39
|
+
|
|
40
|
+
def __init__(self, state_name: StateName | None = None):
|
|
41
|
+
self.state_name = state_name
|
|
42
|
+
|
|
17
43
|
_SUPPORTED_STATE_TYPES: Final[set[StateType]] = {
|
|
18
44
|
StateType.Task,
|
|
19
45
|
StateType.Pass,
|
|
@@ -21,10 +47,147 @@ class TestStateStaticAnalyser(StaticAnalyser):
|
|
|
21
47
|
StateType.Choice,
|
|
22
48
|
StateType.Succeed,
|
|
23
49
|
StateType.Fail,
|
|
50
|
+
StateType.Map,
|
|
24
51
|
}
|
|
25
52
|
|
|
26
|
-
|
|
27
|
-
|
|
53
|
+
@staticmethod
|
|
54
|
+
def is_state_in_definition(definition: Definition, state_name: StateName) -> bool:
|
|
55
|
+
test_program, _ = TestStateAmazonStateLanguageParser.parse(definition, state_name)
|
|
56
|
+
if not isinstance(test_program, TestStateProgram):
|
|
57
|
+
raise ValueError("expected parsed EvalComponent to be of type TestStateProgram")
|
|
58
|
+
|
|
59
|
+
return test_program.test_state is not None
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def validate_mock(mock_input: MockInput, definition: Definition, state_name: StateName) -> None:
|
|
63
|
+
test_program, _ = TestStateAmazonStateLanguageParser.parse(definition, state_name)
|
|
64
|
+
test_state = test_program.test_state
|
|
65
|
+
|
|
66
|
+
# apigateway:invoke: has no equivalent in the AWS SDK service integration.
|
|
67
|
+
# Hence, the validation against botocore doesn't apply.
|
|
68
|
+
# See the note in https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html
|
|
69
|
+
# TODO do custom validation for apigateway:invoke:
|
|
70
|
+
if isinstance(test_state, StateTaskServiceApiGateway):
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if isinstance(test_state, StateTaskService):
|
|
74
|
+
field_validation_mode = mock_input.get(
|
|
75
|
+
"fieldValidationMode", MockResponseValidationMode.STRICT
|
|
76
|
+
)
|
|
77
|
+
mock_result_raw = mock_input.get("result")
|
|
78
|
+
if mock_result_raw is None:
|
|
79
|
+
return
|
|
80
|
+
try:
|
|
81
|
+
mock_result = json.loads(mock_result_raw)
|
|
82
|
+
except json.JSONDecodeError:
|
|
83
|
+
raise ValidationException("Mocked result must be valid JSON")
|
|
84
|
+
if mock_result is None or field_validation_mode == MockResponseValidationMode.NONE:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
boto_service_name = test_state._get_boto_service_name()
|
|
88
|
+
service_action_name = test_state._get_boto_service_action()
|
|
89
|
+
output_shape = test_state._get_boto_operation_model(
|
|
90
|
+
boto_service_name=boto_service_name, service_action_name=service_action_name
|
|
91
|
+
).output_shape
|
|
92
|
+
|
|
93
|
+
# If the operation has no output, there's nothing to validate
|
|
94
|
+
if output_shape is None:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
def _raise_type_error(expected_type: str, field_name: str) -> None:
|
|
98
|
+
raise ValidationException(
|
|
99
|
+
f"Mock result schema validation error: Field '{field_name}' must be {expected_type}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _validate_value(value: Any, shape: Shape, field_name: str | None = None) -> None:
|
|
103
|
+
# Document type accepts any JSON value
|
|
104
|
+
if shape.type_name == "document":
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
if isinstance(shape, StructureShape):
|
|
108
|
+
if not isinstance(value, dict):
|
|
109
|
+
# this is a defensive check, the mock result is loaded from JSON before, so should always be a dict
|
|
110
|
+
raise ValidationException(
|
|
111
|
+
f"Mock result must be a valid JSON object, but got '{type(value)}' instead"
|
|
112
|
+
)
|
|
113
|
+
# Build a mapping from SFN-normalised member keys -> botocore member shapes
|
|
114
|
+
members = shape.members
|
|
115
|
+
sfn_key_to_member_shape: dict[str, Shape] = {
|
|
116
|
+
StateTaskService._to_sfn_cased(member_key): member_shape
|
|
117
|
+
for member_key, member_shape in members.items()
|
|
118
|
+
}
|
|
119
|
+
if field_validation_mode == MockResponseValidationMode.STRICT:
|
|
120
|
+
# Ensure required members are present, using SFN-normalised keys
|
|
121
|
+
for required_key in shape.required_members:
|
|
122
|
+
sfn_required_key = StateTaskService._to_sfn_cased(required_key)
|
|
123
|
+
if sfn_required_key not in value:
|
|
124
|
+
raise ValidationException(
|
|
125
|
+
f"Mock result schema validation error: Required field '{sfn_required_key}' is missing"
|
|
126
|
+
)
|
|
127
|
+
# Validate present fields (match SFN-normalised keys to member shapes)
|
|
128
|
+
for mock_field_name, mock_field_value in value.items():
|
|
129
|
+
member_shape = sfn_key_to_member_shape.get(mock_field_name)
|
|
130
|
+
if member_shape is None:
|
|
131
|
+
# Fields that are present in mock but are not in the API spec should not raise validation errors - forward compatibility
|
|
132
|
+
continue
|
|
133
|
+
_validate_value(mock_field_value, member_shape, mock_field_name)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
if isinstance(shape, ListShape):
|
|
137
|
+
if not isinstance(value, list):
|
|
138
|
+
_raise_type_error("an array", field_name)
|
|
139
|
+
member_shape = shape.member
|
|
140
|
+
for list_item in value:
|
|
141
|
+
_validate_value(list_item, member_shape, field_name)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
if isinstance(shape, MapShape):
|
|
145
|
+
if not isinstance(value, dict):
|
|
146
|
+
_raise_type_error("an object", field_name)
|
|
147
|
+
value_shape = shape.value
|
|
148
|
+
for _, map_item_value in value.items():
|
|
149
|
+
_validate_value(map_item_value, value_shape, field_name)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
# Primitive shapes and others
|
|
153
|
+
type_name = shape.type_name
|
|
154
|
+
match type_name:
|
|
155
|
+
case "string" | "timestamp":
|
|
156
|
+
if not isinstance(value, str):
|
|
157
|
+
_raise_type_error("a string", field_name)
|
|
158
|
+
# Validate enum if present
|
|
159
|
+
if isinstance(shape, StringShape):
|
|
160
|
+
enum = getattr(shape, "enum", None)
|
|
161
|
+
if enum and value not in enum:
|
|
162
|
+
raise ValidationException(
|
|
163
|
+
f"Mock result schema validation error: Field '{field_name}' is not an expected value"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
case "integer" | "long":
|
|
167
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
168
|
+
_raise_type_error("a number", field_name)
|
|
169
|
+
|
|
170
|
+
case "float" | "double":
|
|
171
|
+
if not (isinstance(value, (int, float)) or isinstance(value, bool)):
|
|
172
|
+
_raise_type_error("a number", field_name)
|
|
173
|
+
|
|
174
|
+
case "boolean":
|
|
175
|
+
if not isinstance(value, bool):
|
|
176
|
+
_raise_type_error("a boolean", field_name)
|
|
177
|
+
|
|
178
|
+
case "blob":
|
|
179
|
+
if not (isinstance(value, (str, bytes))):
|
|
180
|
+
_raise_type_error("a string", field_name)
|
|
181
|
+
|
|
182
|
+
# Perform validation against the output shape
|
|
183
|
+
_validate_value(mock_result, output_shape)
|
|
184
|
+
# Non-service tasks or other cases: nothing to validate
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
def analyse(self, definition: str) -> None:
|
|
188
|
+
_, parser_rule_context = TestStateAmazonStateLanguageParser.parse(
|
|
189
|
+
definition, self.state_name
|
|
190
|
+
)
|
|
28
191
|
self.visit(parser_rule_context)
|
|
29
192
|
|
|
30
193
|
def visitState_type(self, ctx: ASLParser.State_typeContext) -> None:
|
|
@@ -32,18 +195,3 @@ class TestStateStaticAnalyser(StaticAnalyser):
|
|
|
32
195
|
state_type = StateType(state_type_value)
|
|
33
196
|
if state_type not in self._SUPPORTED_STATE_TYPES:
|
|
34
197
|
raise ValueError(f"Unsupported state type for TestState runs '{state_type}'.")
|
|
35
|
-
|
|
36
|
-
def visitResource_decl(self, ctx: ASLParser.Resource_declContext) -> None:
|
|
37
|
-
resource_str: str = ctx.string_literal().getText()[1:-1]
|
|
38
|
-
resource = Resource.from_resource_arn(resource_str)
|
|
39
|
-
|
|
40
|
-
if isinstance(resource, ActivityResource):
|
|
41
|
-
raise ValueError(
|
|
42
|
-
f"ActivityResources are not supported for TestState runs {resource_str}."
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
if isinstance(resource, ServiceResource):
|
|
46
|
-
if resource.condition is not None:
|
|
47
|
-
raise ValueError(
|
|
48
|
-
f"Service integration patterns are not supported for TestState runs {resource_str}."
|
|
49
|
-
)
|
|
@@ -59,7 +59,7 @@ from localstack.services.stepfunctions.backend.state_machine import (
|
|
|
59
59
|
StateMachineInstance,
|
|
60
60
|
StateMachineVersion,
|
|
61
61
|
)
|
|
62
|
-
from localstack.services.stepfunctions.
|
|
62
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockTestCase
|
|
63
63
|
|
|
64
64
|
LOG = logging.getLogger(__name__)
|
|
65
65
|
|
|
@@ -108,7 +108,7 @@ class Execution:
|
|
|
108
108
|
state_machine_version_arn: Final[Arn | None]
|
|
109
109
|
state_machine_alias_arn: Final[Arn | None]
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
local_mock_test_case: Final[LocalMockTestCase | None]
|
|
112
112
|
|
|
113
113
|
start_date: Final[Timestamp]
|
|
114
114
|
input_data: Final[json | None]
|
|
@@ -144,7 +144,7 @@ class Execution:
|
|
|
144
144
|
input_data: json | None = None,
|
|
145
145
|
trace_header: TraceHeader | None = None,
|
|
146
146
|
state_machine_alias_arn: Arn | None = None,
|
|
147
|
-
|
|
147
|
+
local_mock_test_case: LocalMockTestCase | None = None,
|
|
148
148
|
):
|
|
149
149
|
self.name = name
|
|
150
150
|
self.sm_type = sm_type
|
|
@@ -173,7 +173,7 @@ class Execution:
|
|
|
173
173
|
self.error = None
|
|
174
174
|
self.cause = None
|
|
175
175
|
self._activity_store = activity_store
|
|
176
|
-
self.
|
|
176
|
+
self.local_mock_test_case = local_mock_test_case
|
|
177
177
|
|
|
178
178
|
def _get_events_client(self):
|
|
179
179
|
return connect_to(aws_access_key_id=self.account_id, region_name=self.region_name).events
|
|
@@ -304,7 +304,7 @@ class Execution:
|
|
|
304
304
|
exec_comm=self._get_start_execution_worker_comm(),
|
|
305
305
|
cloud_watch_logging_session=self._cloud_watch_logging_session,
|
|
306
306
|
activity_store=self._activity_store,
|
|
307
|
-
|
|
307
|
+
local_mock_test_case=self.local_mock_test_case,
|
|
308
308
|
)
|
|
309
309
|
|
|
310
310
|
def start(self) -> None:
|
|
@@ -391,7 +391,7 @@ class SyncExecution(Execution):
|
|
|
391
391
|
exec_comm=self._get_start_execution_worker_comm(),
|
|
392
392
|
cloud_watch_logging_session=self._cloud_watch_logging_session,
|
|
393
393
|
activity_store=self._activity_store,
|
|
394
|
-
|
|
394
|
+
local_mock_test_case=self.local_mock_test_case,
|
|
395
395
|
)
|
|
396
396
|
|
|
397
397
|
def _get_start_execution_worker_comm(self) -> BaseExecutionWorkerCommunication:
|
|
@@ -29,7 +29,7 @@ from localstack.services.stepfunctions.backend.activity import Activity
|
|
|
29
29
|
from localstack.services.stepfunctions.backend.execution_worker_comm import (
|
|
30
30
|
ExecutionWorkerCommunication,
|
|
31
31
|
)
|
|
32
|
-
from localstack.services.stepfunctions.
|
|
32
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockTestCase
|
|
33
33
|
from localstack.utils.common import TMP_THREADS
|
|
34
34
|
|
|
35
35
|
|
|
@@ -37,7 +37,7 @@ class ExecutionWorker:
|
|
|
37
37
|
_evaluation_details: Final[EvaluationDetails]
|
|
38
38
|
_execution_communication: Final[ExecutionWorkerCommunication]
|
|
39
39
|
_cloud_watch_logging_session: Final[CloudWatchLoggingSession | None]
|
|
40
|
-
|
|
40
|
+
_local_mock_test_case: Final[LocalMockTestCase | None]
|
|
41
41
|
_activity_store: dict[Arn, Activity]
|
|
42
42
|
|
|
43
43
|
env: Environment | None
|
|
@@ -48,12 +48,12 @@ class ExecutionWorker:
|
|
|
48
48
|
exec_comm: ExecutionWorkerCommunication,
|
|
49
49
|
cloud_watch_logging_session: CloudWatchLoggingSession | None,
|
|
50
50
|
activity_store: dict[Arn, Activity],
|
|
51
|
-
|
|
51
|
+
local_mock_test_case: LocalMockTestCase | None = None,
|
|
52
52
|
):
|
|
53
53
|
self._evaluation_details = evaluation_details
|
|
54
54
|
self._execution_communication = exec_comm
|
|
55
55
|
self._cloud_watch_logging_session = cloud_watch_logging_session
|
|
56
|
-
self.
|
|
56
|
+
self._local_mock_test_case = local_mock_test_case
|
|
57
57
|
self._activity_store = activity_store
|
|
58
58
|
self.env = None
|
|
59
59
|
|
|
@@ -82,7 +82,7 @@ class ExecutionWorker:
|
|
|
82
82
|
event_history_context=EventHistoryContext.of_program_start(),
|
|
83
83
|
cloud_watch_logging_session=self._cloud_watch_logging_session,
|
|
84
84
|
activity_store=self._activity_store,
|
|
85
|
-
|
|
85
|
+
local_mock_test_case=self._local_mock_test_case,
|
|
86
86
|
)
|
|
87
87
|
|
|
88
88
|
def _execution_logic(self):
|