localstack-core 4.7.1.dev49__py3-none-any.whl → 4.10.1.dev12__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 (253) hide show
  1. localstack/aws/api/cloudformation/__init__.py +18 -4
  2. localstack/aws/api/cloudwatch/__init__.py +41 -1
  3. localstack/aws/api/config/__init__.py +4 -0
  4. localstack/aws/api/core.py +6 -2
  5. localstack/aws/api/dynamodb/__init__.py +30 -0
  6. localstack/aws/api/ec2/__init__.py +1522 -65
  7. localstack/aws/api/iam/__init__.py +7 -0
  8. localstack/aws/api/kinesis/__init__.py +19 -0
  9. localstack/aws/api/kms/__init__.py +6 -0
  10. localstack/aws/api/lambda_/__init__.py +13 -0
  11. localstack/aws/api/logs/__init__.py +15 -0
  12. localstack/aws/api/redshift/__init__.py +9 -3
  13. localstack/aws/api/route53/__init__.py +5 -0
  14. localstack/aws/api/s3/__init__.py +12 -0
  15. localstack/aws/api/s3control/__init__.py +54 -0
  16. localstack/aws/api/ssm/__init__.py +2 -0
  17. localstack/aws/api/transcribe/__init__.py +17 -0
  18. localstack/aws/client.py +7 -2
  19. localstack/aws/forwarder.py +52 -5
  20. localstack/aws/handlers/analytics.py +1 -1
  21. localstack/aws/handlers/internal_requests.py +6 -1
  22. localstack/aws/handlers/logging.py +12 -2
  23. localstack/aws/handlers/metric_handler.py +41 -1
  24. localstack/aws/handlers/service.py +40 -20
  25. localstack/aws/mocking.py +2 -2
  26. localstack/aws/patches.py +2 -2
  27. localstack/aws/protocol/parser.py +459 -32
  28. localstack/aws/protocol/serializer.py +689 -69
  29. localstack/aws/protocol/service_router.py +120 -20
  30. localstack/aws/protocol/validate.py +1 -1
  31. localstack/aws/scaffold.py +1 -1
  32. localstack/aws/skeleton.py +4 -2
  33. localstack/aws/spec-patches.json +58 -0
  34. localstack/aws/spec.py +37 -16
  35. localstack/cli/exceptions.py +1 -1
  36. localstack/cli/localstack.py +6 -6
  37. localstack/cli/lpm.py +3 -4
  38. localstack/cli/plugins.py +1 -1
  39. localstack/cli/profiles.py +1 -2
  40. localstack/config.py +25 -18
  41. localstack/constants.py +4 -29
  42. localstack/dev/kubernetes/__main__.py +130 -7
  43. localstack/dev/run/configurators.py +1 -4
  44. localstack/dev/run/paths.py +1 -1
  45. localstack/dns/plugins.py +5 -1
  46. localstack/dns/server.py +13 -4
  47. localstack/logging/format.py +3 -3
  48. localstack/packages/api.py +9 -8
  49. localstack/packages/core.py +2 -2
  50. localstack/packages/plugins.py +0 -8
  51. localstack/runtime/analytics.py +3 -0
  52. localstack/runtime/hooks.py +1 -1
  53. localstack/runtime/init.py +2 -2
  54. localstack/runtime/main.py +5 -5
  55. localstack/runtime/patches.py +2 -2
  56. localstack/services/apigateway/helpers.py +1 -4
  57. localstack/services/apigateway/legacy/helpers.py +7 -8
  58. localstack/services/apigateway/legacy/integration.py +4 -3
  59. localstack/services/apigateway/legacy/invocations.py +6 -5
  60. localstack/services/apigateway/legacy/provider.py +148 -68
  61. localstack/services/apigateway/legacy/templates.py +1 -1
  62. localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py +7 -2
  63. localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py +1 -2
  64. localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +3 -0
  65. localstack/services/apigateway/next_gen/execute_api/integrations/http.py +3 -3
  66. localstack/services/apigateway/next_gen/execute_api/template_mapping.py +2 -2
  67. localstack/services/apigateway/next_gen/execute_api/test_invoke.py +114 -9
  68. localstack/services/apigateway/next_gen/provider.py +5 -0
  69. localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +1 -1
  70. localstack/services/cloudformation/api_utils.py +4 -8
  71. localstack/services/cloudformation/cfn_utils.py +1 -1
  72. localstack/services/cloudformation/engine/entities.py +14 -4
  73. localstack/services/cloudformation/engine/template_deployer.py +6 -4
  74. localstack/services/cloudformation/engine/transformers.py +6 -4
  75. localstack/services/cloudformation/engine/v2/change_set_model.py +201 -13
  76. localstack/services/cloudformation/engine/v2/change_set_model_describer.py +52 -3
  77. localstack/services/cloudformation/engine/v2/change_set_model_executor.py +117 -76
  78. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +205 -52
  79. localstack/services/cloudformation/engine/v2/change_set_model_transform.py +350 -116
  80. localstack/services/cloudformation/engine/v2/change_set_model_validator.py +56 -14
  81. localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
  82. localstack/services/cloudformation/engine/v2/resolving.py +7 -5
  83. localstack/services/cloudformation/engine/yaml_parser.py +9 -2
  84. localstack/services/cloudformation/provider.py +7 -5
  85. localstack/services/cloudformation/resource_provider.py +7 -1
  86. localstack/services/cloudformation/resources.py +24149 -0
  87. localstack/services/cloudformation/service_models.py +2 -2
  88. localstack/services/cloudformation/v2/entities.py +19 -9
  89. localstack/services/cloudformation/v2/provider.py +336 -106
  90. localstack/services/cloudformation/v2/types.py +13 -7
  91. localstack/services/cloudformation/v2/utils.py +4 -1
  92. localstack/services/cloudwatch/alarm_scheduler.py +4 -1
  93. localstack/services/cloudwatch/provider.py +18 -13
  94. localstack/services/cloudwatch/provider_v2.py +25 -28
  95. localstack/services/dynamodb/packages.py +2 -1
  96. localstack/services/dynamodb/provider.py +42 -0
  97. localstack/services/dynamodb/server.py +2 -2
  98. localstack/services/dynamodb/v2/provider.py +42 -0
  99. localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
  100. localstack/services/edge.py +1 -1
  101. localstack/services/es/provider.py +2 -2
  102. localstack/services/events/event_rule_engine.py +31 -13
  103. localstack/services/events/models.py +4 -5
  104. localstack/services/events/provider.py +17 -14
  105. localstack/services/events/target.py +17 -9
  106. localstack/services/events/v1/provider.py +5 -5
  107. localstack/services/firehose/provider.py +14 -4
  108. localstack/services/iam/provider.py +11 -116
  109. localstack/services/iam/resources/policy_simulator.py +133 -0
  110. localstack/services/kinesis/models.py +15 -2
  111. localstack/services/kinesis/provider.py +86 -3
  112. localstack/services/kms/provider.py +14 -5
  113. localstack/services/lambda_/api_utils.py +6 -3
  114. localstack/services/lambda_/invocation/docker_runtime_executor.py +1 -1
  115. localstack/services/lambda_/invocation/event_manager.py +1 -1
  116. localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
  117. localstack/services/lambda_/invocation/lambda_models.py +10 -7
  118. localstack/services/lambda_/invocation/lambda_service.py +5 -1
  119. localstack/services/lambda_/packages.py +1 -1
  120. localstack/services/lambda_/provider.py +4 -3
  121. localstack/services/lambda_/provider_utils.py +1 -1
  122. localstack/services/logs/provider.py +36 -19
  123. localstack/services/moto.py +2 -1
  124. localstack/services/opensearch/cluster.py +15 -7
  125. localstack/services/opensearch/packages.py +26 -7
  126. localstack/services/opensearch/provider.py +8 -2
  127. localstack/services/opensearch/versions.py +56 -7
  128. localstack/services/plugins.py +11 -7
  129. localstack/services/providers.py +10 -2
  130. localstack/services/redshift/provider.py +0 -21
  131. localstack/services/s3/constants.py +5 -2
  132. localstack/services/s3/cors.py +4 -4
  133. localstack/services/s3/models.py +1 -1
  134. localstack/services/s3/notifications.py +55 -39
  135. localstack/services/s3/presigned_url.py +35 -54
  136. localstack/services/s3/provider.py +73 -15
  137. localstack/services/s3/utils.py +42 -22
  138. localstack/services/s3/validation.py +46 -32
  139. localstack/services/s3/website_hosting.py +4 -2
  140. localstack/services/ses/provider.py +18 -8
  141. localstack/services/sns/constants.py +7 -1
  142. localstack/services/sns/executor.py +9 -2
  143. localstack/services/sns/provider.py +8 -5
  144. localstack/services/sns/publisher.py +31 -16
  145. localstack/services/sns/v2/models.py +167 -0
  146. localstack/services/sns/v2/provider.py +867 -0
  147. localstack/services/sns/v2/utils.py +130 -0
  148. localstack/services/sqs/constants.py +1 -1
  149. localstack/services/sqs/developer_api.py +205 -0
  150. localstack/services/sqs/models.py +48 -5
  151. localstack/services/sqs/provider.py +38 -311
  152. localstack/services/sqs/query_api.py +6 -2
  153. localstack/services/sqs/utils.py +121 -2
  154. localstack/services/ssm/provider.py +1 -1
  155. localstack/services/stepfunctions/asl/component/intrinsic/member.py +1 -1
  156. localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py +5 -11
  157. localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py +2 -2
  158. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +2 -2
  159. localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py +1 -1
  160. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py +2 -2
  161. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +1 -1
  162. localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py +2 -2
  163. localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py +1 -1
  164. localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py +1 -1
  165. localstack/services/stepfunctions/asl/eval/environment.py +1 -1
  166. localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
  167. localstack/services/stepfunctions/backend/execution.py +2 -1
  168. localstack/services/stores.py +1 -1
  169. localstack/services/transcribe/provider.py +6 -1
  170. localstack/state/codecs.py +61 -0
  171. localstack/state/core.py +11 -5
  172. localstack/state/pickle.py +10 -49
  173. localstack/testing/aws/cloudformation_utils.py +1 -1
  174. localstack/testing/pytest/cloudformation/fixtures.py +3 -3
  175. localstack/testing/pytest/cloudformation/transformers.py +0 -0
  176. localstack/testing/pytest/container.py +4 -5
  177. localstack/testing/pytest/fixtures.py +33 -31
  178. localstack/testing/pytest/in_memory_localstack.py +0 -4
  179. localstack/testing/pytest/marking.py +38 -11
  180. localstack/testing/pytest/stepfunctions/utils.py +4 -3
  181. localstack/testing/pytest/util.py +1 -1
  182. localstack/testing/pytest/validation_tracking.py +1 -2
  183. localstack/testing/snapshots/transformer_utility.py +6 -1
  184. localstack/utils/analytics/events.py +2 -2
  185. localstack/utils/analytics/metadata.py +6 -4
  186. localstack/utils/analytics/metrics/counter.py +8 -15
  187. localstack/utils/analytics/publisher.py +1 -2
  188. localstack/utils/analytics/service_providers.py +19 -0
  189. localstack/utils/analytics/service_request_aggregator.py +2 -2
  190. localstack/utils/archives.py +11 -11
  191. localstack/utils/asyncio.py +2 -2
  192. localstack/utils/aws/arns.py +24 -29
  193. localstack/utils/aws/aws_responses.py +8 -8
  194. localstack/utils/aws/aws_stack.py +2 -3
  195. localstack/utils/aws/dead_letter_queue.py +1 -5
  196. localstack/utils/aws/message_forwarding.py +1 -2
  197. localstack/utils/aws/request_context.py +4 -5
  198. localstack/utils/aws/resources.py +1 -1
  199. localstack/utils/aws/templating.py +1 -1
  200. localstack/utils/batch_policy.py +3 -3
  201. localstack/utils/bootstrap.py +21 -13
  202. localstack/utils/catalog/catalog.py +139 -0
  203. localstack/utils/catalog/catalog_loader.py +119 -0
  204. localstack/utils/catalog/common.py +58 -0
  205. localstack/utils/catalog/plugins.py +28 -0
  206. localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
  207. localstack/utils/collections.py +7 -8
  208. localstack/utils/config_listener.py +1 -1
  209. localstack/utils/container_networking.py +2 -3
  210. localstack/utils/container_utils/container_client.py +135 -136
  211. localstack/utils/container_utils/docker_cmd_client.py +85 -69
  212. localstack/utils/container_utils/docker_sdk_client.py +69 -66
  213. localstack/utils/crypto.py +10 -10
  214. localstack/utils/diagnose.py +3 -4
  215. localstack/utils/docker_utils.py +9 -5
  216. localstack/utils/files.py +33 -13
  217. localstack/utils/functions.py +4 -3
  218. localstack/utils/http.py +11 -11
  219. localstack/utils/json.py +20 -6
  220. localstack/utils/kinesis/kinesis_connector.py +2 -1
  221. localstack/utils/net.py +15 -9
  222. localstack/utils/no_exit_argument_parser.py +2 -2
  223. localstack/utils/numbers.py +9 -2
  224. localstack/utils/objects.py +7 -6
  225. localstack/utils/patch.py +10 -3
  226. localstack/utils/run.py +12 -11
  227. localstack/utils/scheduler.py +11 -11
  228. localstack/utils/server/tcp_proxy.py +2 -2
  229. localstack/utils/serving.py +3 -4
  230. localstack/utils/strings.py +15 -16
  231. localstack/utils/sync.py +126 -1
  232. localstack/utils/tagging.py +8 -6
  233. localstack/utils/testutil.py +8 -8
  234. localstack/utils/threads.py +2 -2
  235. localstack/utils/time.py +12 -4
  236. localstack/utils/urls.py +1 -3
  237. localstack/utils/xray/traceid.py +1 -1
  238. localstack/version.py +16 -3
  239. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/METADATA +18 -14
  240. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/RECORD +248 -239
  241. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/entry_points.txt +8 -4
  242. localstack_core-4.10.1.dev12.dist-info/plux.json +1 -0
  243. localstack/packages/terraform.py +0 -46
  244. localstack/services/cloudformation/deploy.html +0 -144
  245. localstack/services/cloudformation/deploy_ui.py +0 -47
  246. localstack/services/cloudformation/plugins.py +0 -12
  247. localstack_core-4.7.1.dev49.dist-info/plux.json +0 -1
  248. {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack +0 -0
  249. {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack-supervisor +0 -0
  250. {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack.bat +0 -0
  251. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/WHEEL +0 -0
  252. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/licenses/LICENSE.txt +0 -0
  253. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/top_level.txt +0 -0
@@ -60,7 +60,7 @@ class ServiceRequestCounter:
60
60
  if context.service_exception:
61
61
  return context.service_exception.code
62
62
 
63
- response = parse_response(context.operation, response)
63
+ response = parse_response(context.operation, context.protocol, response)
64
64
  return response["Error"]["Code"]
65
65
  except Exception:
66
66
  if config.DEBUG_ANALYTICS:
@@ -20,7 +20,12 @@ class InternalRequestParamsEnricher(Handler):
20
20
  try:
21
21
  dto = MappingProxyType(load_dto(header))
22
22
  except Exception as e:
23
- LOG.exception("Error loading request parameters '%s', Error: %s", header, e)
23
+ LOG.error(
24
+ "Error loading request parameters '%s', Error: %s",
25
+ header,
26
+ e,
27
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
28
+ )
24
29
  return
25
30
 
26
31
  context.internal_request_params = dto
@@ -1,6 +1,7 @@
1
1
  """Handlers for logging."""
2
2
 
3
3
  import logging
4
+ import types
4
5
  from functools import cached_property
5
6
 
6
7
  from localstack.aws.api import RequestContext, ServiceException
@@ -21,6 +22,14 @@ class ExceptionLogger(ExceptionHandler):
21
22
  def __init__(self, logger=None):
22
23
  self.logger = logger or LOG
23
24
 
25
+ try:
26
+ import moto.core.exceptions
27
+
28
+ self._moto_service_exception = moto.core.exceptions.ServiceException
29
+ except (ModuleNotFoundError, AttributeError):
30
+ # Moto may not be available in stripped-down versions of LocalStack, like LocalStack S3 image.
31
+ self._moto_service_exception = types.EllipsisType
32
+
24
33
  def __call__(
25
34
  self,
26
35
  chain: HandlerChain,
@@ -28,9 +37,10 @@ class ExceptionLogger(ExceptionHandler):
28
37
  context: RequestContext,
29
38
  response: Response,
30
39
  ):
31
- if isinstance(exception, ServiceException):
40
+ if isinstance(exception, (ServiceException, self._moto_service_exception)):
32
41
  # We do not want to log an error/stacktrace if the handler is working as expected, but chooses to throw
33
- # a service exception
42
+ # a service exception. It may also throw a Moto ServiceException, which should not be logged either
43
+ # because ServiceExceptionHandler understands it.
34
44
  return
35
45
  if self.logger.isEnabledFor(level=logging.DEBUG):
36
46
  self.logger.exception("exception during call chain", exc_info=exception)
@@ -1,9 +1,15 @@
1
+ import csv
1
2
  import logging
3
+ import os
4
+ from datetime import datetime
5
+ from pathlib import Path
2
6
 
3
7
  from localstack import config
4
8
  from localstack.aws.api import RequestContext
5
9
  from localstack.aws.chain import HandlerChain
10
+ from localstack.constants import ENV_INTERNAL_TEST_STORE_METRICS_PATH
6
11
  from localstack.http import Response
12
+ from localstack.utils.strings import short_uid
7
13
 
8
14
  LOG = logging.getLogger(__name__)
9
15
 
@@ -137,6 +143,32 @@ class MetricHandler:
137
143
 
138
144
  def __init__(self) -> None:
139
145
  self.metrics_handler_items = {}
146
+ self.local_filename = None
147
+
148
+ if self.should_store_metric_locally():
149
+ self.local_filename = self.create_local_file()
150
+
151
+ @staticmethod
152
+ def should_store_metric_locally() -> bool:
153
+ return config.is_collect_metrics_mode() and config.store_test_metrics_in_local_filesystem()
154
+
155
+ @staticmethod
156
+ def create_local_file():
157
+ folder = Path(
158
+ os.environ.get(ENV_INTERNAL_TEST_STORE_METRICS_PATH, "/tmp/localstack-metrics")
159
+ )
160
+ if not folder.exists():
161
+ folder.mkdir(parents=True, exist_ok=True)
162
+ LOG.debug("Metric reports will be stored in %s", folder)
163
+ filename = (
164
+ folder
165
+ / f"metric-report-raw-data-{datetime.utcnow().strftime('%Y-%m-%d__%H_%M_%S')}-{short_uid()}.csv"
166
+ )
167
+ with open(filename, "w") as fd:
168
+ LOG.debug("Creating new metric data file %s", filename)
169
+ writer = csv.writer(fd)
170
+ writer.writerow(Metric.RAW_DATA_HEADER)
171
+ return filename
140
172
 
141
173
  def create_metric_handler_item(
142
174
  self, chain: HandlerChain, context: RequestContext, response: Response
@@ -194,7 +226,15 @@ class MetricHandler:
194
226
  )
195
227
  # refrain from adding duplicates
196
228
  if metric not in MetricHandler.metric_data:
197
- MetricHandler.metric_data.append(metric)
229
+ self.append_metric(metric)
198
230
 
199
231
  # cleanup
200
232
  del self.metrics_handler_items[context]
233
+
234
+ def append_metric(self, metric: Metric):
235
+ if self.should_store_metric_locally():
236
+ with open(self.local_filename, "a") as fd:
237
+ writer = csv.writer(fd)
238
+ writer.writerow(metric)
239
+ else:
240
+ MetricHandler.metric_data.append(metric)
@@ -2,6 +2,7 @@
2
2
 
3
3
  import logging
4
4
  import traceback
5
+ import types
5
6
  from collections import defaultdict
6
7
  from typing import Any
7
8
 
@@ -9,15 +10,15 @@ from botocore.model import OperationModel, ServiceModel
9
10
 
10
11
  from localstack import config
11
12
  from localstack.http import Response
12
- from localstack.utils.coverage_docs import get_coverage_link_for_service
13
13
 
14
+ from ...utils.coverage_docs import get_coverage_link_for_service
14
15
  from ..api import CommonServiceException, RequestContext, ServiceException
15
16
  from ..api.core import ServiceOperation
16
17
  from ..chain import CompositeResponseHandler, ExceptionHandler, Handler, HandlerChain
17
18
  from ..client import parse_response, parse_service_exception
18
19
  from ..protocol.parser import RequestParser, create_parser
19
20
  from ..protocol.serializer import create_serializer
20
- from ..protocol.service_router import determine_aws_service_model
21
+ from ..protocol.service_router import determine_aws_protocol, determine_aws_service_model
21
22
  from ..skeleton import Skeleton, create_skeleton
22
23
 
23
24
  LOG = logging.getLogger(__name__)
@@ -33,6 +34,8 @@ class ServiceNameParser(Handler):
33
34
  # example). If it is already set, we can skip the parsing of the request. It is very important for S3, because
34
35
  # parsing the request will consume the data stream and prevent streaming.
35
36
  if context.service:
37
+ if not context.protocol:
38
+ context.protocol = determine_aws_protocol(context.request, context.service)
36
39
  return
37
40
 
38
41
  service_model = determine_aws_service_model(context.request)
@@ -41,6 +44,7 @@ class ServiceNameParser(Handler):
41
44
  return
42
45
 
43
46
  context.service = service_model
47
+ context.protocol = determine_aws_protocol(context.request, service_model)
44
48
 
45
49
 
46
50
  class ServiceRequestParser(Handler):
@@ -63,7 +67,7 @@ class ServiceRequestParser(Handler):
63
67
  return self.parse_and_enrich(context)
64
68
 
65
69
  def parse_and_enrich(self, context: RequestContext):
66
- parser = create_parser(context.service)
70
+ parser = create_parser(context.service, context.protocol)
67
71
  operation, instance = parser.parse(context.request)
68
72
 
69
73
  # enrich context
@@ -137,7 +141,7 @@ class ServiceRequestRouter(Handler):
137
141
  operation_name = operation.name
138
142
  message = f"no handler for operation '{operation_name}' on service '{service_name}'"
139
143
  error = CommonServiceException("InternalFailure", message, status_code=501)
140
- serializer = create_serializer(context.service)
144
+ serializer = create_serializer(context.service, context.protocol)
141
145
  return serializer.serialize_error_to_response(
142
146
  error, operation, context.request.headers, context.request_id
143
147
  )
@@ -153,6 +157,15 @@ class ServiceExceptionSerializer(ExceptionHandler):
153
157
  def __init__(self):
154
158
  self.handle_internal_failures = True
155
159
 
160
+ try:
161
+ import moto.core.exceptions
162
+
163
+ self._moto_service_exception = moto.core.exceptions.ServiceException
164
+ except (ModuleNotFoundError, AttributeError) as exc:
165
+ # Moto may not be available in stripped-down versions of LocalStack, like LocalStack S3 image.
166
+ LOG.debug("Unable to set up Moto ServiceException translation: %s", exc)
167
+ self._moto_service_exception = types.EllipsisType
168
+
156
169
  def __call__(
157
170
  self,
158
171
  chain: HandlerChain,
@@ -176,43 +189,48 @@ class ServiceExceptionSerializer(ExceptionHandler):
176
189
  action_name = operation.name
177
190
  exception_message: str | None = exception.args[0] if exception.args else None
178
191
  message = exception_message or get_coverage_link_for_service(service_name, action_name)
179
- LOG.info(message)
180
192
  error = CommonServiceException("InternalFailure", message, status_code=501)
181
- context.service_exception = error
193
+ LOG.info(message)
194
+
195
+ elif isinstance(exception, self._moto_service_exception):
196
+ # Translate Moto ServiceException to native ServiceException if Moto is available.
197
+ # This allows handler chain to gracefully handles Moto errors when provider handlers invoke Moto methods directly.
198
+ error = CommonServiceException(
199
+ code=exception.code,
200
+ message=exception.message,
201
+ )
182
202
 
183
203
  elif not isinstance(exception, ServiceException):
184
204
  if not self.handle_internal_failures:
185
205
  return
186
206
 
187
207
  if config.INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE:
188
- exception = "".join(
208
+ message = "".join(
189
209
  traceback.format_exception(
190
210
  type(exception), value=exception, tb=exception.__traceback__
191
211
  )
192
212
  )
213
+ else:
214
+ message = str(exception)
193
215
 
194
216
  # wrap exception for serialization
195
217
  if operation:
196
218
  operation_name = operation.name
197
- msg = "exception while calling %s.%s: %s" % (
198
- service_name,
199
- operation_name,
200
- exception,
201
- )
219
+ msg = f"exception while calling {service_name}.{operation_name}: {message}"
202
220
  else:
203
221
  # just use any operation for mocking purposes (the parser needs it to populate the default response)
204
222
  operation = context.service.operation_model(context.service.operation_names[0])
205
- msg = "exception while calling %s with unknown operation: %s" % (
206
- service_name,
207
- exception,
208
- )
223
+ msg = f"exception while calling {service_name} with unknown operation: {message}"
209
224
 
210
225
  status_code = 501 if config.FAIL_FAST else 500
211
226
 
212
- error = CommonServiceException("InternalError", msg, status_code=status_code)
213
- context.service_exception = error
227
+ error = CommonServiceException(
228
+ "InternalError", msg, status_code=status_code
229
+ ).with_traceback(exception.__traceback__)
214
230
 
215
- serializer = create_serializer(context.service) # TODO: serializer cache
231
+ context.service_exception = error
232
+
233
+ serializer = create_serializer(context.service, context.protocol)
216
234
  return serializer.serialize_error_to_response(
217
235
  error, operation, context.request.headers, context.request_id
218
236
  )
@@ -255,7 +273,9 @@ class ServiceResponseParser(Handler):
255
273
  return
256
274
 
257
275
  # in this case we need to parse the raw response
258
- parsed = parse_response(context.operation, response, include_response_metadata=False)
276
+ parsed = parse_response(
277
+ context.operation, context.protocol, response, include_response_metadata=False
278
+ )
259
279
  if service_exception := parse_service_exception(response, parsed):
260
280
  context.service_exception = service_exception
261
281
  else:
localstack/aws/mocking.py CHANGED
@@ -224,7 +224,7 @@ custom_arns = {
224
224
  def generate_instance(shape: Shape, graph: ShapeGraph) -> Instance | None:
225
225
  if shape is None:
226
226
  return None
227
- raise ValueError("could not generate shape for type %s" % shape.type_name)
227
+ raise ValueError(f"could not generate shape for type {shape.type_name}")
228
228
 
229
229
 
230
230
  @generate_instance.register
@@ -383,7 +383,7 @@ def _(shape: Shape, graph: ShapeGraph) -> int | float | bool | bytes | date:
383
383
  if shape.type_name == "timestamp":
384
384
  return datetime.now()
385
385
 
386
- raise ValueError("unknown type %s" % shape.type_name)
386
+ raise ValueError(f"unknown type {shape.type_name}")
387
387
 
388
388
 
389
389
  def generate_response(operation: OperationModel):
localstack/aws/patches.py CHANGED
@@ -20,12 +20,12 @@ def patch_moto_instance_tracker_meta():
20
20
  cls = super(InstanceTrackerMeta, meta).__new__(meta, name, bases, dct)
21
21
  if name == "BaseModel":
22
22
  return cls
23
- cls.instances = []
23
+ cls.instances_tracked = []
24
24
  return cls
25
25
 
26
26
  @patch(BaseModel.__new__, pass_target=False)
27
27
  def new_basemodel(cls, *args, **kwargs):
28
- # skip cls.instances.append(..) which is done by the original/upstream constructor
28
+ # skip cls.instances_tracked.append(..) which is done by the original/upstream constructor
29
29
  instance = super(BaseModel, cls).__new__(cls)
30
30
  return instance
31
31