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
|
@@ -19,7 +19,9 @@ from localstack.services.stepfunctions.asl.eval.program_state import (
|
|
|
19
19
|
ProgramState,
|
|
20
20
|
)
|
|
21
21
|
from localstack.services.stepfunctions.asl.eval.test_state.program_state import (
|
|
22
|
+
ProgramCaughtError,
|
|
22
23
|
ProgramChoiceSelected,
|
|
24
|
+
ProgramRetriable,
|
|
23
25
|
)
|
|
24
26
|
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
25
27
|
from localstack.services.stepfunctions.backend.activity import Activity
|
|
@@ -31,6 +33,7 @@ from localstack.services.stepfunctions.backend.state_machine import StateMachine
|
|
|
31
33
|
from localstack.services.stepfunctions.backend.test_state.execution_worker import (
|
|
32
34
|
TestStateExecutionWorker,
|
|
33
35
|
)
|
|
36
|
+
from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock
|
|
34
37
|
|
|
35
38
|
LOG = logging.getLogger(__name__)
|
|
36
39
|
|
|
@@ -38,6 +41,8 @@ LOG = logging.getLogger(__name__)
|
|
|
38
41
|
class TestStateExecution(Execution):
|
|
39
42
|
exec_worker: TestStateExecutionWorker | None
|
|
40
43
|
next_state: str | None
|
|
44
|
+
state_name: str | None
|
|
45
|
+
mock: TestStateMock | None
|
|
41
46
|
|
|
42
47
|
class TestCaseExecutionWorkerCommunication(BaseExecutionWorkerCommunication):
|
|
43
48
|
_execution: TestStateExecution
|
|
@@ -48,6 +53,16 @@ class TestStateExecution(Execution):
|
|
|
48
53
|
self.execution.exec_status = ExecutionStatus.SUCCEEDED
|
|
49
54
|
self.execution.output = self.execution.exec_worker.env.states.get_input()
|
|
50
55
|
self.execution.next_state = exit_program_state.next_state_name
|
|
56
|
+
elif isinstance(exit_program_state, ProgramCaughtError):
|
|
57
|
+
self.execution.exec_status = ExecutionStatus.SUCCEEDED
|
|
58
|
+
self.execution.error = exit_program_state.error
|
|
59
|
+
self.execution.cause = exit_program_state.cause
|
|
60
|
+
self.execution.output = self.execution.exec_worker.env.states.get_input()
|
|
61
|
+
self.execution.next_state = exit_program_state.next_state_name
|
|
62
|
+
elif isinstance(exit_program_state, ProgramRetriable):
|
|
63
|
+
self.execution.exec_status = ExecutionStatus.SUCCEEDED
|
|
64
|
+
self.execution.error = exit_program_state.error
|
|
65
|
+
self.execution.cause = exit_program_state.cause
|
|
51
66
|
else:
|
|
52
67
|
self._reflect_execution_status()
|
|
53
68
|
|
|
@@ -61,7 +76,9 @@ class TestStateExecution(Execution):
|
|
|
61
76
|
state_machine: StateMachineInstance,
|
|
62
77
|
start_date: Timestamp,
|
|
63
78
|
activity_store: dict[Arn, Activity],
|
|
79
|
+
state_name: str | None = None,
|
|
64
80
|
input_data: dict | None = None,
|
|
81
|
+
mock: TestStateMock | None = None,
|
|
65
82
|
):
|
|
66
83
|
super().__init__(
|
|
67
84
|
name=name,
|
|
@@ -79,6 +96,8 @@ class TestStateExecution(Execution):
|
|
|
79
96
|
)
|
|
80
97
|
self._execution_terminated_event = threading.Event()
|
|
81
98
|
self.next_state = None
|
|
99
|
+
self.state_name = state_name
|
|
100
|
+
self.mock = mock
|
|
82
101
|
|
|
83
102
|
def _get_start_execution_worker_comm(self) -> BaseExecutionWorkerCommunication:
|
|
84
103
|
return self.TestCaseExecutionWorkerCommunication(self)
|
|
@@ -93,6 +112,8 @@ class TestStateExecution(Execution):
|
|
|
93
112
|
exec_comm=self._get_start_execution_worker_comm(),
|
|
94
113
|
cloud_watch_logging_session=self._cloud_watch_logging_session,
|
|
95
114
|
activity_store=self._activity_store,
|
|
115
|
+
state_name=self.state_name,
|
|
116
|
+
mock=self.mock,
|
|
96
117
|
)
|
|
97
118
|
|
|
98
119
|
def publish_execution_status_change_event(self):
|
|
@@ -117,6 +138,21 @@ class TestStateExecution(Execution):
|
|
|
117
138
|
test_state_output = TestStateOutput(
|
|
118
139
|
status=TestExecutionStatus.SUCCEEDED, nextState=self.next_state, output=output_str
|
|
119
140
|
)
|
|
141
|
+
elif isinstance(exit_program_state, ProgramCaughtError):
|
|
142
|
+
output_str = to_json_str(self.output)
|
|
143
|
+
test_state_output = TestStateOutput(
|
|
144
|
+
status=TestExecutionStatus.CAUGHT_ERROR,
|
|
145
|
+
nextState=self.next_state,
|
|
146
|
+
output=output_str,
|
|
147
|
+
error=exit_program_state.error,
|
|
148
|
+
cause=exit_program_state.cause,
|
|
149
|
+
)
|
|
150
|
+
elif isinstance(exit_program_state, ProgramRetriable):
|
|
151
|
+
test_state_output = TestStateOutput(
|
|
152
|
+
status=TestExecutionStatus.RETRIABLE,
|
|
153
|
+
error=exit_program_state.error,
|
|
154
|
+
cause=exit_program_state.cause,
|
|
155
|
+
)
|
|
120
156
|
else:
|
|
121
157
|
# TODO: handle other statuses
|
|
122
158
|
LOG.warning(
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
from localstack.aws.api.stepfunctions import Arn, StateName
|
|
1
2
|
from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent
|
|
2
3
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
4
|
+
from localstack.services.stepfunctions.asl.eval.evaluation_details import EvaluationDetails
|
|
3
5
|
from localstack.services.stepfunctions.asl.eval.event.event_manager import (
|
|
4
6
|
EventHistoryContext,
|
|
5
7
|
)
|
|
8
|
+
from localstack.services.stepfunctions.asl.eval.event.logging import (
|
|
9
|
+
CloudWatchLoggingSession,
|
|
10
|
+
)
|
|
6
11
|
from localstack.services.stepfunctions.asl.eval.states import (
|
|
7
12
|
ContextObjectData,
|
|
8
13
|
ExecutionData,
|
|
@@ -12,15 +17,41 @@ from localstack.services.stepfunctions.asl.eval.test_state.environment import Te
|
|
|
12
17
|
from localstack.services.stepfunctions.asl.parse.test_state.asl_parser import (
|
|
13
18
|
TestStateAmazonStateLanguageParser,
|
|
14
19
|
)
|
|
20
|
+
from localstack.services.stepfunctions.backend.activity import Activity
|
|
15
21
|
from localstack.services.stepfunctions.backend.execution_worker import SyncExecutionWorker
|
|
22
|
+
from localstack.services.stepfunctions.backend.execution_worker_comm import (
|
|
23
|
+
ExecutionWorkerCommunication,
|
|
24
|
+
)
|
|
25
|
+
from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock
|
|
16
26
|
|
|
17
27
|
|
|
18
28
|
class TestStateExecutionWorker(SyncExecutionWorker):
|
|
19
29
|
env: TestStateEnvironment | None
|
|
30
|
+
state_name: str | None = None
|
|
31
|
+
mock: TestStateMock | None
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
evaluation_details: EvaluationDetails,
|
|
36
|
+
exec_comm: ExecutionWorkerCommunication,
|
|
37
|
+
cloud_watch_logging_session: CloudWatchLoggingSession | None,
|
|
38
|
+
activity_store: dict[Arn, Activity],
|
|
39
|
+
state_name: StateName | None = None,
|
|
40
|
+
mock: TestStateMock | None = None,
|
|
41
|
+
):
|
|
42
|
+
super().__init__(
|
|
43
|
+
evaluation_details,
|
|
44
|
+
exec_comm,
|
|
45
|
+
cloud_watch_logging_session,
|
|
46
|
+
activity_store,
|
|
47
|
+
local_mock_test_case=None, # local mock is only applicable to SFN Local, but not for TestState
|
|
48
|
+
)
|
|
49
|
+
self.state_name = state_name
|
|
50
|
+
self.mock = mock
|
|
20
51
|
|
|
21
52
|
def _get_evaluation_entrypoint(self) -> EvalComponent:
|
|
22
53
|
return TestStateAmazonStateLanguageParser.parse(
|
|
23
|
-
self._evaluation_details.state_machine_details.definition
|
|
54
|
+
self._evaluation_details.state_machine_details.definition, self.state_name
|
|
24
55
|
)[0]
|
|
25
56
|
|
|
26
57
|
def _get_evaluation_environment(self) -> Environment:
|
|
@@ -43,4 +74,5 @@ class TestStateExecutionWorker(SyncExecutionWorker):
|
|
|
43
74
|
event_history_context=EventHistoryContext.of_program_start(),
|
|
44
75
|
cloud_watch_logging_session=self._cloud_watch_logging_session,
|
|
45
76
|
activity_store=self._activity_store,
|
|
77
|
+
mock=self.mock,
|
|
46
78
|
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
from pydantic import (
|
|
6
|
+
ValidationError,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from localstack.aws.api.stepfunctions import (
|
|
10
|
+
HistoryEventType,
|
|
11
|
+
MockInput,
|
|
12
|
+
TaskFailedEventDetails,
|
|
13
|
+
TestStateConfiguration,
|
|
14
|
+
)
|
|
15
|
+
from localstack.services.stepfunctions.asl.component.common.error_name.error_name import ErrorName
|
|
16
|
+
from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import (
|
|
17
|
+
FailureEvent,
|
|
18
|
+
FailureEventException,
|
|
19
|
+
)
|
|
20
|
+
from localstack.services.stepfunctions.asl.component.state.state_type import StateType
|
|
21
|
+
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
22
|
+
from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails
|
|
23
|
+
from localstack.services.stepfunctions.asl.eval.states import (
|
|
24
|
+
ContextObjectData,
|
|
25
|
+
)
|
|
26
|
+
from localstack.services.stepfunctions.test_state.mock_config import (
|
|
27
|
+
TestStateContextObjectValidator,
|
|
28
|
+
TestStateMockedResponse,
|
|
29
|
+
TestStateResponseReturn,
|
|
30
|
+
TestStateResponseThrow,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def eval_mocked_response_throw(env: Environment, mocked_response: TestStateResponseThrow) -> None:
|
|
35
|
+
task_failed_event_details = TaskFailedEventDetails(
|
|
36
|
+
error=mocked_response.error, cause=mocked_response.cause
|
|
37
|
+
)
|
|
38
|
+
error_name = ErrorName(mocked_response.error)
|
|
39
|
+
failure_event = FailureEvent(
|
|
40
|
+
env=env,
|
|
41
|
+
error_name=error_name,
|
|
42
|
+
event_type=HistoryEventType.TaskFailed, # TODO(gregfurman): Should this be state specific?
|
|
43
|
+
event_details=EventDetails(taskFailedEventDetails=task_failed_event_details),
|
|
44
|
+
)
|
|
45
|
+
raise FailureEventException(failure_event=failure_event)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestStateMock:
|
|
49
|
+
_mock_input: MockInput | None
|
|
50
|
+
_state_configuration: TestStateConfiguration | None
|
|
51
|
+
_result_stack: Final[list[TestStateMockedResponse]]
|
|
52
|
+
_context: Final[ContextObjectData | None]
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
mock_input: MockInput | None,
|
|
57
|
+
state_configuration: TestStateConfiguration | None,
|
|
58
|
+
context: str | None,
|
|
59
|
+
):
|
|
60
|
+
self._mock_input = mock_input
|
|
61
|
+
self._state_configuration = state_configuration
|
|
62
|
+
self._result_stack = []
|
|
63
|
+
self._context = None
|
|
64
|
+
|
|
65
|
+
if not mock_input:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
self._context = None if context is None else self.parse_context(context)
|
|
69
|
+
|
|
70
|
+
if mock_result_raw := mock_input.get("result"):
|
|
71
|
+
mock = json.loads(mock_result_raw)
|
|
72
|
+
self._result_stack.append(TestStateResponseReturn(mock))
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
if mock_error_output := mock_input.get("errorOutput"):
|
|
76
|
+
mock = copy.deepcopy(mock_error_output)
|
|
77
|
+
self._result_stack.append(TestStateResponseThrow(**mock))
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
def is_mocked(self):
|
|
81
|
+
if self._mock_input or self._state_configuration:
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
def add_result(self, result: TestStateMockedResponse):
|
|
87
|
+
mock = copy.deepcopy(result)
|
|
88
|
+
self._result_stack.append(mock)
|
|
89
|
+
|
|
90
|
+
def get_next_result(self) -> TestStateMockedResponse:
|
|
91
|
+
if not self._result_stack:
|
|
92
|
+
return None
|
|
93
|
+
return self._result_stack.pop()
|
|
94
|
+
|
|
95
|
+
def get_context(self) -> ContextObjectData | None:
|
|
96
|
+
if self._context is not None:
|
|
97
|
+
return copy.deepcopy(self._context)
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def parse_context(context: str, state_type: StateType = None) -> ContextObjectData:
|
|
102
|
+
"""Parse and validate context JSON string."""
|
|
103
|
+
try:
|
|
104
|
+
validation_result = TestStateContextObjectValidator.model_validate_json(context)
|
|
105
|
+
return validation_result.model_dump(exclude_unset=True, exclude_none=True)
|
|
106
|
+
except ValidationError as e:
|
|
107
|
+
error = e.errors()[0]
|
|
108
|
+
path_str = ".".join(str(x) for x in error["loc"])
|
|
109
|
+
|
|
110
|
+
match error:
|
|
111
|
+
case {"type": "extra_forbidden", "loc": ("Map",)}:
|
|
112
|
+
raise ValueError("'Map' field is not supported when mocking a Context object")
|
|
113
|
+
|
|
114
|
+
case {"type": "extra_forbidden", "loc": (*_, forbidden_key)}:
|
|
115
|
+
raise ValueError(f"Field '{forbidden_key}' is not allowed")
|
|
116
|
+
|
|
117
|
+
case {"type": t} if t in ("string_type", "int_type", "dict_type", "model_type"):
|
|
118
|
+
expected_map = {
|
|
119
|
+
"string_type": "string",
|
|
120
|
+
"int_type": "integer",
|
|
121
|
+
"dict_type": "object",
|
|
122
|
+
"model_type": "object",
|
|
123
|
+
}
|
|
124
|
+
expected = expected_map.get(t, "valid type")
|
|
125
|
+
raise ValueError(f"{path_str} must be a {expected}")
|
|
126
|
+
case _:
|
|
127
|
+
raise ValueError(f"{error['msg']}")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
from typing import Any, Final
|
|
3
3
|
|
|
4
|
-
from localstack.services.stepfunctions.
|
|
4
|
+
from localstack.services.stepfunctions.local_mocking.mock_config_file import (
|
|
5
5
|
RawMockConfig,
|
|
6
6
|
RawResponseModel,
|
|
7
7
|
RawTestCase,
|
|
@@ -9,7 +9,7 @@ from localstack.services.stepfunctions.mocking.mock_config_file import (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class LocalMockedResponse(abc.ABC):
|
|
13
13
|
range_start: Final[int]
|
|
14
14
|
range_end: Final[int]
|
|
15
15
|
|
|
@@ -28,7 +28,7 @@ class MockedResponse(abc.ABC):
|
|
|
28
28
|
self.range_end = range_end
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
class
|
|
31
|
+
class LocalMockedResponseReturn(LocalMockedResponse):
|
|
32
32
|
payload: Final[Any]
|
|
33
33
|
|
|
34
34
|
def __init__(self, range_start: int, range_end: int, payload: Any):
|
|
@@ -36,7 +36,7 @@ class MockedResponseReturn(MockedResponse):
|
|
|
36
36
|
self.payload = payload
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
class
|
|
39
|
+
class LocalMockedResponseThrow(LocalMockedResponse):
|
|
40
40
|
error: Final[str]
|
|
41
41
|
cause: Final[str]
|
|
42
42
|
|
|
@@ -49,10 +49,13 @@ class MockedResponseThrow(MockedResponse):
|
|
|
49
49
|
class StateMockedResponses:
|
|
50
50
|
state_name: Final[str]
|
|
51
51
|
mocked_response_name: Final[str]
|
|
52
|
-
mocked_responses: Final[list[
|
|
52
|
+
mocked_responses: Final[list[LocalMockedResponse]]
|
|
53
53
|
|
|
54
54
|
def __init__(
|
|
55
|
-
self,
|
|
55
|
+
self,
|
|
56
|
+
state_name: str,
|
|
57
|
+
mocked_response_name: str,
|
|
58
|
+
mocked_responses: list[LocalMockedResponse],
|
|
56
59
|
):
|
|
57
60
|
self.state_name = state_name
|
|
58
61
|
self.mocked_response_name = mocked_response_name
|
|
@@ -74,7 +77,7 @@ class StateMockedResponses:
|
|
|
74
77
|
last_range_end = mocked_response.range_end
|
|
75
78
|
|
|
76
79
|
|
|
77
|
-
class
|
|
80
|
+
class LocalMockTestCase:
|
|
78
81
|
state_machine_name: Final[str]
|
|
79
82
|
test_case_name: Final[str]
|
|
80
83
|
state_mocked_responses: Final[dict[str, StateMockedResponses]]
|
|
@@ -127,13 +130,15 @@ def _parse_mocked_response_range(string_definition: str) -> tuple[int, int]:
|
|
|
127
130
|
|
|
128
131
|
def _mocked_response_from_raw(
|
|
129
132
|
raw_response_model_range: str, raw_response_model: RawResponseModel
|
|
130
|
-
) ->
|
|
133
|
+
) -> LocalMockedResponse:
|
|
131
134
|
range_start, range_end = _parse_mocked_response_range(raw_response_model_range)
|
|
132
135
|
if raw_response_model.Return:
|
|
133
136
|
payload = raw_response_model.Return.model_dump()
|
|
134
|
-
return
|
|
137
|
+
return LocalMockedResponseReturn(
|
|
138
|
+
range_start=range_start, range_end=range_end, payload=payload
|
|
139
|
+
)
|
|
135
140
|
throw_definition = raw_response_model.Throw
|
|
136
|
-
return
|
|
141
|
+
return LocalMockedResponseThrow(
|
|
137
142
|
range_start=range_start,
|
|
138
143
|
range_end=range_end,
|
|
139
144
|
error=throw_definition.Error,
|
|
@@ -143,7 +148,7 @@ def _mocked_response_from_raw(
|
|
|
143
148
|
|
|
144
149
|
def _mocked_responses_from_raw(
|
|
145
150
|
mocked_response_name: str, raw_mock_config: RawMockConfig
|
|
146
|
-
) -> list[
|
|
151
|
+
) -> list[LocalMockedResponse]:
|
|
147
152
|
raw_response_models: dict[str, RawResponseModel] | None = raw_mock_config.MockedResponses.get(
|
|
148
153
|
mocked_response_name
|
|
149
154
|
)
|
|
@@ -151,9 +156,9 @@ def _mocked_responses_from_raw(
|
|
|
151
156
|
raise RuntimeError(
|
|
152
157
|
f"No definitions for mocked response '{mocked_response_name}' in the mock configuration file."
|
|
153
158
|
)
|
|
154
|
-
mocked_responses: list[
|
|
159
|
+
mocked_responses: list[LocalMockedResponse] = []
|
|
155
160
|
for raw_response_model_range, raw_response_model in raw_response_models.items():
|
|
156
|
-
mocked_response:
|
|
161
|
+
mocked_response: LocalMockedResponse = _mocked_response_from_raw(
|
|
157
162
|
raw_response_model_range=raw_response_model_range, raw_response_model=raw_response_model
|
|
158
163
|
)
|
|
159
164
|
mocked_responses.append(mocked_response)
|
|
@@ -175,7 +180,7 @@ def _state_mocked_responses_from_raw(
|
|
|
175
180
|
|
|
176
181
|
def _mock_test_case_from_raw(
|
|
177
182
|
state_machine_name: str, test_case_name: str, raw_mock_config: RawMockConfig
|
|
178
|
-
) ->
|
|
183
|
+
) -> LocalMockTestCase:
|
|
179
184
|
state_machine = raw_mock_config.StateMachines.get(state_machine_name)
|
|
180
185
|
if not state_machine:
|
|
181
186
|
raise RuntimeError(
|
|
@@ -195,18 +200,20 @@ def _mock_test_case_from_raw(
|
|
|
195
200
|
raw_mock_config=raw_mock_config,
|
|
196
201
|
)
|
|
197
202
|
state_mocked_responses_list.append(state_mocked_responses)
|
|
198
|
-
return
|
|
203
|
+
return LocalMockTestCase(
|
|
199
204
|
state_machine_name=state_machine_name,
|
|
200
205
|
test_case_name=test_case_name,
|
|
201
206
|
state_mocked_responses_list=state_mocked_responses_list,
|
|
202
207
|
)
|
|
203
208
|
|
|
204
209
|
|
|
205
|
-
def
|
|
210
|
+
def load_local_mock_test_case_for(
|
|
211
|
+
state_machine_name: str, test_case_name: str
|
|
212
|
+
) -> LocalMockTestCase | None:
|
|
206
213
|
raw_mock_config: RawMockConfig | None = _load_sfn_raw_mock_config()
|
|
207
214
|
if raw_mock_config is None:
|
|
208
215
|
return None
|
|
209
|
-
mock_test_case:
|
|
216
|
+
mock_test_case: LocalMockTestCase = _mock_test_case_from_raw(
|
|
210
217
|
state_machine_name=state_machine_name,
|
|
211
218
|
test_case_name=test_case_name,
|
|
212
219
|
raw_mock_config=raw_mock_config,
|
|
@@ -57,13 +57,13 @@ from localstack.aws.api.stepfunctions import (
|
|
|
57
57
|
LongArn,
|
|
58
58
|
MaxConcurrency,
|
|
59
59
|
MissingRequiredParameter,
|
|
60
|
+
MockInput,
|
|
60
61
|
Name,
|
|
61
62
|
PageSize,
|
|
62
63
|
PageToken,
|
|
63
64
|
Publish,
|
|
64
65
|
PublishStateMachineVersionOutput,
|
|
65
66
|
ResourceNotFound,
|
|
66
|
-
RevealSecrets,
|
|
67
67
|
ReverseOrder,
|
|
68
68
|
RevisionId,
|
|
69
69
|
RoutingConfigurationList,
|
|
@@ -89,6 +89,7 @@ from localstack.aws.api.stepfunctions import (
|
|
|
89
89
|
TaskDoesNotExist,
|
|
90
90
|
TaskTimedOut,
|
|
91
91
|
TaskToken,
|
|
92
|
+
TestStateInput,
|
|
92
93
|
TestStateOutput,
|
|
93
94
|
ToleratedFailureCount,
|
|
94
95
|
ToleratedFailurePercentage,
|
|
@@ -150,9 +151,10 @@ from localstack.services.stepfunctions.backend.store import SFNStore, sfn_stores
|
|
|
150
151
|
from localstack.services.stepfunctions.backend.test_state.execution import (
|
|
151
152
|
TestStateExecution,
|
|
152
153
|
)
|
|
153
|
-
from localstack.services.stepfunctions.
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock
|
|
155
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import (
|
|
156
|
+
LocalMockTestCase,
|
|
157
|
+
load_local_mock_test_case_for,
|
|
156
158
|
)
|
|
157
159
|
from localstack.services.stepfunctions.stepfunctions_utils import (
|
|
158
160
|
assert_pagination_parameters_valid,
|
|
@@ -240,6 +242,12 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
240
242
|
f"Invalid Arn: 'Resource type not valid in this context: {lower_resource_type}'"
|
|
241
243
|
)
|
|
242
244
|
|
|
245
|
+
@staticmethod
|
|
246
|
+
def _validate_test_state_mock_input(mock_input: MockInput) -> None:
|
|
247
|
+
if {"result", "errorOutput"} <= mock_input.keys():
|
|
248
|
+
# FIXME create proper error
|
|
249
|
+
raise ValidationException("Cannot define both 'result' and 'errorOutput'")
|
|
250
|
+
|
|
243
251
|
@staticmethod
|
|
244
252
|
def _validate_activity_name(name: str) -> None:
|
|
245
253
|
# The activity name is validated according to the AWS StepFunctions documentation, the name should not contain:
|
|
@@ -772,14 +780,16 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
772
780
|
return state_machine_arn.split("#")[0]
|
|
773
781
|
|
|
774
782
|
@staticmethod
|
|
775
|
-
def
|
|
783
|
+
def _get_local_mock_test_case(
|
|
784
|
+
state_machine_arn: str, state_machine_name: str
|
|
785
|
+
) -> LocalMockTestCase | None:
|
|
776
786
|
"""Extract and load a mock test case from a state machine ARN if present."""
|
|
777
787
|
parts = state_machine_arn.split("#")
|
|
778
788
|
if len(parts) != 2:
|
|
779
789
|
return None
|
|
780
790
|
|
|
781
791
|
mock_test_case_name = parts[1]
|
|
782
|
-
mock_test_case =
|
|
792
|
+
mock_test_case = load_local_mock_test_case_for(
|
|
783
793
|
state_machine_name=state_machine_name, test_case_name=mock_test_case_name
|
|
784
794
|
)
|
|
785
795
|
if mock_test_case is None:
|
|
@@ -856,7 +866,9 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
856
866
|
configuration=state_machine_clone.cloud_watch_logging_configuration,
|
|
857
867
|
)
|
|
858
868
|
|
|
859
|
-
|
|
869
|
+
local_mock_test_case = self._get_local_mock_test_case(
|
|
870
|
+
state_machine_arn, state_machine_clone.name
|
|
871
|
+
)
|
|
860
872
|
|
|
861
873
|
execution = Execution(
|
|
862
874
|
name=exec_name,
|
|
@@ -872,7 +884,7 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
872
884
|
input_data=input_data,
|
|
873
885
|
trace_header=trace_header,
|
|
874
886
|
activity_store=self.get_store(context).activities,
|
|
875
|
-
|
|
887
|
+
local_mock_test_case=local_mock_test_case,
|
|
876
888
|
)
|
|
877
889
|
|
|
878
890
|
store.executions[exec_arn] = execution
|
|
@@ -932,7 +944,9 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
932
944
|
configuration=state_machine_clone.cloud_watch_logging_configuration,
|
|
933
945
|
)
|
|
934
946
|
|
|
935
|
-
|
|
947
|
+
local_mock_test_case = self._get_local_mock_test_case(
|
|
948
|
+
state_machine_arn, state_machine_clone.name
|
|
949
|
+
)
|
|
936
950
|
|
|
937
951
|
execution = SyncExecution(
|
|
938
952
|
name=exec_name,
|
|
@@ -947,7 +961,7 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
947
961
|
input_data=input_data,
|
|
948
962
|
trace_header=trace_header,
|
|
949
963
|
activity_store=self.get_store(context).activities,
|
|
950
|
-
|
|
964
|
+
local_mock_test_case=local_mock_test_case,
|
|
951
965
|
)
|
|
952
966
|
self.get_store(context).executions[exec_arn] = execution
|
|
953
967
|
|
|
@@ -1483,20 +1497,48 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
1483
1497
|
raise ResourceNotFound()
|
|
1484
1498
|
|
|
1485
1499
|
def test_state(
|
|
1486
|
-
self,
|
|
1487
|
-
context: RequestContext,
|
|
1488
|
-
definition: Definition,
|
|
1489
|
-
role_arn: Arn = None,
|
|
1490
|
-
input: SensitiveData = None,
|
|
1491
|
-
inspection_level: InspectionLevel = None,
|
|
1492
|
-
reveal_secrets: RevealSecrets = None,
|
|
1493
|
-
variables: SensitiveData = None,
|
|
1494
|
-
**kwargs,
|
|
1500
|
+
self, context: RequestContext, request: TestStateInput, **kwargs
|
|
1495
1501
|
) -> TestStateOutput:
|
|
1502
|
+
state_name = request.get("stateName")
|
|
1503
|
+
definition = request["definition"]
|
|
1504
|
+
|
|
1496
1505
|
StepFunctionsProvider._validate_definition(
|
|
1497
|
-
definition=definition,
|
|
1506
|
+
definition=definition,
|
|
1507
|
+
static_analysers=[TestStateStaticAnalyser(state_name)],
|
|
1498
1508
|
)
|
|
1499
1509
|
|
|
1510
|
+
# if StateName is present, we need to ensure the state being referenced exists in full definition.
|
|
1511
|
+
if state_name and not TestStateStaticAnalyser.is_state_in_definition(
|
|
1512
|
+
definition=definition, state_name=state_name
|
|
1513
|
+
):
|
|
1514
|
+
raise ValidationException("State not found in definition")
|
|
1515
|
+
|
|
1516
|
+
mock_input = request.get("mock")
|
|
1517
|
+
if mock_input is not None:
|
|
1518
|
+
self._validate_test_state_mock_input(mock_input)
|
|
1519
|
+
TestStateStaticAnalyser.validate_mock(
|
|
1520
|
+
mock_input=mock_input, definition=definition, state_name=state_name
|
|
1521
|
+
)
|
|
1522
|
+
|
|
1523
|
+
if state_configuration := request.get("stateConfiguration"):
|
|
1524
|
+
# TODO: Add validations for this i.e assert len(input) <= failureCount
|
|
1525
|
+
pass
|
|
1526
|
+
|
|
1527
|
+
if state_context := request.get("context"):
|
|
1528
|
+
# TODO: Add validation ensuring only present if 'mock' is specified
|
|
1529
|
+
# An error occurred (ValidationException) when calling the TestState operation: State type 'Pass' is not supported when a mock is specified
|
|
1530
|
+
pass
|
|
1531
|
+
|
|
1532
|
+
try:
|
|
1533
|
+
state_mock = TestStateMock(
|
|
1534
|
+
mock_input=mock_input,
|
|
1535
|
+
state_configuration=state_configuration,
|
|
1536
|
+
context=state_context,
|
|
1537
|
+
)
|
|
1538
|
+
except ValueError as e:
|
|
1539
|
+
LOG.error(e)
|
|
1540
|
+
raise ValidationException(f"Invalid Context object provided: {e}")
|
|
1541
|
+
|
|
1500
1542
|
name: Name | None = f"TestState-{short_uid()}"
|
|
1501
1543
|
arn = stepfunctions_state_machine_arn(
|
|
1502
1544
|
name=name, account_id=context.account_id, region_name=context.region
|
|
@@ -1504,27 +1546,36 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
1504
1546
|
state_machine = TestStateMachine(
|
|
1505
1547
|
name=name,
|
|
1506
1548
|
arn=arn,
|
|
1507
|
-
role_arn=
|
|
1508
|
-
definition=definition,
|
|
1549
|
+
role_arn=request["roleArn"],
|
|
1550
|
+
definition=request["definition"],
|
|
1509
1551
|
)
|
|
1510
|
-
exec_arn = stepfunctions_standard_execution_arn(state_machine.arn, name)
|
|
1511
1552
|
|
|
1512
|
-
|
|
1553
|
+
# HACK(gregfurman): The ARN that gets generated has a duplicate 'name' field in the
|
|
1554
|
+
# resource ARN. Just replace this duplication and extract the execution ID.
|
|
1555
|
+
exec_arn = stepfunctions_express_execution_arn(state_machine.arn, name)
|
|
1556
|
+
exec_arn = exec_arn.replace(f":{name}:{name}:", f":{name}:", 1)
|
|
1557
|
+
_, exec_name = exec_arn.rsplit(":", 1)
|
|
1558
|
+
|
|
1559
|
+
if input_json := request.get("input", {}):
|
|
1560
|
+
input_json = json.loads(input_json)
|
|
1561
|
+
|
|
1513
1562
|
execution = TestStateExecution(
|
|
1514
|
-
name=
|
|
1515
|
-
role_arn=
|
|
1563
|
+
name=exec_name,
|
|
1564
|
+
role_arn=request["roleArn"],
|
|
1516
1565
|
exec_arn=exec_arn,
|
|
1517
1566
|
account_id=context.account_id,
|
|
1518
1567
|
region_name=context.region,
|
|
1519
1568
|
state_machine=state_machine,
|
|
1520
1569
|
start_date=datetime.datetime.now(tz=datetime.UTC),
|
|
1521
1570
|
input_data=input_json,
|
|
1571
|
+
state_name=state_name,
|
|
1522
1572
|
activity_store=self.get_store(context).activities,
|
|
1573
|
+
mock=state_mock,
|
|
1523
1574
|
)
|
|
1524
1575
|
execution.start()
|
|
1525
1576
|
|
|
1526
1577
|
test_state_output = execution.to_test_state_output(
|
|
1527
|
-
inspection_level=
|
|
1578
|
+
inspection_level=request.get("inspectionLevel", InspectionLevel.INFO)
|
|
1528
1579
|
)
|
|
1529
1580
|
|
|
1530
1581
|
return test_state_output
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Any, Final
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr, create_model
|
|
5
|
+
|
|
6
|
+
from localstack.services.stepfunctions.asl.eval.states import (
|
|
7
|
+
ExecutionData,
|
|
8
|
+
StateData,
|
|
9
|
+
StateMachineData,
|
|
10
|
+
TaskData,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestStateMockedResponse(abc.ABC):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestStateResponseReturn(TestStateMockedResponse):
|
|
19
|
+
payload: Final[Any]
|
|
20
|
+
|
|
21
|
+
def __init__(self, payload: Any):
|
|
22
|
+
self.payload = payload
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestStateResponseThrow(TestStateMockedResponse):
|
|
26
|
+
error: Final[str]
|
|
27
|
+
cause: Final[str]
|
|
28
|
+
|
|
29
|
+
def __init__(self, error: str, cause: str):
|
|
30
|
+
self.error = error
|
|
31
|
+
self.cause = cause
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _to_strict_model(name: str, source: type):
|
|
35
|
+
type_map = {str: StrictStr, int: StrictInt}
|
|
36
|
+
fields = {k: (type_map.get(v, v) | None, None) for k, v in source.__annotations__.items()}
|
|
37
|
+
return create_model(name, __config__=ConfigDict(extra="forbid"), **fields)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
TestStateContextObjectValidator: Final[type[BaseModel]] = create_model(
|
|
41
|
+
"ContextValidator",
|
|
42
|
+
__config__=ConfigDict(extra="forbid"),
|
|
43
|
+
Execution=(_to_strict_model("Execution", ExecutionData) | None, None),
|
|
44
|
+
State=(_to_strict_model("State", StateData) | None, None),
|
|
45
|
+
StateMachine=(_to_strict_model("StateMachine", StateMachineData) | None, None),
|
|
46
|
+
Task=(_to_strict_model("Task", TaskData) | None, None),
|
|
47
|
+
)
|