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.
Files changed (152) hide show
  1. localstack/aws/api/acm/__init__.py +122 -122
  2. localstack/aws/api/apigateway/__init__.py +604 -561
  3. localstack/aws/api/cloudcontrol/__init__.py +63 -63
  4. localstack/aws/api/cloudformation/__init__.py +1201 -969
  5. localstack/aws/api/cloudwatch/__init__.py +375 -375
  6. localstack/aws/api/config/__init__.py +784 -786
  7. localstack/aws/api/dynamodb/__init__.py +753 -759
  8. localstack/aws/api/dynamodbstreams/__init__.py +74 -74
  9. localstack/aws/api/ec2/__init__.py +10062 -8826
  10. localstack/aws/api/es/__init__.py +453 -453
  11. localstack/aws/api/events/__init__.py +552 -552
  12. localstack/aws/api/firehose/__init__.py +541 -543
  13. localstack/aws/api/iam/__init__.py +866 -572
  14. localstack/aws/api/kinesis/__init__.py +235 -147
  15. localstack/aws/api/kms/__init__.py +341 -336
  16. localstack/aws/api/lambda_/__init__.py +974 -621
  17. localstack/aws/api/logs/__init__.py +988 -675
  18. localstack/aws/api/opensearch/__init__.py +903 -785
  19. localstack/aws/api/pipes/__init__.py +336 -336
  20. localstack/aws/api/redshift/__init__.py +1257 -1166
  21. localstack/aws/api/resource_groups/__init__.py +175 -175
  22. localstack/aws/api/resourcegroupstaggingapi/__init__.py +103 -67
  23. localstack/aws/api/route53/__init__.py +296 -254
  24. localstack/aws/api/route53resolver/__init__.py +397 -396
  25. localstack/aws/api/s3/__init__.py +1412 -1349
  26. localstack/aws/api/s3control/__init__.py +594 -594
  27. localstack/aws/api/scheduler/__init__.py +118 -118
  28. localstack/aws/api/secretsmanager/__init__.py +221 -216
  29. localstack/aws/api/ses/__init__.py +227 -227
  30. localstack/aws/api/sns/__init__.py +115 -115
  31. localstack/aws/api/sqs/__init__.py +100 -100
  32. localstack/aws/api/ssm/__init__.py +1977 -1971
  33. localstack/aws/api/stepfunctions/__init__.py +375 -333
  34. localstack/aws/api/sts/__init__.py +142 -66
  35. localstack/aws/api/support/__init__.py +112 -112
  36. localstack/aws/api/swf/__init__.py +378 -386
  37. localstack/aws/api/transcribe/__init__.py +425 -425
  38. localstack/aws/handlers/logging.py +8 -4
  39. localstack/aws/handlers/service.py +22 -3
  40. localstack/aws/protocol/parser.py +1 -1
  41. localstack/aws/protocol/serializer.py +1 -1
  42. localstack/aws/scaffold.py +15 -17
  43. localstack/cli/localstack.py +6 -1
  44. localstack/deprecations.py +0 -6
  45. localstack/dev/kubernetes/__main__.py +38 -3
  46. localstack/services/acm/provider.py +4 -0
  47. localstack/services/apigateway/helpers.py +5 -9
  48. localstack/services/apigateway/legacy/provider.py +60 -24
  49. localstack/services/apigateway/patches.py +0 -9
  50. localstack/services/cloudformation/engine/template_preparer.py +6 -2
  51. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +12 -0
  52. localstack/services/cloudformation/provider.py +2 -2
  53. localstack/services/cloudformation/v2/provider.py +6 -6
  54. localstack/services/cloudwatch/provider.py +10 -3
  55. localstack/services/cloudwatch/provider_v2.py +6 -3
  56. localstack/services/configservice/provider.py +5 -1
  57. localstack/services/dynamodb/provider.py +1 -0
  58. localstack/services/dynamodb/v2/provider.py +1 -0
  59. localstack/services/dynamodbstreams/provider.py +6 -0
  60. localstack/services/dynamodbstreams/v2/provider.py +6 -0
  61. localstack/services/ec2/provider.py +6 -0
  62. localstack/services/es/provider.py +6 -0
  63. localstack/services/events/provider.py +4 -0
  64. localstack/services/events/v1/provider.py +9 -0
  65. localstack/services/firehose/provider.py +5 -0
  66. localstack/services/iam/provider.py +4 -0
  67. localstack/services/kinesis/packages.py +1 -1
  68. localstack/services/kms/models.py +44 -24
  69. localstack/services/kms/provider.py +97 -16
  70. localstack/services/lambda_/api_utils.py +40 -21
  71. localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
  72. localstack/services/lambda_/invocation/assignment.py +4 -1
  73. localstack/services/lambda_/invocation/execution_environment.py +21 -2
  74. localstack/services/lambda_/invocation/lambda_models.py +27 -2
  75. localstack/services/lambda_/invocation/lambda_service.py +51 -3
  76. localstack/services/lambda_/invocation/models.py +9 -1
  77. localstack/services/lambda_/invocation/version_manager.py +18 -3
  78. localstack/services/lambda_/packages.py +1 -1
  79. localstack/services/lambda_/provider.py +240 -96
  80. localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
  81. localstack/services/lambda_/runtimes.py +10 -3
  82. localstack/services/logs/provider.py +45 -19
  83. localstack/services/opensearch/provider.py +53 -3
  84. localstack/services/resource_groups/provider.py +5 -1
  85. localstack/services/resourcegroupstaggingapi/provider.py +6 -1
  86. localstack/services/s3/provider.py +29 -16
  87. localstack/services/s3/utils.py +35 -14
  88. localstack/services/s3control/provider.py +101 -2
  89. localstack/services/s3control/validation.py +50 -0
  90. localstack/services/sns/constants.py +3 -1
  91. localstack/services/sns/publisher.py +15 -6
  92. localstack/services/sns/v2/models.py +30 -1
  93. localstack/services/sns/v2/provider.py +794 -31
  94. localstack/services/sns/v2/utils.py +20 -0
  95. localstack/services/sqs/models.py +37 -10
  96. localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
  97. localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
  98. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
  99. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
  100. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
  101. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
  102. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
  103. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
  104. localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
  105. localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
  106. localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
  107. localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
  108. localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
  109. localstack/services/stepfunctions/asl/eval/environment.py +30 -22
  110. localstack/services/stepfunctions/asl/eval/states.py +1 -1
  111. localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
  112. localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
  113. localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
  114. localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
  115. localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
  116. localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
  117. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +170 -22
  118. localstack/services/stepfunctions/backend/execution.py +6 -6
  119. localstack/services/stepfunctions/backend/execution_worker.py +5 -5
  120. localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
  121. localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
  122. localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
  123. localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
  124. localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
  125. localstack/services/stepfunctions/provider.py +78 -27
  126. localstack/services/stepfunctions/test_state/mock_config.py +47 -0
  127. localstack/testing/pytest/fixtures.py +28 -0
  128. localstack/testing/snapshots/transformer_utility.py +7 -0
  129. localstack/testing/testselection/matching.py +0 -1
  130. localstack/utils/analytics/publisher.py +37 -155
  131. localstack/utils/analytics/service_request_aggregator.py +6 -4
  132. localstack/utils/aws/arns.py +7 -0
  133. localstack/utils/aws/client_types.py +0 -8
  134. localstack/utils/batching.py +258 -0
  135. localstack/utils/catalog/catalog_loader.py +111 -3
  136. localstack/utils/collections.py +23 -11
  137. localstack/utils/crypto.py +109 -0
  138. localstack/version.py +2 -2
  139. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/METADATA +7 -6
  140. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/RECORD +149 -141
  141. localstack_core-4.11.2.dev14.dist-info/plux.json +1 -0
  142. localstack/services/stepfunctions/mocking/__init__.py +0 -0
  143. localstack/utils/batch_policy.py +0 -124
  144. localstack_core-4.10.1.dev7.dist-info/plux.json +0 -1
  145. /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
  146. {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack +0 -0
  147. {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack-supervisor +0 -0
  148. {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack.bat +0 -0
  149. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/WHEEL +0 -0
  150. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/entry_points.txt +0 -0
  151. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/licenses/LICENSE.txt +0 -0
  152. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/top_level.txt +0 -0
@@ -6,6 +6,7 @@ from botocore.utils import InvalidArnException
6
6
 
7
7
  from localstack.aws.api.sns import InvalidParameterException
8
8
  from localstack.services.sns.constants import E164_REGEX, VALID_SUBSCRIPTION_ATTR_NAME
9
+ from localstack.services.sns.v2.models import SnsStore, SnsSubscription
9
10
  from localstack.utils.aws.arns import ArnData, parse_arn
10
11
  from localstack.utils.strings import short_uid, to_bytes, to_str
11
12
 
@@ -103,6 +104,14 @@ def create_subscription_arn(topic_arn: str) -> str:
103
104
  return f"{topic_arn}:{uuid4()}"
104
105
 
105
106
 
107
+ def create_platform_endpoint_arn(
108
+ platform_application_arn: str,
109
+ ) -> str:
110
+ # This is the format of an Endpoint Arn
111
+ # arn:aws:sns:us-west-2:1234567890:endpoint/GCM/MyApplication/12345678-abcd-9012-efgh-345678901234
112
+ return f"{platform_application_arn.replace('app', 'endpoint', 1)}/{uuid4()}"
113
+
114
+
106
115
  def encode_subscription_token_with_region(region: str) -> str:
107
116
  """
108
117
  Create a 64 characters Subscription Token with the region encoded
@@ -128,3 +137,14 @@ def get_region_from_subscription_token(token: str) -> str:
128
137
  return bytes.fromhex(region).decode("utf-8")
129
138
  except (IndexError, ValueError, TypeError, UnicodeDecodeError):
130
139
  raise InvalidParameterException("Invalid parameter: Token")
140
+
141
+
142
+ def get_topic_subscriptions(store: SnsStore, topic_arn: str) -> list[SnsSubscription]:
143
+ # TODO: delete this once the legacy v1 implementation has been removed
144
+ if hasattr(store, "topic_subscriptions"):
145
+ sub_arns = store.topic_subscriptions.get(topic_arn, [])
146
+ else:
147
+ sub_arns: list[str] = store.topics[topic_arn].get("subscriptions", [])
148
+
149
+ subscriptions = [store.subscriptions[k] for k in sub_arns if k in store.subscriptions]
150
+ return subscriptions
@@ -314,7 +314,8 @@ class SqsQueue:
314
314
  purge_timestamp: float | None
315
315
 
316
316
  delayed: set[SqsMessage]
317
- inflight: set[SqsMessage]
317
+ # Simulating an ordered set in python. Only the keys are used and of interest.
318
+ inflight: dict[SqsMessage, None]
318
319
  receipts: dict[str, SqsMessage]
319
320
 
320
321
  def __init__(self, name: str, region: str, account_id: str, attributes=None, tags=None) -> None:
@@ -326,7 +327,7 @@ class SqsQueue:
326
327
  self.tags = tags or {}
327
328
 
328
329
  self.delayed = set()
329
- self.inflight = set()
330
+ self.inflight = {}
330
331
  self.receipts = {}
331
332
 
332
333
  self.attributes = self.default_attributes()
@@ -513,7 +514,7 @@ class SqsQueue:
513
514
  )
514
515
  # Terminating the visibility timeout for a message
515
516
  # https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html#terminating-message-visibility-timeout
516
- self.inflight.remove(standard_message)
517
+ del self.inflight[standard_message]
517
518
  self._put_message(standard_message)
518
519
 
519
520
  def remove(self, receipt_handle: str):
@@ -606,9 +607,17 @@ class SqsQueue:
606
607
  standard_message,
607
608
  self.arn,
608
609
  )
609
- self.inflight.remove(standard_message)
610
+ del self.inflight[standard_message]
610
611
  self._put_message(standard_message)
611
612
 
613
+ def add_inflight_message(self, message: SqsMessage):
614
+ """
615
+ We are simulating an ordered set with a dict. When a value is added, it is added as key to the dict, which
616
+ is all we need. Hence all "values" in this ordered set are None
617
+ :param message: The message to put in flight
618
+ """
619
+ self.inflight[message] = None
620
+
612
621
  def enqueue_delayed_messages(self):
613
622
  if not self.delayed:
614
623
  return
@@ -779,7 +788,6 @@ class SqsQueue:
779
788
 
780
789
  class StandardQueue(SqsQueue):
781
790
  visible: InterruptiblePriorityQueue[SqsMessage]
782
- inflight: set[SqsMessage]
783
791
 
784
792
  def __init__(self, name: str, region: str, account_id: str, attributes=None, tags=None) -> None:
785
793
  super().__init__(name, region, account_id, attributes, tags)
@@ -923,13 +931,13 @@ class StandardQueue(SqsQueue):
923
931
  if message.visibility_timeout == 0:
924
932
  self.visible.put_nowait(message)
925
933
  else:
926
- self.inflight.add(message)
934
+ self.add_inflight_message(message)
927
935
 
928
936
  return result
929
937
 
930
938
  def _on_remove_message(self, message: SqsMessage):
931
939
  try:
932
- self.inflight.remove(message)
940
+ del self.inflight[message]
933
941
  except KeyError:
934
942
  # this likely means the message was removed with an expired receipt handle unfortunately this
935
943
  # means we need to scan the queue for the element and remove it from there, and then re-heapify
@@ -1149,6 +1157,26 @@ class FifoQueue(SqsQueue):
1149
1157
  elif previously_empty:
1150
1158
  self.message_group_queue.put_nowait(message_group)
1151
1159
 
1160
+ def requeue_inflight_messages(self):
1161
+ if not self.inflight:
1162
+ return
1163
+
1164
+ with self.mutex:
1165
+ messages = list(self.inflight)
1166
+ for standard_message in messages:
1167
+ # in fifo, an invisible message blocks potentially visible messages afterwards
1168
+ # this can happen for example if multiple message of the same group are received at once, then one
1169
+ # message of this batch has its visibility timeout extended
1170
+ if not standard_message.is_visible:
1171
+ return
1172
+ LOG.debug(
1173
+ "re-queueing inflight messages %s into queue %s",
1174
+ standard_message,
1175
+ self.arn,
1176
+ )
1177
+ del self.inflight[standard_message]
1178
+ self._put_message(standard_message)
1179
+
1152
1180
  def remove_expired_messages(self):
1153
1181
  with self.mutex:
1154
1182
  retention_period = self.message_retention_period
@@ -1278,8 +1306,7 @@ class FifoQueue(SqsQueue):
1278
1306
  if message.visibility_timeout == 0:
1279
1307
  self._put_message(message)
1280
1308
  else:
1281
- self.inflight.add(message)
1282
-
1309
+ self.add_inflight_message(message)
1283
1310
  return result
1284
1311
 
1285
1312
  def _on_remove_message(self, message: SqsMessage):
@@ -1288,7 +1315,7 @@ class FifoQueue(SqsQueue):
1288
1315
 
1289
1316
  with self.mutex:
1290
1317
  try:
1291
- self.inflight.remove(message)
1318
+ del self.inflight[message]
1292
1319
  except KeyError:
1293
1320
  # in FIFO queues, this should not happen, as expired receipt handles cannot be used to
1294
1321
  # delete a message.
@@ -18,7 +18,7 @@ class ResultPath(EvalComponent):
18
18
  def _eval_body(self, env: Environment) -> None:
19
19
  state_input = env.states.get_input()
20
20
 
21
- # Discard task output if there is one, and set the output ot be the state's input.
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(
@@ -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.mock_eval_utils import (
10
- eval_mocked_response,
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.mocking.mock_config import MockedResponse
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 _mocked_invoke_lambda_function(env: Environment) -> InvocationResponse:
46
- mocked_response: MockedResponse = env.get_current_mocked_response()
47
- eval_mocked_response(env=env, mocked_response=mocked_response)
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.is_mocked_mode():
72
- invocation_response: InvocationResponse = _mocked_invoke_lambda_function(env=env)
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.mocking.mock_config import (
14
- MockedResponse,
15
- MockedResponseReturn,
16
- MockedResponseThrow,
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(env: Environment, mocked_response: MockedResponseThrow) -> None:
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(env: Environment, mocked_response: MockedResponseReturn) -> None:
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 eval_mocked_response(env: Environment, mocked_response: MockedResponse) -> None:
40
- if isinstance(mocked_response, MockedResponseReturn):
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, MockedResponseThrow):
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.mock_eval_utils import (
37
- eval_mocked_response,
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.mocking.mock_config import MockedResponse
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.is_mocked_mode():
360
- mocked_response: MockedResponse = env.get_current_mocked_response()
361
- eval_mocked_response(env=env, mocked_response=mocked_response)
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.is_mocked_mode():
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