localstack-core 4.10.1.dev42__py3-none-any.whl → 4.11.2.dev14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localstack/aws/api/apigateway/__init__.py +42 -0
- localstack/aws/api/cloudformation/__init__.py +161 -0
- localstack/aws/api/ec2/__init__.py +1165 -12
- localstack/aws/api/iam/__init__.py +227 -0
- localstack/aws/api/kms/__init__.py +1 -0
- localstack/aws/api/lambda_/__init__.py +418 -66
- localstack/aws/api/logs/__init__.py +312 -0
- localstack/aws/api/opensearch/__init__.py +89 -0
- localstack/aws/api/redshift/__init__.py +69 -0
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +36 -0
- localstack/aws/api/route53/__init__.py +42 -0
- localstack/aws/api/route53resolver/__init__.py +1 -0
- localstack/aws/api/s3/__init__.py +62 -0
- localstack/aws/api/secretsmanager/__init__.py +28 -23
- localstack/aws/api/stepfunctions/__init__.py +52 -10
- localstack/aws/api/sts/__init__.py +52 -0
- localstack/aws/handlers/logging.py +8 -4
- localstack/aws/handlers/service.py +11 -2
- localstack/aws/protocol/serializer.py +1 -1
- localstack/deprecations.py +0 -6
- localstack/services/acm/provider.py +4 -0
- 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_preproc.py +12 -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/kms/models.py +10 -20
- localstack/services/kms/provider.py +4 -0
- 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/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_/provider.py +239 -95
- 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/provider.py +53 -3
- localstack/services/resource_groups/provider.py +5 -1
- localstack/services/resourcegroupstaggingapi/provider.py +6 -1
- localstack/services/s3/provider.py +28 -15
- 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 +6 -0
- localstack/services/sns/v2/provider.py +650 -19
- localstack/services/sns/v2/utils.py +12 -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 +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 +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/batching.py +258 -0
- localstack/utils/collections.py +23 -11
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/METADATA +5 -5
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/RECORD +113 -105
- 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.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.11.2.dev14.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.11.2.dev14.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.11.2.dev14.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,7 @@ from botocore.utils import InvalidArnException
|
|
|
6
6
|
|
|
7
7
|
from localstack.aws.api.sns import InvalidParameterException
|
|
8
8
|
from localstack.services.sns.constants import E164_REGEX, VALID_SUBSCRIPTION_ATTR_NAME
|
|
9
|
+
from localstack.services.sns.v2.models import SnsStore, SnsSubscription
|
|
9
10
|
from localstack.utils.aws.arns import ArnData, parse_arn
|
|
10
11
|
from localstack.utils.strings import short_uid, to_bytes, to_str
|
|
11
12
|
|
|
@@ -136,3 +137,14 @@ def get_region_from_subscription_token(token: str) -> str:
|
|
|
136
137
|
return bytes.fromhex(region).decode("utf-8")
|
|
137
138
|
except (IndexError, ValueError, TypeError, UnicodeDecodeError):
|
|
138
139
|
raise InvalidParameterException("Invalid parameter: Token")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_topic_subscriptions(store: SnsStore, topic_arn: str) -> list[SnsSubscription]:
|
|
143
|
+
# TODO: delete this once the legacy v1 implementation has been removed
|
|
144
|
+
if hasattr(store, "topic_subscriptions"):
|
|
145
|
+
sub_arns = store.topic_subscriptions.get(topic_arn, [])
|
|
146
|
+
else:
|
|
147
|
+
sub_arns: list[str] = store.topics[topic_arn].get("subscriptions", [])
|
|
148
|
+
|
|
149
|
+
subscriptions = [store.subscriptions[k] for k in sub_arns if k in store.subscriptions]
|
|
150
|
+
return subscriptions
|
|
@@ -18,7 +18,7 @@ class ResultPath(EvalComponent):
|
|
|
18
18
|
def _eval_body(self, env: Environment) -> None:
|
|
19
19
|
state_input = env.states.get_input()
|
|
20
20
|
|
|
21
|
-
# Discard task output if there is one, and set the output
|
|
21
|
+
# Discard task output if there is one, and set the output to be the state's input.
|
|
22
22
|
if self.result_path_src is None:
|
|
23
23
|
env.stack.clear()
|
|
24
24
|
env.stack.append(state_input)
|
|
@@ -251,7 +251,6 @@ class ExecutionState(CommonStateField, abc.ABC):
|
|
|
251
251
|
)
|
|
252
252
|
error_output = self._construct_error_output_value(failure_event=failure_event)
|
|
253
253
|
env.states.set_error_output(error_output)
|
|
254
|
-
env.states.set_result(error_output)
|
|
255
254
|
|
|
256
255
|
if self.retry:
|
|
257
256
|
retry_outcome: RetryOutcome = self._handle_retry(
|
localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py
CHANGED
|
@@ -311,7 +311,6 @@ class StateMap(ExecutionState):
|
|
|
311
311
|
failure_event: FailureEvent = self._from_error(env=env, ex=ex)
|
|
312
312
|
error_output = self._construct_error_output_value(failure_event=failure_event)
|
|
313
313
|
env.states.set_error_output(error_output)
|
|
314
|
-
env.states.set_result(error_output)
|
|
315
314
|
|
|
316
315
|
if self.retry:
|
|
317
316
|
retry_outcome: RetryOutcome = self._handle_retry(
|
|
@@ -6,13 +6,13 @@ from localstack.aws.api.lambda_ import InvocationResponse
|
|
|
6
6
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import (
|
|
7
7
|
StateCredentials,
|
|
8
8
|
)
|
|
9
|
-
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.
|
|
10
|
-
|
|
9
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.local_mock_eval_utils import (
|
|
10
|
+
eval_local_mocked_response,
|
|
11
11
|
)
|
|
12
12
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
13
13
|
from localstack.services.stepfunctions.asl.utils.boto_client import boto_client_for
|
|
14
14
|
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
15
|
-
from localstack.services.stepfunctions.
|
|
15
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockedResponse
|
|
16
16
|
from localstack.utils.collections import select_from_typed_dict
|
|
17
17
|
from localstack.utils.strings import to_bytes
|
|
18
18
|
|
|
@@ -42,9 +42,9 @@ def _from_payload(payload_streaming_body: IO[bytes]) -> Any | str:
|
|
|
42
42
|
return decoded_data
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def
|
|
46
|
-
mocked_response:
|
|
47
|
-
|
|
45
|
+
def _local_mocked_invoke_lambda_function(env: Environment) -> InvocationResponse:
|
|
46
|
+
mocked_response: LocalMockedResponse = env.get_current_local_mocked_response()
|
|
47
|
+
eval_local_mocked_response(env=env, mocked_response=mocked_response)
|
|
48
48
|
invocation_resp: InvocationResponse = env.stack.pop()
|
|
49
49
|
return invocation_resp
|
|
50
50
|
|
|
@@ -68,8 +68,8 @@ def _invoke_lambda_function(
|
|
|
68
68
|
def execute_lambda_function_integration(
|
|
69
69
|
env: Environment, parameters: dict, region: str, state_credentials: StateCredentials
|
|
70
70
|
) -> None:
|
|
71
|
-
if env.
|
|
72
|
-
invocation_response: InvocationResponse =
|
|
71
|
+
if env.is_local_mocked_mode():
|
|
72
|
+
invocation_response: InvocationResponse = _local_mocked_invoke_lambda_function(env=env)
|
|
73
73
|
else:
|
|
74
74
|
invocation_response: InvocationResponse = _invoke_lambda_function(
|
|
75
75
|
parameters=parameters, region=region, state_credentials=state_credentials
|
|
@@ -10,14 +10,16 @@ from localstack.services.stepfunctions.asl.component.common.error_name.failure_e
|
|
|
10
10
|
)
|
|
11
11
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
12
12
|
from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails
|
|
13
|
-
from localstack.services.stepfunctions.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import (
|
|
14
|
+
LocalMockedResponse,
|
|
15
|
+
LocalMockedResponseReturn,
|
|
16
|
+
LocalMockedResponseThrow,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def _eval_mocked_response_throw(
|
|
20
|
+
def _eval_mocked_response_throw(
|
|
21
|
+
env: Environment, mocked_response: LocalMockedResponseThrow
|
|
22
|
+
) -> None:
|
|
21
23
|
task_failed_event_details = TaskFailedEventDetails(
|
|
22
24
|
error=mocked_response.error, cause=mocked_response.cause
|
|
23
25
|
)
|
|
@@ -31,15 +33,17 @@ def _eval_mocked_response_throw(env: Environment, mocked_response: MockedRespons
|
|
|
31
33
|
raise FailureEventException(failure_event=failure_event)
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
def _eval_mocked_response_return(
|
|
36
|
+
def _eval_mocked_response_return(
|
|
37
|
+
env: Environment, mocked_response: LocalMockedResponseReturn
|
|
38
|
+
) -> None:
|
|
35
39
|
payload_copy = copy.deepcopy(mocked_response.payload)
|
|
36
40
|
env.stack.append(payload_copy)
|
|
37
41
|
|
|
38
42
|
|
|
39
|
-
def
|
|
40
|
-
if isinstance(mocked_response,
|
|
43
|
+
def eval_local_mocked_response(env: Environment, mocked_response: LocalMockedResponse) -> None:
|
|
44
|
+
if isinstance(mocked_response, LocalMockedResponseReturn):
|
|
41
45
|
_eval_mocked_response_return(env=env, mocked_response=mocked_response)
|
|
42
|
-
elif isinstance(mocked_response,
|
|
46
|
+
elif isinstance(mocked_response, LocalMockedResponseThrow):
|
|
43
47
|
_eval_mocked_response_throw(env=env, mocked_response=mocked_response)
|
|
44
48
|
else:
|
|
45
49
|
raise RuntimeError(f"Invalid MockedResponse type '{type(mocked_response)}'")
|
|
@@ -33,8 +33,8 @@ from localstack.services.stepfunctions.asl.component.common.error_name.states_er
|
|
|
33
33
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import (
|
|
34
34
|
StateCredentials,
|
|
35
35
|
)
|
|
36
|
-
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.
|
|
37
|
-
|
|
36
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.local_mock_eval_utils import (
|
|
37
|
+
eval_local_mocked_response,
|
|
38
38
|
)
|
|
39
39
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.resource import (
|
|
40
40
|
ResourceRuntimePart,
|
|
@@ -47,7 +47,7 @@ from localstack.services.stepfunctions.asl.component.state.state_props import St
|
|
|
47
47
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
48
48
|
from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails
|
|
49
49
|
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
50
|
-
from localstack.services.stepfunctions.
|
|
50
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockedResponse
|
|
51
51
|
from localstack.services.stepfunctions.quotas import is_within_size_quota
|
|
52
52
|
from localstack.utils.strings import camel_to_snake_case, snake_to_camel_case, to_bytes, to_str
|
|
53
53
|
|
|
@@ -356,9 +356,9 @@ class StateTaskService(StateTask, abc.ABC):
|
|
|
356
356
|
normalised_parameters = copy.deepcopy(raw_parameters)
|
|
357
357
|
self._normalise_parameters(normalised_parameters)
|
|
358
358
|
|
|
359
|
-
if env.
|
|
360
|
-
mocked_response:
|
|
361
|
-
|
|
359
|
+
if env.is_local_mocked_mode():
|
|
360
|
+
mocked_response: LocalMockedResponse = env.get_current_local_mocked_response()
|
|
361
|
+
eval_local_mocked_response(env=env, mocked_response=mocked_response)
|
|
362
362
|
else:
|
|
363
363
|
self._eval_service_task(
|
|
364
364
|
env=env,
|
|
@@ -346,7 +346,7 @@ class StateTaskServiceCallback(StateTaskService, abc.ABC):
|
|
|
346
346
|
)
|
|
347
347
|
),
|
|
348
348
|
)
|
|
349
|
-
if not env.
|
|
349
|
+
if not env.is_local_mocked_mode() and not env.is_test_state_mocked_mode():
|
|
350
350
|
self._eval_integration_pattern(
|
|
351
351
|
env=env,
|
|
352
352
|
resource_runtime_part=resource_runtime_part,
|
|
@@ -7,6 +7,9 @@ from localstack.services.stepfunctions.asl.component.common.error_name.failure_e
|
|
|
7
7
|
FailureEventException,
|
|
8
8
|
)
|
|
9
9
|
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.state.state_continue_with import (
|
|
11
|
+
ContinueWithEnd,
|
|
12
|
+
)
|
|
10
13
|
from localstack.services.stepfunctions.asl.component.state.state_fail.cause_decl import CauseDecl
|
|
11
14
|
from localstack.services.stepfunctions.asl.component.state.state_fail.error_decl import ErrorDecl
|
|
12
15
|
from localstack.services.stepfunctions.asl.component.state.state_props import StateProps
|
|
@@ -27,6 +30,7 @@ class StateFail(CommonStateField):
|
|
|
27
30
|
super().from_state_props(state_props)
|
|
28
31
|
self.cause = state_props.get(CauseDecl)
|
|
29
32
|
self.error = state_props.get(ErrorDecl)
|
|
33
|
+
self.continue_with = ContinueWithEnd()
|
|
30
34
|
|
|
31
35
|
def _eval_state(self, env: Environment) -> None:
|
|
32
36
|
task_failed_event_details = TaskFailedEventDetails()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import copy
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent
|
|
6
|
+
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
7
|
+
from localstack.services.stepfunctions.asl.component.state.state_continue_with import (
|
|
8
|
+
ContinueWithNext,
|
|
9
|
+
)
|
|
10
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
11
|
+
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
12
|
+
from localstack.services.stepfunctions.backend.test_state.test_state_mock import (
|
|
13
|
+
TestStateResponseReturn,
|
|
14
|
+
TestStateResponseThrow,
|
|
15
|
+
eval_mocked_response_throw,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T", bound=CommonStateField)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MockedBaseState(Generic[T], abc.ABC):
|
|
22
|
+
is_single_state: bool
|
|
23
|
+
_wrapped: T
|
|
24
|
+
|
|
25
|
+
def __init__(self, wrapped: T):
|
|
26
|
+
super().__init__()
|
|
27
|
+
self._wrapped = wrapped
|
|
28
|
+
self.apply_patches()
|
|
29
|
+
|
|
30
|
+
def apply_patches(self):
|
|
31
|
+
self._apply_patches()
|
|
32
|
+
|
|
33
|
+
original_eval_body = self._wrapped._eval_body
|
|
34
|
+
self._wrapped._eval_body = self.wrap_with_post_return(
|
|
35
|
+
original_eval_body, self.stop_execution
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def _apply_patches(self): ...
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def wrap(cls, state: T, is_single_state: bool = False) -> T:
|
|
43
|
+
cls.is_single_state = is_single_state
|
|
44
|
+
cls._wrapped = state
|
|
45
|
+
return cls(state)._wrapped
|
|
46
|
+
|
|
47
|
+
def __getattr__(self, attr: str):
|
|
48
|
+
return getattr(self._wrapped, attr)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def before_mock(self, env: TestStateEnvironment):
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def do_mock(self, env: TestStateEnvironment):
|
|
56
|
+
mocked_response = env.mock.get_next_result()
|
|
57
|
+
if not mocked_response:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
if isinstance(mocked_response, TestStateResponseThrow):
|
|
61
|
+
eval_mocked_response_throw(env, mocked_response)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
if isinstance(mocked_response, TestStateResponseReturn):
|
|
65
|
+
result_copy = copy.deepcopy(mocked_response.payload)
|
|
66
|
+
env.stack.append(result_copy)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def after_mock(self, env: TestStateEnvironment):
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def wrap_with_mock(cls, original_method):
|
|
74
|
+
def wrapper(env: TestStateEnvironment, *args, **kwargs):
|
|
75
|
+
if not env.mock.is_mocked():
|
|
76
|
+
original_method(env, *args, **kwargs)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
cls.before_mock(env)
|
|
80
|
+
try:
|
|
81
|
+
cls.do_mock(env)
|
|
82
|
+
finally:
|
|
83
|
+
cls.after_mock(env)
|
|
84
|
+
|
|
85
|
+
return wrapper
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def wrap_with_post_return(method, post_return_fn):
|
|
89
|
+
def wrapper(env: TestStateEnvironment, *args, **kwargs):
|
|
90
|
+
try:
|
|
91
|
+
method(env, *args, **kwargs)
|
|
92
|
+
finally:
|
|
93
|
+
post_return_fn(env)
|
|
94
|
+
|
|
95
|
+
return wrapper
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _eval_with_inspect(component: EvalComponent, key: str):
|
|
99
|
+
if not component:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
eval_body_fn = component._eval_body
|
|
103
|
+
|
|
104
|
+
def _update(env: TestStateEnvironment, *args, **kwargs):
|
|
105
|
+
# if inspectionData already populated, don't execute again
|
|
106
|
+
if key in env.inspection_data:
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
eval_body_fn(env, *args, **kwargs)
|
|
110
|
+
result = env.stack[-1]
|
|
111
|
+
env.inspection_data[key] = to_json_str(result)
|
|
112
|
+
|
|
113
|
+
component._eval_body = MockedBaseState.wrap_with_post_return(eval_body_fn, _update)
|
|
114
|
+
|
|
115
|
+
def stop_execution(self, env: TestStateEnvironment):
|
|
116
|
+
if isinstance(self._wrapped.continue_with, ContinueWithNext):
|
|
117
|
+
if next_state := self._wrapped.continue_with.next_state:
|
|
118
|
+
env.set_choice_selected(next_state.name)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
2
|
+
from localstack.services.stepfunctions.asl.component.state.state_choice.state_choice import (
|
|
3
|
+
StateChoice,
|
|
4
|
+
)
|
|
5
|
+
from localstack.services.stepfunctions.asl.component.state.state_continue_with import (
|
|
6
|
+
ContinueWithEnd,
|
|
7
|
+
)
|
|
8
|
+
from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail
|
|
9
|
+
from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import (
|
|
11
|
+
StateSucceed,
|
|
12
|
+
)
|
|
13
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import (
|
|
14
|
+
MockedBaseState,
|
|
15
|
+
)
|
|
16
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MockedCommonState(MockedBaseState[CommonStateField]):
|
|
20
|
+
def add_inspection_data(self, env: TestStateEnvironment):
|
|
21
|
+
state = self._wrapped
|
|
22
|
+
|
|
23
|
+
if not isinstance(state, StatePass):
|
|
24
|
+
if not self.is_single_state:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
if "afterInputPath" not in env.inspection_data:
|
|
28
|
+
env.inspection_data["afterInputPath"] = env.states.get_input()
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# If not a terminal state, only populate inspection data from pre-processor.
|
|
32
|
+
if not isinstance(self._wrapped.continue_with, ContinueWithEnd):
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if state.result:
|
|
36
|
+
# TODO: investigate interactions between these inspectionData field types.
|
|
37
|
+
# i.e parity tests shows that if "Result" is defined, 'afterInputPath' and 'afterParameters'
|
|
38
|
+
# cannot be present in the inspection data.
|
|
39
|
+
env.inspection_data.pop("afterInputPath", None)
|
|
40
|
+
env.inspection_data.pop("afterParameters", None)
|
|
41
|
+
|
|
42
|
+
if "afterResultSelector" not in env.inspection_data:
|
|
43
|
+
env.inspection_data["afterResultSelector"] = state.result.result_obj
|
|
44
|
+
|
|
45
|
+
if "afterResultPath" not in env.inspection_data:
|
|
46
|
+
env.inspection_data["afterResultPath"] = env.inspection_data.get(
|
|
47
|
+
"afterResultSelector", env.states.get_input()
|
|
48
|
+
)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
if "afterInputPath" not in env.inspection_data:
|
|
52
|
+
env.inspection_data["afterInputPath"] = env.states.get_input()
|
|
53
|
+
|
|
54
|
+
if "afterParameters" not in env.inspection_data:
|
|
55
|
+
env.inspection_data["afterParameters"] = env.inspection_data.get(
|
|
56
|
+
"afterInputPath", env.states.get_input()
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if "afterResultSelector" not in env.inspection_data:
|
|
60
|
+
env.inspection_data["afterResultSelector"] = env.inspection_data["afterParameters"]
|
|
61
|
+
|
|
62
|
+
if "afterResultPath" not in env.inspection_data:
|
|
63
|
+
env.inspection_data["afterResultPath"] = env.inspection_data.get(
|
|
64
|
+
"afterResultSelector", env.states.get_input()
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def _apply_patches(self):
|
|
68
|
+
if not isinstance(self._wrapped, (StatePass, StateFail, StateChoice, StateSucceed)):
|
|
69
|
+
raise ValueError("Needs to be a Pass, Fail, Choice, or Succeed state.")
|
|
70
|
+
|
|
71
|
+
original_eval_body = self.wrap_with_mock(self._wrapped._eval_body)
|
|
72
|
+
|
|
73
|
+
def mock_eval_execution(env: TestStateEnvironment):
|
|
74
|
+
original_eval_body(env)
|
|
75
|
+
env.set_choice_selected(env.next_state_name)
|
|
76
|
+
|
|
77
|
+
mock_eval_execution = self.wrap_with_post_return(
|
|
78
|
+
method=mock_eval_execution,
|
|
79
|
+
post_return_fn=self.add_inspection_data,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self._wrapped._eval_body = mock_eval_execution
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
from localstack.services.stepfunctions.asl.component.common.catch.catcher_outcome import (
|
|
5
|
+
CatcherOutcomeCaught,
|
|
6
|
+
)
|
|
7
|
+
from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import (
|
|
8
|
+
FailureEvent,
|
|
9
|
+
)
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.common.query_language import (
|
|
11
|
+
QueryLanguageMode,
|
|
12
|
+
)
|
|
13
|
+
from localstack.services.stepfunctions.asl.component.common.retry.retrier_decl import RetrierDecl
|
|
14
|
+
from localstack.services.stepfunctions.asl.component.common.retry.retrier_outcome import (
|
|
15
|
+
RetrierOutcome,
|
|
16
|
+
)
|
|
17
|
+
from localstack.services.stepfunctions.asl.component.common.retry.retry_outcome import RetryOutcome
|
|
18
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.execute_state import (
|
|
19
|
+
ExecutionState,
|
|
20
|
+
)
|
|
21
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import (
|
|
22
|
+
MockedBaseState,
|
|
23
|
+
)
|
|
24
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
25
|
+
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MockedStateExecution(MockedBaseState[ExecutionState]):
|
|
29
|
+
def add_inspection_data(self, env: TestStateEnvironment):
|
|
30
|
+
if self._wrapped.query_language.query_language_mode == QueryLanguageMode.JSONPath:
|
|
31
|
+
if "afterResultSelector" not in env.inspection_data:
|
|
32
|
+
# HACK: A DistributedItemProcessorEvalInput is added to the stack and never popped off
|
|
33
|
+
# during an error case. So we need to check the inspected value is correct before
|
|
34
|
+
# adding it to our inspectionData.
|
|
35
|
+
if isinstance(env.stack[-1], (dict, str, int, float)):
|
|
36
|
+
env.inspection_data["afterResultSelector"] = to_json_str(env.stack[-1])
|
|
37
|
+
|
|
38
|
+
if catch := self._wrapped.catch:
|
|
39
|
+
for ind, catcher in enumerate(catch.catchers):
|
|
40
|
+
original_fn = catcher._eval_body
|
|
41
|
+
catcher._eval_body = self.with_catch_state_id(original_fn, ind)
|
|
42
|
+
|
|
43
|
+
if retry := self._wrapped.retry:
|
|
44
|
+
for ind, retrier in enumerate(retry.retriers):
|
|
45
|
+
original_fn = retrier._eval_body
|
|
46
|
+
retrier._eval_body = self.with_retry_state_id(retrier, ind)
|
|
47
|
+
|
|
48
|
+
def _apply_patches(self):
|
|
49
|
+
if not isinstance(self._wrapped, ExecutionState):
|
|
50
|
+
raise ValueError("Can only apply MockedStateExecution patches to an ExecutionState")
|
|
51
|
+
state = self._wrapped
|
|
52
|
+
|
|
53
|
+
if state.query_language.query_language_mode == QueryLanguageMode.JSONPath:
|
|
54
|
+
self._eval_with_inspect(self._wrapped.input_path, "afterInputPath")
|
|
55
|
+
self._eval_with_inspect(self._wrapped.result_path, "afterResultPath")
|
|
56
|
+
|
|
57
|
+
self._eval_with_inspect(self._wrapped.result_selector, "afterResultSelector")
|
|
58
|
+
original_eval_execution = self._wrapped._eval_execution
|
|
59
|
+
|
|
60
|
+
if self._wrapped.catch:
|
|
61
|
+
original_fn = self._wrapped._handle_catch
|
|
62
|
+
self._wrapped._handle_catch = partial(self._handle_catch, original_fn)
|
|
63
|
+
|
|
64
|
+
if self._wrapped.retry:
|
|
65
|
+
original_fn = self._wrapped._handle_retry
|
|
66
|
+
self._wrapped._handle_retry = partial(self._handle_retry, original_fn)
|
|
67
|
+
|
|
68
|
+
self._wrapped._eval_execution = self.wrap_with_post_return(
|
|
69
|
+
method=original_eval_execution,
|
|
70
|
+
post_return_fn=self.add_inspection_data,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def with_catch_state_id(
|
|
75
|
+
original_eval_body: Callable[[TestStateEnvironment], None], state_id: int
|
|
76
|
+
) -> Callable[[TestStateEnvironment], None]:
|
|
77
|
+
def _wrapped(env: TestStateEnvironment):
|
|
78
|
+
original_eval_body(env)
|
|
79
|
+
|
|
80
|
+
if isinstance(env.stack[-1], CatcherOutcomeCaught):
|
|
81
|
+
if not (error_details := env.inspection_data.get("errorDetails")):
|
|
82
|
+
error_details = env.inspection_data["errorDetails"] = {}
|
|
83
|
+
|
|
84
|
+
error_details["catchIndex"] = state_id
|
|
85
|
+
|
|
86
|
+
return _wrapped
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def with_retry_state_id(
|
|
90
|
+
retrier: RetrierDecl, state_id: int
|
|
91
|
+
) -> Callable[[TestStateEnvironment], None]:
|
|
92
|
+
original_retrier_eval_body = retrier._eval_body
|
|
93
|
+
|
|
94
|
+
def _wrapped(env: TestStateEnvironment):
|
|
95
|
+
if (retry_count := env.mock._state_configuration.get("retrierRetryCount", 0)) > 0:
|
|
96
|
+
retrier.max_attempts._store_attempt_number(env, retry_count - 1)
|
|
97
|
+
|
|
98
|
+
original_retrier_eval_body(env)
|
|
99
|
+
|
|
100
|
+
if not (error_details := env.inspection_data.get("errorDetails")):
|
|
101
|
+
error_details = env.inspection_data["errorDetails"] = {}
|
|
102
|
+
|
|
103
|
+
error_details["retryIndex"] = state_id
|
|
104
|
+
if env.stack[-1] == RetrierOutcome.Executed:
|
|
105
|
+
# TODO(gregfurman): Ideally, retryBackoffIntervalSeconds should be written to inspectionData
|
|
106
|
+
# within the retrier.backoff_rate decleration (perhaps at _access_next_multiplier).
|
|
107
|
+
rate = retrier.backoff_rate.rate
|
|
108
|
+
interval = retrier.interval_seconds.seconds
|
|
109
|
+
error_details["retryBackoffIntervalSeconds"] = int(interval * (rate**retry_count))
|
|
110
|
+
|
|
111
|
+
return _wrapped
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _handle_catch(
|
|
115
|
+
original_handle_catch: Callable[[TestStateEnvironment, FailureEvent], None],
|
|
116
|
+
env: TestStateEnvironment,
|
|
117
|
+
failure_event: FailureEvent,
|
|
118
|
+
) -> None:
|
|
119
|
+
original_handle_catch(env, failure_event)
|
|
120
|
+
|
|
121
|
+
spec: dict[str, str] = ExecutionState._construct_error_output_value(failure_event)
|
|
122
|
+
error, cause = spec.get("Error"), spec.get("Cause")
|
|
123
|
+
|
|
124
|
+
env.set_caught_error(env.next_state_name, error, cause)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _handle_retry(
|
|
128
|
+
original_handle_retry: Callable[[TestStateEnvironment, FailureEvent], RetryOutcome],
|
|
129
|
+
env: TestStateEnvironment,
|
|
130
|
+
failure_event: FailureEvent,
|
|
131
|
+
) -> RetryOutcome:
|
|
132
|
+
res = original_handle_retry(env, failure_event)
|
|
133
|
+
|
|
134
|
+
spec: dict[str, str] = ExecutionState._construct_error_output_value(failure_event)
|
|
135
|
+
error, cause = spec.get("Error"), spec.get("Cause")
|
|
136
|
+
|
|
137
|
+
if res == RetryOutcome.CanRetry:
|
|
138
|
+
env.set_retriable_error(error, cause)
|
|
139
|
+
return res
|
|
@@ -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
|
+
)
|