localstack-core 4.7.1.dev139__py3-none-any.whl → 4.10.1.dev42__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 (208) hide show
  1. localstack/aws/api/acm/__init__.py +122 -122
  2. localstack/aws/api/apigateway/__init__.py +560 -559
  3. localstack/aws/api/cloudcontrol/__init__.py +63 -63
  4. localstack/aws/api/cloudformation/__init__.py +1041 -969
  5. localstack/aws/api/cloudwatch/__init__.py +408 -368
  6. localstack/aws/api/config/__init__.py +788 -786
  7. localstack/aws/api/core.py +4 -0
  8. localstack/aws/api/dynamodb/__init__.py +753 -759
  9. localstack/aws/api/dynamodbstreams/__init__.py +74 -74
  10. localstack/aws/api/ec2/__init__.py +9713 -8573
  11. localstack/aws/api/es/__init__.py +453 -453
  12. localstack/aws/api/events/__init__.py +552 -552
  13. localstack/aws/api/firehose/__init__.py +541 -543
  14. localstack/aws/api/iam/__init__.py +646 -572
  15. localstack/aws/api/kinesis/__init__.py +251 -144
  16. localstack/aws/api/kms/__init__.py +343 -333
  17. localstack/aws/api/lambda_/__init__.py +585 -571
  18. localstack/aws/api/logs/__init__.py +682 -666
  19. localstack/aws/api/opensearch/__init__.py +814 -785
  20. localstack/aws/api/pipes/__init__.py +336 -336
  21. localstack/aws/api/redshift/__init__.py +1192 -1164
  22. localstack/aws/api/resource_groups/__init__.py +175 -175
  23. localstack/aws/api/resourcegroupstaggingapi/__init__.py +67 -67
  24. localstack/aws/api/route53/__init__.py +256 -254
  25. localstack/aws/api/route53resolver/__init__.py +396 -396
  26. localstack/aws/api/s3/__init__.py +1358 -1345
  27. localstack/aws/api/s3control/__init__.py +616 -584
  28. localstack/aws/api/scheduler/__init__.py +118 -118
  29. localstack/aws/api/secretsmanager/__init__.py +193 -193
  30. localstack/aws/api/ses/__init__.py +227 -227
  31. localstack/aws/api/sns/__init__.py +115 -115
  32. localstack/aws/api/sqs/__init__.py +100 -100
  33. localstack/aws/api/ssm/__init__.py +1978 -1970
  34. localstack/aws/api/stepfunctions/__init__.py +323 -323
  35. localstack/aws/api/sts/__init__.py +90 -66
  36. localstack/aws/api/support/__init__.py +112 -112
  37. localstack/aws/api/swf/__init__.py +378 -386
  38. localstack/aws/api/transcribe/__init__.py +425 -425
  39. localstack/aws/client.py +7 -2
  40. localstack/aws/forwarder.py +52 -5
  41. localstack/aws/handlers/analytics.py +1 -1
  42. localstack/aws/handlers/logging.py +12 -2
  43. localstack/aws/handlers/metric_handler.py +41 -1
  44. localstack/aws/handlers/service.py +43 -10
  45. localstack/aws/protocol/parser.py +440 -21
  46. localstack/aws/protocol/serializer.py +684 -64
  47. localstack/aws/protocol/service_router.py +120 -20
  48. localstack/aws/scaffold.py +15 -17
  49. localstack/aws/skeleton.py +4 -2
  50. localstack/aws/spec-patches.json +58 -0
  51. localstack/aws/spec.py +33 -13
  52. localstack/cli/exceptions.py +1 -1
  53. localstack/cli/localstack.py +10 -5
  54. localstack/cli/lpm.py +3 -4
  55. localstack/cli/profiles.py +1 -2
  56. localstack/config.py +18 -12
  57. localstack/constants.py +4 -29
  58. localstack/dev/kubernetes/__main__.py +39 -4
  59. localstack/dev/run/paths.py +1 -1
  60. localstack/dns/plugins.py +5 -1
  61. localstack/dns/server.py +12 -3
  62. localstack/packages/api.py +9 -8
  63. localstack/packages/core.py +2 -2
  64. localstack/packages/plugins.py +0 -8
  65. localstack/runtime/init.py +1 -1
  66. localstack/services/apigateway/helpers.py +5 -9
  67. localstack/services/apigateway/legacy/provider.py +85 -12
  68. localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +3 -0
  69. localstack/services/apigateway/next_gen/execute_api/integrations/http.py +3 -3
  70. localstack/services/apigateway/next_gen/execute_api/test_invoke.py +50 -6
  71. localstack/services/apigateway/next_gen/provider.py +5 -0
  72. localstack/services/apigateway/patches.py +0 -9
  73. localstack/services/cloudformation/engine/entities.py +12 -1
  74. localstack/services/cloudformation/engine/v2/change_set_model.py +0 -3
  75. localstack/services/cloudformation/engine/v2/change_set_model_describer.py +14 -0
  76. localstack/services/cloudformation/engine/v2/change_set_model_executor.py +13 -15
  77. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +118 -24
  78. localstack/services/cloudformation/engine/v2/change_set_model_transform.py +4 -1
  79. localstack/services/cloudformation/engine/v2/change_set_model_validator.py +5 -14
  80. localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
  81. localstack/services/cloudformation/engine/v2/resolving.py +6 -4
  82. localstack/services/cloudformation/engine/yaml_parser.py +9 -2
  83. localstack/services/cloudformation/provider.py +2 -2
  84. localstack/services/cloudformation/resource_provider.py +5 -1
  85. localstack/services/cloudformation/resources.py +24149 -0
  86. localstack/services/cloudformation/v2/entities.py +6 -3
  87. localstack/services/cloudformation/v2/provider.py +178 -33
  88. localstack/services/cloudformation/v2/types.py +8 -4
  89. localstack/services/cloudwatch/provider_v2.py +25 -28
  90. localstack/services/dynamodb/packages.py +2 -1
  91. localstack/services/dynamodb/provider.py +42 -0
  92. localstack/services/dynamodb/v2/provider.py +42 -0
  93. localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
  94. localstack/services/es/provider.py +2 -2
  95. localstack/services/events/event_rule_engine.py +31 -13
  96. localstack/services/events/models.py +4 -5
  97. localstack/services/events/target.py +17 -9
  98. localstack/services/iam/provider.py +11 -116
  99. localstack/services/iam/resources/policy_simulator.py +133 -0
  100. localstack/services/kinesis/models.py +15 -2
  101. localstack/services/kinesis/packages.py +1 -1
  102. localstack/services/kinesis/provider.py +77 -0
  103. localstack/services/kms/models.py +34 -4
  104. localstack/services/kms/provider.py +107 -21
  105. localstack/services/lambda_/api_utils.py +3 -1
  106. localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
  107. localstack/services/lambda_/packages.py +1 -1
  108. localstack/services/lambda_/provider.py +1 -1
  109. localstack/services/lambda_/runtimes.py +8 -3
  110. localstack/services/logs/provider.py +36 -19
  111. localstack/services/moto.py +2 -1
  112. localstack/services/opensearch/cluster.py +15 -7
  113. localstack/services/opensearch/packages.py +26 -7
  114. localstack/services/opensearch/provider.py +6 -1
  115. localstack/services/opensearch/versions.py +56 -7
  116. localstack/services/s3/constants.py +5 -2
  117. localstack/services/s3/cors.py +4 -4
  118. localstack/services/s3/notifications.py +1 -1
  119. localstack/services/s3/presigned_url.py +27 -43
  120. localstack/services/s3/provider.py +68 -12
  121. localstack/services/s3/utils.py +42 -11
  122. localstack/services/ses/provider.py +16 -7
  123. localstack/services/sns/constants.py +7 -1
  124. localstack/services/sns/v2/models.py +190 -0
  125. localstack/services/sns/v2/provider.py +992 -2
  126. localstack/services/sns/v2/utils.py +138 -0
  127. localstack/services/sqs/developer_api.py +205 -0
  128. localstack/services/sqs/models.py +79 -13
  129. localstack/services/sqs/provider.py +8 -309
  130. localstack/services/sqs/query_api.py +1 -1
  131. localstack/services/sqs/utils.py +121 -2
  132. localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
  133. localstack/testing/aws/cloudformation_utils.py +1 -1
  134. localstack/testing/pytest/cloudformation/fixtures.py +3 -3
  135. localstack/testing/pytest/container.py +4 -5
  136. localstack/testing/pytest/fixtures.py +20 -19
  137. localstack/testing/pytest/in_memory_localstack.py +0 -4
  138. localstack/testing/pytest/marking.py +13 -4
  139. localstack/testing/pytest/stepfunctions/utils.py +4 -3
  140. localstack/testing/pytest/util.py +1 -1
  141. localstack/testing/pytest/validation_tracking.py +1 -2
  142. localstack/testing/snapshots/transformer_utility.py +7 -0
  143. localstack/testing/testselection/matching.py +0 -1
  144. localstack/utils/analytics/events.py +2 -2
  145. localstack/utils/analytics/metadata.py +1 -2
  146. localstack/utils/analytics/metrics/counter.py +6 -8
  147. localstack/utils/analytics/publisher.py +1 -2
  148. localstack/utils/analytics/service_request_aggregator.py +2 -2
  149. localstack/utils/archives.py +11 -11
  150. localstack/utils/aws/arns.py +17 -9
  151. localstack/utils/aws/aws_responses.py +7 -7
  152. localstack/utils/aws/aws_stack.py +2 -3
  153. localstack/utils/aws/client_types.py +0 -8
  154. localstack/utils/aws/message_forwarding.py +1 -2
  155. localstack/utils/aws/request_context.py +4 -5
  156. localstack/utils/batch_policy.py +3 -3
  157. localstack/utils/bootstrap.py +7 -7
  158. localstack/utils/catalog/catalog.py +139 -0
  159. localstack/utils/catalog/catalog_loader.py +119 -0
  160. localstack/utils/catalog/common.py +58 -0
  161. localstack/utils/catalog/plugins.py +28 -0
  162. localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
  163. localstack/utils/collections.py +7 -8
  164. localstack/utils/config_listener.py +1 -1
  165. localstack/utils/container_networking.py +2 -3
  166. localstack/utils/container_utils/container_client.py +115 -131
  167. localstack/utils/container_utils/docker_cmd_client.py +42 -42
  168. localstack/utils/container_utils/docker_sdk_client.py +63 -62
  169. localstack/utils/crypto.py +109 -0
  170. localstack/utils/diagnose.py +2 -3
  171. localstack/utils/docker_utils.py +3 -4
  172. localstack/utils/files.py +31 -7
  173. localstack/utils/functions.py +3 -2
  174. localstack/utils/http.py +4 -5
  175. localstack/utils/json.py +19 -5
  176. localstack/utils/kinesis/kinesis_connector.py +2 -1
  177. localstack/utils/net.py +6 -6
  178. localstack/utils/no_exit_argument_parser.py +2 -2
  179. localstack/utils/numbers.py +9 -2
  180. localstack/utils/objects.py +6 -5
  181. localstack/utils/patch.py +2 -1
  182. localstack/utils/run.py +10 -9
  183. localstack/utils/scheduler.py +11 -11
  184. localstack/utils/server/tcp_proxy.py +2 -2
  185. localstack/utils/serving.py +2 -3
  186. localstack/utils/strings.py +10 -11
  187. localstack/utils/sync.py +126 -1
  188. localstack/utils/tagging.py +1 -4
  189. localstack/utils/testutil.py +5 -4
  190. localstack/utils/threads.py +2 -2
  191. localstack/utils/time.py +11 -3
  192. localstack/utils/urls.py +1 -3
  193. localstack/version.py +2 -2
  194. {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/METADATA +19 -13
  195. {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/RECORD +203 -199
  196. {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/entry_points.txt +4 -2
  197. localstack_core-4.10.1.dev42.dist-info/plux.json +1 -0
  198. localstack/packages/terraform.py +0 -46
  199. localstack/services/cloudformation/deploy.html +0 -144
  200. localstack/services/cloudformation/deploy_ui.py +0 -47
  201. localstack/services/cloudformation/plugins.py +0 -12
  202. localstack_core-4.7.1.dev139.dist-info/plux.json +0 -1
  203. {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack +0 -0
  204. {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack-supervisor +0 -0
  205. {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack.bat +0 -0
  206. {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/WHEEL +0 -0
  207. {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/licenses/LICENSE.txt +0 -0
  208. {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,3 @@
1
- import copy
2
- import hashlib
3
1
  import json
4
2
  import logging
5
3
  import re
@@ -8,15 +6,12 @@ import time
8
6
  from collections.abc import Iterable
9
7
  from concurrent.futures.thread import ThreadPoolExecutor
10
8
  from itertools import islice
11
- from typing import Literal
12
9
 
13
10
  from botocore.utils import InvalidArnException
14
- from moto.sqs.models import BINARY_TYPE_FIELD_INDEX, STRING_TYPE_FIELD_INDEX
15
- from moto.sqs.models import Message as MotoMessage
16
11
  from werkzeug import Request as WerkzeugRequest
17
12
 
18
13
  from localstack import config
19
- from localstack.aws.api import CommonServiceException, RequestContext, ServiceException
14
+ from localstack.aws.api import RequestContext, ServiceException
20
15
  from localstack.aws.api.sqs import (
21
16
  ActionNameList,
22
17
  AttributeNameList,
@@ -70,11 +65,8 @@ from localstack.aws.api.sqs import (
70
65
  Token,
71
66
  TooManyEntriesInBatchRequest,
72
67
  )
73
- from localstack.aws.protocol.parser import create_parser
74
- from localstack.aws.protocol.serializer import aws_response_serializer
75
68
  from localstack.aws.spec import load_service
76
69
  from localstack.config import SQS_DISABLE_MAX_NUMBER_OF_MESSAGE_LIMIT
77
- from localstack.http import Request, route
78
70
  from localstack.services.edge import ROUTER
79
71
  from localstack.services.plugins import ServiceLifecycleHook
80
72
  from localstack.services.sqs import constants as sqs_constants
@@ -84,6 +76,7 @@ from localstack.services.sqs.constants import (
84
76
  HEADER_LOCALSTACK_SQS_OVERRIDE_WAIT_TIME_SECONDS,
85
77
  MAX_RESULT_LIMIT,
86
78
  )
79
+ from localstack.services.sqs.developer_api import SqsDeveloperApi
87
80
  from localstack.services.sqs.exceptions import (
88
81
  InvalidParameterValueException,
89
82
  MissingRequiredParameterException,
@@ -97,8 +90,10 @@ from localstack.services.sqs.models import (
97
90
  SqsStore,
98
91
  StandardQueue,
99
92
  sqs_stores,
93
+ to_sqs_api_message,
100
94
  )
101
95
  from localstack.services.sqs.utils import (
96
+ create_message_attribute_hash,
102
97
  decode_move_task_handle,
103
98
  generate_message_id,
104
99
  is_fifo_queue,
@@ -107,7 +102,6 @@ from localstack.services.sqs.utils import (
107
102
  )
108
103
  from localstack.services.stores import AccountRegionBundle
109
104
  from localstack.utils.aws.arns import parse_arn
110
- from localstack.utils.aws.request_context import extract_region_from_headers
111
105
  from localstack.utils.bootstrap import is_api_enabled
112
106
  from localstack.utils.cloudwatch.cloudwatch_util import (
113
107
  SqsMetricBatchData,
@@ -127,13 +121,6 @@ MAX_NUMBER_OF_MESSAGES = 10
127
121
  _STORE_LOCK = threading.RLock()
128
122
 
129
123
 
130
- class InvalidAddress(ServiceException):
131
- code = "InvalidAddress"
132
- message = "The address https://queue.amazonaws.com/ is not valid for this endpoint."
133
- sender_fault = True
134
- status_code = 404
135
-
136
-
137
124
  def assert_queue_name(queue_name: str, fifo: bool = False):
138
125
  if queue_name.endswith(".fifo"):
139
126
  if not fifo:
@@ -640,165 +627,6 @@ def check_fifo_id(fifo_id: str | None, parameter: str):
640
627
  )
641
628
 
642
629
 
643
- def get_sqs_protocol(request: Request) -> Literal["query", "json"]:
644
- content_type = request.headers.get("Content-Type")
645
- return "json" if content_type == "application/x-amz-json-1.0" else "query"
646
-
647
-
648
- def sqs_auto_protocol_aws_response_serializer(service_name: str, operation: str):
649
- def _decorate(fn):
650
- def _proxy(*args, **kwargs):
651
- # extract request from function invocation (decorator can be used for methods as well as for functions).
652
- if len(args) > 0 and isinstance(args[0], WerkzeugRequest):
653
- # function
654
- request = args[0]
655
- elif len(args) > 1 and isinstance(args[1], WerkzeugRequest):
656
- # method (arg[0] == self)
657
- request = args[1]
658
- elif "request" in kwargs:
659
- request = kwargs["request"]
660
- else:
661
- raise ValueError(f"could not find Request in signature of function {fn}")
662
-
663
- protocol = get_sqs_protocol(request)
664
- return aws_response_serializer(service_name, operation, protocol)(fn)(*args, **kwargs)
665
-
666
- return _proxy
667
-
668
- return _decorate
669
-
670
-
671
- class SqsDeveloperEndpoints:
672
- """
673
- A set of SQS developer tool endpoints:
674
-
675
- - ``/_aws/sqs/messages``: list SQS messages without side effects, compatible with ``ReceiveMessage``.
676
- """
677
-
678
- def __init__(self, stores=None):
679
- self.stores = stores or sqs_stores
680
-
681
- @route("/_aws/sqs/messages", methods=["GET", "POST"])
682
- @sqs_auto_protocol_aws_response_serializer("sqs", "ReceiveMessage")
683
- def list_messages(self, request: Request) -> ReceiveMessageResult:
684
- """
685
- This endpoint expects a ``QueueUrl`` request parameter (either as query arg or form parameter), similar to
686
- the ``ReceiveMessage`` operation. It will parse the Queue URL generated by one of the SQS endpoint strategies.
687
- """
688
-
689
- if "x-amz-" in request.mimetype or "x-www-form-urlencoded" in request.mimetype:
690
- # only parse the request using a parser if it comes from an AWS client
691
- protocol = get_sqs_protocol(request)
692
- operation, service_request = create_parser(
693
- load_service("sqs", protocol=protocol)
694
- ).parse(request)
695
- if operation.name != "ReceiveMessage":
696
- raise CommonServiceException(
697
- "InvalidRequest", "This endpoint only accepts ReceiveMessage calls"
698
- )
699
- else:
700
- service_request = dict(request.values)
701
-
702
- if not service_request.get("QueueUrl"):
703
- raise QueueDoesNotExist()
704
-
705
- try:
706
- account_id, region, queue_name = parse_queue_url(service_request.get("QueueUrl"))
707
- except ValueError:
708
- LOG.error(
709
- "Error while parsing Queue URL from request values: %s",
710
- service_request.get,
711
- exc_info=LOG.isEnabledFor(logging.DEBUG),
712
- )
713
- raise InvalidAddress()
714
-
715
- if not region:
716
- region = extract_region_from_headers(request.headers)
717
-
718
- return self._get_and_serialize_messages(request, region, account_id, queue_name)
719
-
720
- @route("/_aws/sqs/messages/<region>/<account_id>/<queue_name>")
721
- @sqs_auto_protocol_aws_response_serializer("sqs", "ReceiveMessage")
722
- def list_messages_for_queue_url(
723
- self, request: Request, region: str, account_id: str, queue_name: str
724
- ) -> ReceiveMessageResult:
725
- """
726
- This endpoint extracts the region, account_id, and queue_name directly from the URL rather than requiring the
727
- QueueUrl as parameter.
728
- """
729
- return self._get_and_serialize_messages(request, region, account_id, queue_name)
730
-
731
- def _get_and_serialize_messages(
732
- self,
733
- request: Request,
734
- region: str,
735
- account_id: str,
736
- queue_name: str,
737
- ) -> ReceiveMessageResult:
738
- show_invisible = request.values.get("ShowInvisible", "").lower() in ["true", "1"]
739
- show_delayed = request.values.get("ShowDelayed", "").lower() in ["true", "1"]
740
-
741
- try:
742
- store = SqsProvider.get_store(account_id, region)
743
- queue = store.queues[queue_name]
744
- except KeyError:
745
- LOG.info(
746
- "no queue named %s in region %s and account %s", queue_name, region, account_id
747
- )
748
- raise QueueDoesNotExist()
749
-
750
- messages = self._collect_messages(
751
- queue, show_invisible=show_invisible, show_delayed=show_delayed
752
- )
753
-
754
- return ReceiveMessageResult(Messages=messages)
755
-
756
- def _collect_messages(
757
- self, queue: SqsQueue, show_invisible: bool = False, show_delayed: bool = False
758
- ) -> list[Message]:
759
- """
760
- Retrieves from a given SqsQueue all visible messages without causing any side effects (not setting any
761
- receive timestamps, receive counts, or visibility state).
762
-
763
- :param queue: the queue
764
- :param show_invisible: show invisible messages as well
765
- :param show_delayed: show delayed messages as well
766
- :return: a list of messages
767
- """
768
- receipt_handle = "SQS/BACKDOOR/ACCESS" # dummy receipt handle
769
-
770
- sqs_messages: list[SqsMessage] = []
771
-
772
- if show_invisible:
773
- sqs_messages.extend(queue.inflight)
774
-
775
- if isinstance(queue, StandardQueue):
776
- sqs_messages.extend(queue.visible.queue)
777
- elif isinstance(queue, FifoQueue):
778
- for message_group in queue.message_groups.values():
779
- for sqs_message in message_group.messages:
780
- sqs_messages.append(sqs_message)
781
- else:
782
- raise ValueError(f"unknown queue type {type(queue)}")
783
-
784
- if show_delayed:
785
- sqs_messages.extend(queue.delayed)
786
-
787
- messages = []
788
-
789
- for sqs_message in sqs_messages:
790
- message: Message = to_sqs_api_message(sqs_message, [QueueAttributeName.All], ["All"])
791
- # these are all non-standard fields so we squelch the linter
792
- if show_invisible:
793
- message["Attributes"]["IsVisible"] = str(sqs_message.is_visible).lower() # noqa
794
- if show_delayed:
795
- message["Attributes"]["IsDelayed"] = str(sqs_message.is_delayed).lower() # noqa
796
- messages.append(message)
797
- message["ReceiptHandle"] = receipt_handle
798
-
799
- return messages
800
-
801
-
802
630
  class SqsProvider(SqsApi, ServiceLifecycleHook):
803
631
  """
804
632
  LocalStack SQS Provider.
@@ -840,7 +668,6 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
840
668
  # default are limited to 500kb payload size by Werkzeug. we make sure we only *increase* the limit if it's
841
669
  # already set, and if it's already set to unlimited we leave it.
842
670
  from rolo import Request as RoloRequest
843
- from werkzeug import Request as WerkzeugRequest
844
671
 
845
672
  # needed for the webserver integration (webservers create Werkzeug request objects)
846
673
  if WerkzeugRequest.max_form_memory_size is not None:
@@ -855,7 +682,7 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
855
682
 
856
683
  def on_before_start(self):
857
684
  query_api.register(ROUTER)
858
- self._router_rules = ROUTER.add(SqsDeveloperEndpoints())
685
+ self._router_rules = ROUTER.add(SqsDeveloperApi())
859
686
  self._queue_update_worker.start()
860
687
  self._start_cloudwatch_metrics_reporting()
861
688
 
@@ -1150,7 +977,7 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
1150
977
  MD5OfMessageBody=message["MD5OfBody"],
1151
978
  MD5OfMessageAttributes=message.get("MD5OfMessageAttributes"),
1152
979
  SequenceNumber=queue_item.sequence_number,
1153
- MD5OfMessageSystemAttributes=_create_message_attribute_hash(message_system_attributes),
980
+ MD5OfMessageSystemAttributes=create_message_attribute_hash(message_system_attributes),
1154
981
  )
1155
982
 
1156
983
  def send_message_batch(
@@ -1196,7 +1023,7 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
1196
1023
  MessageId=message.get("MessageId"),
1197
1024
  MD5OfMessageBody=message.get("MD5OfBody"),
1198
1025
  MD5OfMessageAttributes=message.get("MD5OfMessageAttributes"),
1199
- MD5OfMessageSystemAttributes=_create_message_attribute_hash(
1026
+ MD5OfMessageSystemAttributes=create_message_attribute_hash(
1200
1027
  message.get("message_system_attributes")
1201
1028
  ),
1202
1029
  SequenceNumber=queue_item.sequence_number,
@@ -1250,7 +1077,7 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
1250
1077
  MD5OfBody=md5(message_body),
1251
1078
  Body=message_body,
1252
1079
  Attributes=self._create_message_attributes(context, message_system_attributes),
1253
- MD5OfMessageAttributes=_create_message_attribute_hash(message_attributes),
1080
+ MD5OfMessageAttributes=create_message_attribute_hash(message_attributes),
1254
1081
  MessageAttributes=message_attributes,
1255
1082
  )
1256
1083
  if self._cloudwatch_dispatcher:
@@ -1810,34 +1637,6 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
1810
1637
  self._cloudwatch_dispatcher.shutdown()
1811
1638
 
1812
1639
 
1813
- # Method from moto's attribute_md5 of moto/sqs/models.py, separated from the Message Object
1814
- def _create_message_attribute_hash(message_attributes) -> str | None:
1815
- # To avoid the need to check for dict conformity everytime we invoke this function
1816
- if not isinstance(message_attributes, dict):
1817
- return
1818
- hash = hashlib.md5()
1819
-
1820
- for attrName in sorted(message_attributes.keys()):
1821
- attr_value = message_attributes[attrName]
1822
- # Encode name
1823
- MotoMessage.update_binary_length_and_value(hash, MotoMessage.utf8(attrName))
1824
- # Encode data type
1825
- MotoMessage.update_binary_length_and_value(hash, MotoMessage.utf8(attr_value["DataType"]))
1826
- # Encode transport type and value
1827
- if attr_value.get("StringValue"):
1828
- hash.update(bytearray([STRING_TYPE_FIELD_INDEX]))
1829
- MotoMessage.update_binary_length_and_value(
1830
- hash, MotoMessage.utf8(attr_value.get("StringValue"))
1831
- )
1832
- elif attr_value.get("BinaryValue"):
1833
- hash.update(bytearray([BINARY_TYPE_FIELD_INDEX]))
1834
- decoded_binary_value = attr_value.get("BinaryValue")
1835
- MotoMessage.update_binary_length_and_value(hash, decoded_binary_value)
1836
- # string_list_value, binary_list_value type is not implemented, reserved for the future use.
1837
- # See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html
1838
- return hash.hexdigest()
1839
-
1840
-
1841
1640
  def resolve_queue_location(
1842
1641
  context: RequestContext, queue_name: str | None = None, queue_url: str | None = None
1843
1642
  ) -> tuple[str, str | None, str]:
@@ -1862,106 +1661,6 @@ def resolve_queue_location(
1862
1661
  return context.account_id, context.region, queue_name
1863
1662
 
1864
1663
 
1865
- def to_sqs_api_message(
1866
- standard_message: SqsMessage,
1867
- attribute_names: AttributeNameList = None,
1868
- message_attribute_names: MessageAttributeNameList = None,
1869
- ) -> Message:
1870
- """
1871
- Utility function to convert an SQS message from LocalStack's internal representation to the AWS API
1872
- concept 'Message', which is the format returned by the ``ReceiveMessage`` operation.
1873
-
1874
- :param standard_message: A LocalStack SQS message
1875
- :param attribute_names: the attribute name list to filter
1876
- :param message_attribute_names: the message attribute names to filter
1877
- :return: a copy of the original Message with updated message attributes and MD5 attribute hash sums
1878
- """
1879
- # prepare message for receiver
1880
- message = copy.deepcopy(standard_message.message)
1881
-
1882
- # update system attributes of the message copy
1883
- message["Attributes"][MessageSystemAttributeName.ApproximateFirstReceiveTimestamp] = str(
1884
- int((standard_message.first_received or 0) * 1000)
1885
- )
1886
-
1887
- # filter attributes for receiver
1888
- message_filter_attributes(message, attribute_names)
1889
- message_filter_message_attributes(message, message_attribute_names)
1890
- if message.get("MessageAttributes"):
1891
- message["MD5OfMessageAttributes"] = _create_message_attribute_hash(
1892
- message["MessageAttributes"]
1893
- )
1894
- else:
1895
- # delete the value that was computed when creating the message
1896
- message.pop("MD5OfMessageAttributes", None)
1897
- return message
1898
-
1899
-
1900
- def message_filter_attributes(message: Message, names: AttributeNameList | None):
1901
- """
1902
- Utility function filter from the given message (in-place) the system attributes from the given list. It will
1903
- apply all rules according to:
1904
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message.
1905
-
1906
- :param message: The message to filter (it will be modified)
1907
- :param names: the attributes names/filters
1908
- """
1909
- if "Attributes" not in message:
1910
- return
1911
-
1912
- if not names:
1913
- del message["Attributes"]
1914
- return
1915
-
1916
- if QueueAttributeName.All in names:
1917
- return
1918
-
1919
- for k in list(message["Attributes"].keys()):
1920
- if k not in names:
1921
- del message["Attributes"][k]
1922
-
1923
-
1924
- def message_filter_message_attributes(message: Message, names: MessageAttributeNameList | None):
1925
- """
1926
- Utility function filter from the given message (in-place) the message attributes from the given list. It will
1927
- apply all rules according to:
1928
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message.
1929
-
1930
- :param message: The message to filter (it will be modified)
1931
- :param names: the attributes names/filters (can be 'All', '.*', '*' or prefix filters like 'Foo.*')
1932
- """
1933
- if not message.get("MessageAttributes"):
1934
- return
1935
-
1936
- if not names:
1937
- del message["MessageAttributes"]
1938
- return
1939
-
1940
- if "All" in names or ".*" in names or "*" in names:
1941
- return
1942
-
1943
- attributes = message["MessageAttributes"]
1944
- matched = []
1945
-
1946
- keys = [name for name in names if ".*" not in name]
1947
- prefixes = [name.split(".*")[0] for name in names if ".*" in name]
1948
-
1949
- # match prefix filters
1950
- for k in attributes:
1951
- if k in keys:
1952
- matched.append(k)
1953
- continue
1954
-
1955
- for prefix in prefixes:
1956
- if k.startswith(prefix):
1957
- matched.append(k)
1958
- break
1959
- if matched:
1960
- message["MessageAttributes"] = {k: attributes[k] for k in matched}
1961
- else:
1962
- message.pop("MessageAttributes")
1963
-
1964
-
1965
1664
  def extract_message_count_from_headers(context: RequestContext) -> int | None:
1966
1665
  if override := context.request.headers.get(
1967
1666
  HEADER_LOCALSTACK_SQS_OVERRIDE_MESSAGE_COUNT, default=None, type=int
@@ -35,7 +35,7 @@ LOG = logging.getLogger(__name__)
35
35
 
36
36
  service = load_service("sqs-query")
37
37
  parser = create_parser(service)
38
- serializer = create_serializer(service)
38
+ serializer = create_serializer(service, "query")
39
39
 
40
40
 
41
41
  @route(
@@ -1,12 +1,20 @@
1
1
  import base64
2
+ import hashlib
2
3
  import itertools
3
4
  import json
4
5
  import re
6
+ import struct
5
7
  import time
6
- from typing import Literal, NamedTuple
8
+ from typing import Any, Literal, NamedTuple
7
9
  from urllib.parse import urlparse
8
10
 
9
- from localstack.aws.api.sqs import QueueAttributeName, ReceiptHandleIsInvalid
11
+ from localstack.aws.api.sqs import (
12
+ AttributeNameList,
13
+ Message,
14
+ MessageAttributeNameList,
15
+ QueueAttributeName,
16
+ ReceiptHandleIsInvalid,
17
+ )
10
18
  from localstack.services.sqs.constants import (
11
19
  DOMAIN_STRATEGY_URL_REGEX,
12
20
  LEGACY_STRATEGY_URL_REGEX,
@@ -22,6 +30,11 @@ DOMAIN_ENDPOINT = re.compile(DOMAIN_STRATEGY_URL_REGEX)
22
30
  PATH_ENDPOINT = re.compile(PATH_STRATEGY_URL_REGEX)
23
31
  LEGACY_ENDPOINT = re.compile(LEGACY_STRATEGY_URL_REGEX)
24
32
 
33
+ STRING_TYPE_FIELD_INDEX = 1
34
+ BINARY_TYPE_FIELD_INDEX = 2
35
+ STRING_LIST_TYPE_FIELD_INDEX = 3
36
+ BINARY_LIST_TYPE_FIELD_INDEX = 4
37
+
25
38
 
26
39
  def is_sqs_queue_url(url: str) -> bool:
27
40
  return any(
@@ -184,3 +197,109 @@ def global_message_sequence():
184
197
 
185
198
  def generate_message_id():
186
199
  return long_uid()
200
+
201
+
202
+ def message_filter_attributes(message: Message, names: AttributeNameList | None):
203
+ """
204
+ Utility function filter from the given message (in-place) the system attributes from the given list. It will
205
+ apply all rules according to:
206
+ https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message.
207
+
208
+ :param message: The message to filter (it will be modified)
209
+ :param names: the attributes names/filters
210
+ """
211
+ if "Attributes" not in message:
212
+ return
213
+
214
+ if not names:
215
+ del message["Attributes"]
216
+ return
217
+
218
+ if QueueAttributeName.All in names:
219
+ return
220
+
221
+ for k in list(message["Attributes"].keys()):
222
+ if k not in names:
223
+ del message["Attributes"][k]
224
+
225
+
226
+ def message_filter_message_attributes(message: Message, names: MessageAttributeNameList | None):
227
+ """
228
+ Utility function filter from the given message (in-place) the message attributes from the given list. It will
229
+ apply all rules according to:
230
+ https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message.
231
+
232
+ :param message: The message to filter (it will be modified)
233
+ :param names: the attributes names/filters (can be 'All', '.*', '*' or prefix filters like 'Foo.*')
234
+ """
235
+ if not message.get("MessageAttributes"):
236
+ return
237
+
238
+ if not names:
239
+ del message["MessageAttributes"]
240
+ return
241
+
242
+ if "All" in names or ".*" in names or "*" in names:
243
+ return
244
+
245
+ attributes = message["MessageAttributes"]
246
+ matched = []
247
+
248
+ keys = [name for name in names if ".*" not in name]
249
+ prefixes = [name.split(".*")[0] for name in names if ".*" in name]
250
+
251
+ # match prefix filters
252
+ for k in attributes:
253
+ if k in keys:
254
+ matched.append(k)
255
+ continue
256
+
257
+ for prefix in prefixes:
258
+ if k.startswith(prefix):
259
+ matched.append(k)
260
+ break
261
+ if matched:
262
+ message["MessageAttributes"] = {k: attributes[k] for k in matched}
263
+ else:
264
+ message.pop("MessageAttributes")
265
+
266
+
267
+ def _utf8(value: Any) -> bytes: # type: ignore[misc]
268
+ if isinstance(value, str):
269
+ return value.encode("utf-8")
270
+ return value
271
+
272
+
273
+ def _update_binary_length_and_value(md5: Any, value: bytes) -> None: # type: ignore[misc]
274
+ length_bytes = struct.pack("!I".encode("ascii"), len(value))
275
+ md5.update(length_bytes)
276
+ md5.update(value)
277
+
278
+
279
+ def create_message_attribute_hash(message_attributes) -> str | None:
280
+ """
281
+ Method from moto's attribute_md5 of moto/sqs/models.py, separated from the Message Object.
282
+ """
283
+ # To avoid the need to check for dict conformity everytime we invoke this function
284
+ if not isinstance(message_attributes, dict):
285
+ return
286
+
287
+ hash = hashlib.md5()
288
+
289
+ for attrName in sorted(message_attributes.keys()):
290
+ attr_value = message_attributes[attrName]
291
+ # Encode name
292
+ _update_binary_length_and_value(hash, _utf8(attrName))
293
+ # Encode data type
294
+ _update_binary_length_and_value(hash, _utf8(attr_value["DataType"]))
295
+ # Encode transport type and value
296
+ if attr_value.get("StringValue"):
297
+ hash.update(bytearray([STRING_TYPE_FIELD_INDEX]))
298
+ _update_binary_length_and_value(hash, _utf8(attr_value.get("StringValue")))
299
+ elif attr_value.get("BinaryValue"):
300
+ hash.update(bytearray([BINARY_TYPE_FIELD_INDEX]))
301
+ decoded_binary_value = attr_value.get("BinaryValue")
302
+ _update_binary_length_and_value(hash, decoded_binary_value)
303
+ # string_list_value, binary_list_value type is not implemented, reserved for the future use.
304
+ # See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html
305
+ return hash.hexdigest()
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  from typing import Any, Final
8
8
 
9
9
  import jpype
10
- import jpype.imports
10
+ import jpype.imports # noqa # Required for JVM Java class imports
11
11
 
12
12
  from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
13
13
  from localstack.services.stepfunctions.packages import jpype_jsonata_package
@@ -29,7 +29,7 @@ def load_template_file(file_path: str | os.PathLike, *, path_ctx: str | os.PathL
29
29
  elif not file_path_obj.is_absolute():
30
30
  raise ValueError("Provided path must be absolute if no path_ctx is provided")
31
31
 
32
- return load_file(file_path_obj.absolute())
32
+ return load_file(file_path_obj.absolute(), strict=True)
33
33
 
34
34
 
35
35
  # TODO: TBH this utility really doesn't add anything, probably better to just remove it
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  from collections import defaultdict
3
- from collections.abc import Generator
4
- from typing import Callable, Optional, TypedDict
3
+ from collections.abc import Callable, Generator
4
+ from typing import TypedDict
5
5
 
6
6
  import pytest
7
7
  from botocore.exceptions import WaiterError
@@ -13,7 +13,7 @@ from localstack.utils.strings import short_uid
13
13
 
14
14
 
15
15
  class NormalizedEvent(TypedDict):
16
- PhysicalResourceId: Optional[str]
16
+ PhysicalResourceId: str | None
17
17
  LogicalResourceId: str
18
18
  ResourceType: str
19
19
  ResourceStatus: str
@@ -2,8 +2,7 @@ import logging
2
2
  import os
3
3
  import shlex
4
4
  import threading
5
- from collections.abc import Generator
6
- from typing import Callable, Optional
5
+ from collections.abc import Callable, Generator
7
6
 
8
7
  import pytest
9
8
 
@@ -38,8 +37,8 @@ class ContainerFactory:
38
37
  self,
39
38
  # convenience properties
40
39
  pro: bool = False,
41
- publish: Optional[list[int]] = None,
42
- configurators: Optional[list[ContainerConfigurator]] = None,
40
+ publish: list[int] | None = None,
41
+ configurators: list[ContainerConfigurator] | None = None,
43
42
  # ContainerConfig properties
44
43
  **kwargs,
45
44
  ) -> Container:
@@ -172,7 +171,7 @@ def container_factory() -> Generator[ContainerFactory, None, None]:
172
171
 
173
172
  @pytest.fixture(scope="session")
174
173
  def wait_for_localstack_ready():
175
- def _wait_for(container: RunningContainer, timeout: Optional[float] = None):
174
+ def _wait_for(container: RunningContainer, timeout: float | None = None):
176
175
  container.wait_until_ready(timeout)
177
176
 
178
177
  poll_condition(