localstack-core 4.10.1.dev42__py3-none-any.whl → 4.12.1.dev18__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of localstack-core might be problematic. Click here for more details.

Files changed (158) hide show
  1. localstack/aws/api/apigateway/__init__.py +42 -0
  2. localstack/aws/api/cloudformation/__init__.py +161 -0
  3. localstack/aws/api/ec2/__init__.py +1178 -12
  4. localstack/aws/api/iam/__init__.py +228 -0
  5. localstack/aws/api/kms/__init__.py +1 -0
  6. localstack/aws/api/lambda_/__init__.py +1034 -66
  7. localstack/aws/api/logs/__init__.py +500 -0
  8. localstack/aws/api/opensearch/__init__.py +100 -0
  9. localstack/aws/api/redshift/__init__.py +69 -0
  10. localstack/aws/api/resourcegroupstaggingapi/__init__.py +36 -0
  11. localstack/aws/api/route53/__init__.py +45 -0
  12. localstack/aws/api/route53resolver/__init__.py +1 -0
  13. localstack/aws/api/s3/__init__.py +64 -0
  14. localstack/aws/api/s3control/__init__.py +19 -0
  15. localstack/aws/api/secretsmanager/__init__.py +37 -23
  16. localstack/aws/api/stepfunctions/__init__.py +52 -10
  17. localstack/aws/api/sts/__init__.py +52 -0
  18. localstack/aws/connect.py +35 -15
  19. localstack/aws/handlers/logging.py +8 -4
  20. localstack/aws/handlers/service.py +11 -2
  21. localstack/aws/protocol/serializer.py +1 -1
  22. localstack/config.py +8 -0
  23. localstack/constants.py +3 -0
  24. localstack/deprecations.py +0 -6
  25. localstack/dev/kubernetes/__main__.py +39 -14
  26. localstack/runtime/analytics.py +11 -0
  27. localstack/services/acm/provider.py +17 -1
  28. localstack/services/apigateway/legacy/provider.py +28 -15
  29. localstack/services/cloudformation/engine/template_preparer.py +6 -2
  30. localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
  31. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +15 -1
  32. localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
  33. localstack/services/cloudformation/provider.py +26 -1
  34. localstack/services/cloudformation/provider_utils.py +20 -0
  35. localstack/services/cloudformation/resource_provider.py +5 -4
  36. localstack/services/cloudformation/scaffolding/__main__.py +94 -22
  37. localstack/services/cloudformation/v2/provider.py +41 -0
  38. localstack/services/cloudwatch/provider.py +10 -3
  39. localstack/services/cloudwatch/provider_v2.py +6 -3
  40. localstack/services/configservice/provider.py +5 -1
  41. localstack/services/dynamodb/provider.py +1 -0
  42. localstack/services/dynamodb/v2/provider.py +1 -0
  43. localstack/services/dynamodbstreams/provider.py +6 -0
  44. localstack/services/dynamodbstreams/v2/provider.py +6 -0
  45. localstack/services/ec2/provider.py +6 -0
  46. localstack/services/es/provider.py +6 -0
  47. localstack/services/events/provider.py +4 -0
  48. localstack/services/events/v1/provider.py +9 -0
  49. localstack/services/firehose/provider.py +5 -0
  50. localstack/services/iam/provider.py +4 -0
  51. localstack/services/kinesis/packages.py +1 -1
  52. localstack/services/kms/models.py +16 -22
  53. localstack/services/kms/provider.py +4 -0
  54. localstack/services/lambda_/analytics.py +11 -2
  55. localstack/services/lambda_/api_utils.py +37 -20
  56. localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
  57. localstack/services/lambda_/invocation/assignment.py +4 -1
  58. localstack/services/lambda_/invocation/event_manager.py +15 -11
  59. localstack/services/lambda_/invocation/execution_environment.py +21 -2
  60. localstack/services/lambda_/invocation/lambda_models.py +31 -2
  61. localstack/services/lambda_/invocation/lambda_service.py +62 -3
  62. localstack/services/lambda_/invocation/models.py +9 -1
  63. localstack/services/lambda_/invocation/version_manager.py +18 -3
  64. localstack/services/lambda_/provider.py +307 -106
  65. localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
  66. localstack/services/lambda_/runtimes.py +3 -1
  67. localstack/services/logs/provider.py +9 -0
  68. localstack/services/opensearch/packages.py +34 -20
  69. localstack/services/opensearch/provider.py +53 -3
  70. localstack/services/resource_groups/provider.py +5 -1
  71. localstack/services/resourcegroupstaggingapi/provider.py +6 -1
  72. localstack/services/route53/provider.py +7 -0
  73. localstack/services/route53resolver/provider.py +5 -0
  74. localstack/services/s3/constants.py +5 -0
  75. localstack/services/s3/exceptions.py +9 -0
  76. localstack/services/s3/models.py +9 -1
  77. localstack/services/s3/provider.py +51 -43
  78. localstack/services/s3/utils.py +81 -15
  79. localstack/services/s3control/provider.py +107 -2
  80. localstack/services/s3control/validation.py +50 -0
  81. localstack/services/scheduler/provider.py +4 -2
  82. localstack/services/secretsmanager/provider.py +4 -0
  83. localstack/services/ses/provider.py +4 -0
  84. localstack/services/sns/constants.py +16 -1
  85. localstack/services/sns/provider.py +5 -0
  86. localstack/services/sns/publisher.py +15 -6
  87. localstack/services/sns/v2/models.py +9 -0
  88. localstack/services/sns/v2/provider.py +750 -19
  89. localstack/services/sns/v2/utils.py +12 -0
  90. localstack/services/sqs/constants.py +6 -0
  91. localstack/services/sqs/provider.py +9 -1
  92. localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
  93. localstack/services/ssm/provider.py +6 -0
  94. localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
  95. localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
  96. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
  97. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
  98. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
  99. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
  100. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
  101. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
  102. localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
  103. localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
  104. localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
  105. localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
  106. localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
  107. localstack/services/stepfunctions/asl/eval/environment.py +30 -22
  108. localstack/services/stepfunctions/asl/eval/states.py +1 -1
  109. localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
  110. localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
  111. localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
  112. localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
  113. localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
  114. localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
  115. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +256 -22
  116. localstack/services/stepfunctions/backend/execution.py +10 -11
  117. localstack/services/stepfunctions/backend/execution_worker.py +5 -5
  118. localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
  119. localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
  120. localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
  121. localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
  122. localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
  123. localstack/services/stepfunctions/provider.py +83 -25
  124. localstack/services/stepfunctions/test_state/mock_config.py +47 -0
  125. localstack/services/sts/provider.py +7 -0
  126. localstack/services/support/provider.py +5 -1
  127. localstack/services/swf/provider.py +5 -1
  128. localstack/services/transcribe/provider.py +7 -0
  129. localstack/testing/aws/lambda_utils.py +1 -1
  130. localstack/testing/aws/util.py +2 -1
  131. localstack/testing/config.py +1 -0
  132. localstack/testing/pytest/fixtures.py +28 -0
  133. localstack/testing/snapshots/transformer_utility.py +5 -0
  134. localstack/utils/analytics/publisher.py +37 -155
  135. localstack/utils/analytics/service_request_aggregator.py +6 -4
  136. localstack/utils/aws/arns.py +7 -0
  137. localstack/utils/aws/client_types.py +2 -4
  138. localstack/utils/batching.py +258 -0
  139. localstack/utils/bootstrap.py +2 -2
  140. localstack/utils/catalog/catalog.py +3 -2
  141. localstack/utils/collections.py +23 -11
  142. localstack/utils/container_utils/container_client.py +22 -13
  143. localstack/utils/container_utils/docker_cmd_client.py +6 -6
  144. localstack/version.py +2 -2
  145. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/METADATA +7 -7
  146. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/RECORD +155 -146
  147. localstack_core-4.12.1.dev18.dist-info/plux.json +1 -0
  148. localstack/services/stepfunctions/mocking/__init__.py +0 -0
  149. localstack/utils/batch_policy.py +0 -124
  150. localstack_core-4.10.1.dev42.dist-info/plux.json +0 -1
  151. /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
  152. {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack +0 -0
  153. {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack-supervisor +0 -0
  154. {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack.bat +0 -0
  155. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/WHEEL +0 -0
  156. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/entry_points.txt +0 -0
  157. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/licenses/LICENSE.txt +0 -0
  158. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.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
@@ -31,6 +31,12 @@ INTERNAL_QUEUE_ATTRIBUTES = [
31
31
  QueueAttributeName.QueueArn,
32
32
  ]
33
33
 
34
+ #
35
+ # If these attributes are set to their default values, they are effectively
36
+ # deleted from the queue attributes and not returned in future calls to get_queue_attributes()
37
+ #
38
+ DELETE_IF_DEFAULT = {"KmsMasterKeyId": "", "KmsDataKeyReusePeriodSeconds": "300"}
39
+
34
40
  INVALID_STANDARD_QUEUE_ATTRIBUTES = [
35
41
  QueueAttributeName.FifoQueue,
36
42
  QueueAttributeName.ContentBasedDeduplication,
@@ -101,6 +101,7 @@ from localstack.services.sqs.utils import (
101
101
  parse_queue_url,
102
102
  )
103
103
  from localstack.services.stores import AccountRegionBundle
104
+ from localstack.state import StateVisitor
104
105
  from localstack.utils.aws.arns import parse_arn
105
106
  from localstack.utils.bootstrap import is_api_enabled
106
107
  from localstack.utils.cloudwatch.cloudwatch_util import (
@@ -659,6 +660,9 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
659
660
  self._router_rules = []
660
661
  self._init_cloudwatch_metrics_reporting()
661
662
 
663
+ def accept_state_visitor(self, visitor: StateVisitor):
664
+ visitor.visit(sqs_stores)
665
+
662
666
  @staticmethod
663
667
  def get_store(account_id: str, region: str) -> SqsStore:
664
668
  return sqs_stores[account_id][region]
@@ -1269,7 +1273,11 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
1269
1273
  for k, v in attributes.items():
1270
1274
  if k in sqs_constants.INTERNAL_QUEUE_ATTRIBUTES:
1271
1275
  raise InvalidAttributeName(f"Unknown Attribute {k}.")
1272
- queue.attributes[k] = v
1276
+ if k in sqs_constants.DELETE_IF_DEFAULT and v == sqs_constants.DELETE_IF_DEFAULT[k]:
1277
+ if k in queue.attributes:
1278
+ del queue.attributes[k]
1279
+ else:
1280
+ queue.attributes[k] = v
1273
1281
 
1274
1282
  # Special cases
1275
1283
  if queue.attributes.get(QueueAttributeName.Policy) == "":
@@ -64,6 +64,35 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
64
64
  TYPE = "AWS::SQS::Queue" # Autogenerated. Don't change
65
65
  SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change
66
66
 
67
+ # Values used when a property is removed from a template and needs to be set to its default.
68
+ # If AWS changes their defaults in the future, our parity tests should break.
69
+ DEFAULT_ATTRIBUTE_VALUES = {
70
+ "ReceiveMessageWaitTimeSeconds": "0",
71
+ "DelaySeconds": "0",
72
+ "KmsMasterKeyId": "",
73
+ "RedrivePolicy": "",
74
+ "MessageRetentionPeriod": "345600",
75
+ "MaximumMessageSize": "262144", # Note: CloudFormation sets this to 256KB on update, but 1MB on create
76
+ "VisibilityTimeout": "30",
77
+ "KmsDataKeyReusePeriodSeconds": "300",
78
+ }
79
+
80
+ # Private method for creating a unique queue name, if none is specified.
81
+ def _autogenerated_queue_name(self, request: ResourceRequest[SQSQueueProperties]) -> str:
82
+ queue_name = util.generate_default_name(request.stack_name, request.logical_resource_id)
83
+ isFifoQueue = request.desired_state.get("FifoQueue")
84
+
85
+ # Note that it's an SQS FIFO queue only if the FifoQueue property is set to boolean True, or the string "true"
86
+ # (case insensitive). If it's None (property was omitted) or False, or any type of string (e.g. a typo
87
+ # such as "Fasle"), then it's not a FIFO queue. This extra check is needed because the CloudFormation engine
88
+ # doesn't fully validate the FifoQueue property before passing it to the resource provider.
89
+ if (
90
+ isFifoQueue == True # noqa: E712
91
+ or (isinstance(isFifoQueue, str) and isFifoQueue.lower() == "true")
92
+ ):
93
+ queue_name = f"{queue_name[:-5]}.fifo"
94
+ return queue_name
95
+
67
96
  def create(
68
97
  self,
69
98
  request: ResourceRequest[SQSQueueProperties],
@@ -74,8 +103,6 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
74
103
  Primary identifier fields:
75
104
  - /properties/QueueUrl
76
105
 
77
-
78
-
79
106
  Create-only properties:
80
107
  - /properties/FifoQueue
81
108
  - /properties/QueueName
@@ -92,26 +119,13 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
92
119
  - sqs:TagQueue
93
120
 
94
121
  """
95
- # TODO: validations
122
+ # TODO: validations - what validations are needed?
96
123
  model = request.desired_state
97
124
  sqs = request.aws_client_factory.sqs
98
125
 
99
- if model.get("FifoQueue", False):
100
- model["FifoQueue"] = model["FifoQueue"]
101
-
102
- queue_name = model.get("QueueName")
103
- if not queue_name:
104
- # TODO: verify patterns here
105
- if model.get("FifoQueue"):
106
- queue_name = util.generate_default_name(
107
- request.stack_name, request.logical_resource_id
108
- )[:-5]
109
- queue_name = f"{queue_name}.fifo"
110
- else:
111
- queue_name = util.generate_default_name(
112
- request.stack_name, request.logical_resource_id
113
- )
114
- model["QueueName"] = queue_name
126
+ # if no QueueName is specified, automatically generate one
127
+ if not model.get("QueueName"):
128
+ model["QueueName"] = self._autogenerated_queue_name(request)
115
129
 
116
130
  attributes = self._compile_sqs_queue_attributes(model)
117
131
  result = request.aws_client_factory.sqs.create_queue(
@@ -184,38 +198,30 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
184
198
  """
185
199
  sqs = request.aws_client_factory.sqs
186
200
  model = request.desired_state
201
+ prev_model = request.previous_state
187
202
 
188
203
  assert request.previous_state is not None
189
204
 
190
- should_replace = (
191
- request.desired_state.get("QueueName", request.previous_state["QueueName"])
192
- != request.previous_state["QueueName"]
193
- ) or (
194
- request.desired_state.get("FifoQueue", request.previous_state.get("FifoQueue"))
195
- != request.previous_state.get("FifoQueue")
205
+ queue_url = prev_model["QueueUrl"]
206
+ self._populate_missing_attributes_with_defaults(model)
207
+ sqs.set_queue_attributes(
208
+ QueueUrl=queue_url, Attributes=self._compile_sqs_queue_attributes(model)
196
209
  )
197
210
 
198
- if not should_replace:
199
- return ProgressEvent(OperationStatus.SUCCESS, resource_model=request.previous_state)
200
-
201
- # TODO: copied from the create handler, extract?
202
- if model.get("FifoQueue"):
203
- queue_name = util.generate_default_name(
204
- request.stack_name, request.logical_resource_id
205
- )[:-5]
206
- queue_name = f"{queue_name}.fifo"
207
- else:
208
- queue_name = util.generate_default_name(request.stack_name, request.logical_resource_id)
209
-
210
- # replacement (TODO: find out if we should handle this in the provider or outside of it)
211
- # delete old queue
212
- sqs.delete_queue(QueueUrl=request.previous_state["QueueUrl"])
213
- # create new queue (TODO: re-use create logic to make this more robust, e.g. for
214
- # auto-generated queue names)
215
- model["QueueUrl"] = sqs.create_queue(QueueName=queue_name)["QueueUrl"]
216
- model["Arn"] = sqs.get_queue_attributes(
217
- QueueUrl=model["QueueUrl"], AttributeNames=["QueueArn"]
218
- )["Attributes"]["QueueArn"]
211
+ (tags_to_remove, tags_to_add_or_update) = util.resource_tags_to_remove_or_update(
212
+ prev_model.get("Tags", []), model.get("Tags", [])
213
+ )
214
+ sqs.untag_queue(QueueUrl=queue_url, TagKeys=tags_to_remove)
215
+ sqs.tag_queue(QueueUrl=queue_url, Tags=tags_to_add_or_update)
216
+
217
+ model["QueueUrl"] = queue_url
218
+ model["Arn"] = request.previous_state["Arn"]
219
+
220
+ # For QueueName and FifoQueue, always use the value from the previous model. These fields
221
+ # are create-only, so they cannot be changed via an update (even though they might be omitted)
222
+ model["QueueName"] = prev_model.get("QueueName")
223
+ model["FifoQueue"] = prev_model.get("FifoQueue", False)
224
+
219
225
  return ProgressEvent(OperationStatus.SUCCESS, resource_model=model)
220
226
 
221
227
  def _compile_sqs_queue_attributes(self, properties: SQSQueueProperties) -> dict[str, str]:
@@ -250,6 +256,15 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
250
256
 
251
257
  return result
252
258
 
259
+ def _populate_missing_attributes_with_defaults(self, properties: SQSQueueProperties) -> None:
260
+ """
261
+ For any attribute that is missing from the desired state, populate it with the default value.
262
+ This is the only way to remove an attribute from an existing SQS queue's configuration.
263
+ :param properties: the properties passed from cloudformation
264
+ """
265
+ for k, v in self.DEFAULT_ATTRIBUTE_VALUES.items():
266
+ properties.setdefault(k, v)
267
+
253
268
  def list(
254
269
  self,
255
270
  request: ResourceRequest[SQSQueueProperties],
@@ -78,6 +78,7 @@ from localstack.aws.api.ssm import (
78
78
  )
79
79
  from localstack.aws.connect import connect_to
80
80
  from localstack.services.moto import call_moto, call_moto_with_request
81
+ from localstack.state import StateVisitor
81
82
  from localstack.utils.aws.arns import extract_resource_from_arn, is_arn
82
83
  from localstack.utils.bootstrap import is_api_enabled
83
84
  from localstack.utils.collections import remove_attributes
@@ -105,6 +106,11 @@ class InvalidParameterNameException(ValidationException):
105
106
 
106
107
  # TODO: check if _normalize_name(..) calls are still required here
107
108
  class SsmProvider(SsmApi, ABC):
109
+ def accept_state_visitor(self, visitor: StateVisitor):
110
+ from moto.ssm.models import ssm_backends
111
+
112
+ visitor.visit(ssm_backends)
113
+
108
114
  def get_parameters(
109
115
  self,
110
116
  context: RequestContext,
@@ -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