localstack-core 4.3.1.dev6__py3-none-any.whl → 4.3.1.dev28__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 (44) hide show
  1. localstack/aws/api/ec2/__init__.py +597 -0
  2. localstack/aws/api/events/__init__.py +18 -12
  3. localstack/aws/api/route53/__init__.py +2 -0
  4. localstack/aws/api/s3control/__init__.py +89 -0
  5. localstack/aws/api/transcribe/__init__.py +1 -0
  6. localstack/services/cloudformation/engine/entities.py +18 -1
  7. localstack/services/cloudformation/engine/template_deployer.py +0 -9
  8. localstack/services/cloudformation/engine/v2/change_set_model.py +164 -35
  9. localstack/services/cloudformation/engine/v2/change_set_model_describer.py +143 -69
  10. localstack/services/cloudformation/engine/v2/change_set_model_executor.py +170 -0
  11. localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +8 -0
  12. localstack/services/cloudformation/v2/provider.py +72 -6
  13. localstack/services/ec2/patches.py +31 -3
  14. localstack/services/events/provider.py +6 -1
  15. localstack/services/kms/models.py +1 -1
  16. localstack/services/lambda_/event_source_mapping/pollers/dynamodb_poller.py +2 -0
  17. localstack/services/lambda_/event_source_mapping/pollers/kinesis_poller.py +2 -0
  18. localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +4 -2
  19. localstack/services/lambda_/invocation/assignment.py +4 -2
  20. localstack/services/lambda_/invocation/execution_environment.py +16 -4
  21. localstack/services/lambda_/invocation/logs.py +28 -4
  22. localstack/services/lambda_/provider.py +18 -3
  23. localstack/services/lambda_/runtimes.py +15 -2
  24. localstack/services/s3/presigned_url.py +15 -11
  25. localstack/services/secretsmanager/provider.py +13 -4
  26. localstack/services/sqs/models.py +22 -3
  27. localstack/services/sqs/utils.py +16 -7
  28. localstack/services/ssm/resource_providers/aws_ssm_parameter.py +1 -5
  29. localstack/services/stepfunctions/asl/utils/json_path.py +9 -0
  30. localstack/testing/snapshots/transformer_utility.py +13 -0
  31. localstack/utils/aws/client_types.py +8 -0
  32. localstack/utils/docker_utils.py +2 -2
  33. localstack/version.py +2 -2
  34. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev28.dist-info}/METADATA +5 -5
  35. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev28.dist-info}/RECORD +43 -42
  36. localstack_core-4.3.1.dev28.dist-info/plux.json +1 -0
  37. localstack_core-4.3.1.dev6.dist-info/plux.json +0 -1
  38. {localstack_core-4.3.1.dev6.data → localstack_core-4.3.1.dev28.data}/scripts/localstack +0 -0
  39. {localstack_core-4.3.1.dev6.data → localstack_core-4.3.1.dev28.data}/scripts/localstack-supervisor +0 -0
  40. {localstack_core-4.3.1.dev6.data → localstack_core-4.3.1.dev28.data}/scripts/localstack.bat +0 -0
  41. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev28.dist-info}/WHEEL +0 -0
  42. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev28.dist-info}/entry_points.txt +0 -0
  43. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev28.dist-info}/licenses/LICENSE.txt +0 -0
  44. {localstack_core-4.3.1.dev6.dist-info → localstack_core-4.3.1.dev28.dist-info}/top_level.txt +0 -0
@@ -59,6 +59,7 @@ IMAGE_MAPPING: dict[Runtime, str] = {
59
59
  Runtime.dotnet6: "dotnet:6",
60
60
  Runtime.dotnetcore3_1: "dotnet:core3.1", # deprecated Apr 3, 2023 => Apr 3, 2023 => May 3, 2023
61
61
  Runtime.go1_x: "go:1", # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024
62
+ Runtime.ruby3_4: "ruby:3.4",
62
63
  Runtime.ruby3_3: "ruby:3.3",
63
64
  Runtime.ruby3_2: "ruby:3.2",
64
65
  Runtime.ruby2_7: "ruby:2.7", # deprecated Dec 7, 2023 => Jan 9, 2024 => Feb 8, 2024
@@ -133,6 +134,7 @@ RUNTIMES_AGGREGATED = {
133
134
  "ruby": [
134
135
  Runtime.ruby3_2,
135
136
  Runtime.ruby3_3,
137
+ Runtime.ruby3_4,
136
138
  ],
137
139
  "dotnet": [
138
140
  Runtime.dotnet6,
@@ -149,7 +151,18 @@ TESTED_RUNTIMES: list[Runtime] = [
149
151
  runtime for runtime_group in RUNTIMES_AGGREGATED.values() for runtime in runtime_group
150
152
  ]
151
153
 
154
+ # An unordered list of snapstart-enabled runtimes. Related to snapshots in test_snapstart_exceptions
155
+ # https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html
156
+ SNAP_START_SUPPORTED_RUNTIMES = [
157
+ Runtime.java11,
158
+ Runtime.java17,
159
+ Runtime.java21,
160
+ Runtime.python3_12,
161
+ Runtime.python3_13,
162
+ Runtime.dotnet8,
163
+ ]
164
+
152
165
  # An ordered list of all Lambda runtimes considered valid by AWS. Matching snapshots in test_create_lambda_exceptions
153
- VALID_RUNTIMES: str = "[nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, java8.al2, ruby3.2, python3.8, python3.9]"
166
+ VALID_RUNTIMES: str = "[nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9]"
154
167
  # An ordered list of all Lambda runtimes for layers considered valid by AWS. Matching snapshots in test_layer_exceptions
155
- VALID_LAYER_RUNTIMES: str = "[ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java17, nodejs, nodejs4.3, java8.al2, go1.x, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, nodejs22.x, python3.10, java8, nodejs12.x, python3.11, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, python3.13, nodejs16.x, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby3.3, ruby2.5, python3.6, python2.7]"
168
+ VALID_LAYER_RUNTIMES: str = "[ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java25, java17, nodejs, nodejs4.3, java8.al2, go1.x, dotnet10, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, nodejs22.x, python3.10, java8, nodejs12.x, python3.11, nodejs24.x, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, python3.13, python3.14, nodejs16.x, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby3.3, ruby3.4, ruby2.5, python3.6, python2.7]"
@@ -60,7 +60,7 @@ LOG = logging.getLogger(__name__)
60
60
 
61
61
  SIGNATURE_V2_POST_FIELDS = [
62
62
  "signature",
63
- "AWSAccessKeyId",
63
+ "awsaccesskeyid",
64
64
  ]
65
65
 
66
66
  SIGNATURE_V4_POST_FIELDS = [
@@ -768,13 +768,17 @@ def validate_post_policy(
768
768
  )
769
769
  raise ex
770
770
 
771
- if not (policy := request_form.get("policy")):
771
+ form_dict = {k.lower(): v for k, v in request_form.items()}
772
+
773
+ policy = form_dict.get("policy")
774
+ if not policy:
772
775
  # A POST request needs a policy except if the bucket is publicly writable
773
776
  return
774
777
 
775
778
  # TODO: this does validation of fields only for now
776
- is_v4 = _is_match_with_signature_fields(request_form, SIGNATURE_V4_POST_FIELDS)
777
- is_v2 = _is_match_with_signature_fields(request_form, SIGNATURE_V2_POST_FIELDS)
779
+ is_v4 = _is_match_with_signature_fields(form_dict, SIGNATURE_V4_POST_FIELDS)
780
+ is_v2 = _is_match_with_signature_fields(form_dict, SIGNATURE_V2_POST_FIELDS)
781
+
778
782
  if not is_v2 and not is_v4:
779
783
  ex: AccessDenied = AccessDenied("Access Denied")
780
784
  ex.HostId = FAKE_HOST_ID
@@ -784,7 +788,7 @@ def validate_post_policy(
784
788
  policy_decoded = json.loads(base64.b64decode(policy).decode("utf-8"))
785
789
  except ValueError:
786
790
  # this means the policy has been tampered with
787
- signature = request_form.get("signature") if is_v2 else request_form.get("x-amz-signature")
791
+ signature = form_dict.get("signature") if is_v2 else form_dict.get("x-amz-signature")
788
792
  credentials = get_credentials_from_parameters(request_form, "us-east-1")
789
793
  ex: SignatureDoesNotMatch = create_signature_does_not_match_sig_v2(
790
794
  request_signature=signature,
@@ -813,7 +817,6 @@ def validate_post_policy(
813
817
  return
814
818
 
815
819
  conditions = policy_decoded.get("conditions", [])
816
- form_dict = {k.lower(): v for k, v in request_form.items()}
817
820
  for condition in conditions:
818
821
  if not _verify_condition(condition, form_dict, additional_policy_metadata):
819
822
  str_condition = str(condition).replace("'", '"')
@@ -896,7 +899,7 @@ def _parse_policy_expiration_date(expiration_string: str) -> datetime.datetime:
896
899
 
897
900
 
898
901
  def _is_match_with_signature_fields(
899
- request_form: ImmutableMultiDict, signature_fields: list[str]
902
+ request_form: dict[str, str], signature_fields: list[str]
900
903
  ) -> bool:
901
904
  """
902
905
  Checks if the form contains at least one of the required fields passed in `signature_fields`
@@ -910,12 +913,13 @@ def _is_match_with_signature_fields(
910
913
  for p in signature_fields:
911
914
  if p not in request_form:
912
915
  LOG.info("POST pre-sign missing fields")
913
- # .capitalize() does not work here, because of AWSAccessKeyId casing
914
916
  argument_name = (
915
- capitalize_header_name_from_snake_case(p)
916
- if "-" in p
917
- else f"{p[0].upper()}{p[1:]}"
917
+ capitalize_header_name_from_snake_case(p) if "-" in p else p.capitalize()
918
918
  )
919
+ # AWSAccessKeyId is a special case
920
+ if argument_name == "Awsaccesskeyid":
921
+ argument_name = "AWSAccessKeyId"
922
+
919
923
  ex: InvalidArgument = _create_invalid_argument_exc(
920
924
  message=f"Bucket POST must contain a field named '{argument_name}'. If it is specified, please check the order of the fields.",
921
925
  name=argument_name,
@@ -729,17 +729,28 @@ def backend_rotate_secret(
729
729
  if not self._is_valid_identifier(secret_id):
730
730
  raise SecretNotFoundException()
731
731
 
732
- if self.secrets[secret_id].is_deleted():
732
+ secret = self.secrets[secret_id]
733
+ if secret.is_deleted():
733
734
  raise InvalidRequestException(
734
735
  "An error occurred (InvalidRequestException) when calling the RotateSecret operation: You tried to \
735
736
  perform the operation on a secret that's currently marked deleted."
736
737
  )
738
+ # Resolve rotation_lambda_arn and fallback to previous value if its missing
739
+ # from the current request
740
+ rotation_lambda_arn = rotation_lambda_arn or secret.rotation_lambda_arn
741
+ if not rotation_lambda_arn:
742
+ raise InvalidRequestException(
743
+ "No Lambda rotation function ARN is associated with this secret."
744
+ )
737
745
 
738
746
  if rotation_lambda_arn:
739
747
  if len(rotation_lambda_arn) > 2048:
740
748
  msg = "RotationLambdaARN must <= 2048 characters long."
741
749
  raise InvalidParameterException(msg)
742
750
 
751
+ # In case rotation_period is not provided, resolve auto_rotate_after_days
752
+ # and fallback to previous value if its missing from the current request.
753
+ rotation_period = secret.auto_rotate_after_days or 0
743
754
  if rotation_rules:
744
755
  if rotation_days in rotation_rules:
745
756
  rotation_period = rotation_rules[rotation_days]
@@ -753,8 +764,6 @@ def backend_rotate_secret(
753
764
  except Exception:
754
765
  raise ResourceNotFoundException("Lambda does not exist or could not be accessed")
755
766
 
756
- secret = self.secrets[secret_id]
757
-
758
767
  # The rotation function must end with the versions of the secret in
759
768
  # one of two states:
760
769
  #
@@ -782,7 +791,7 @@ def backend_rotate_secret(
782
791
  pass
783
792
 
784
793
  secret.rotation_lambda_arn = rotation_lambda_arn
785
- secret.auto_rotate_after_days = rotation_rules.get(rotation_days, 0)
794
+ secret.auto_rotate_after_days = rotation_period
786
795
  if secret.auto_rotate_after_days > 0:
787
796
  wait_interval_s = int(rotation_period) * 86400
788
797
  secret.next_rotation_date = int(time.time()) + wait_interval_s
@@ -30,9 +30,9 @@ from localstack.services.sqs.exceptions import (
30
30
  )
31
31
  from localstack.services.sqs.queue import InterruptiblePriorityQueue, InterruptibleQueue
32
32
  from localstack.services.sqs.utils import (
33
- decode_receipt_handle,
34
33
  encode_move_task_handle,
35
34
  encode_receipt_handle,
35
+ extract_receipt_handle_info,
36
36
  global_message_sequence,
37
37
  guess_endpoint_strategy_and_host,
38
38
  is_message_deduplication_id_required,
@@ -445,7 +445,7 @@ class SqsQueue:
445
445
  return len(self.delayed)
446
446
 
447
447
  def validate_receipt_handle(self, receipt_handle: str):
448
- if self.arn != decode_receipt_handle(receipt_handle):
448
+ if self.arn != extract_receipt_handle_info(receipt_handle).queue_arn:
449
449
  raise ReceiptHandleIsInvalid(
450
450
  f'The input receipt handle "{receipt_handle}" is not a valid receipt handle.'
451
451
  )
@@ -490,6 +490,7 @@ class SqsQueue:
490
490
  return
491
491
 
492
492
  standard_message = self.receipts[receipt_handle]
493
+ self._pre_delete_checks(standard_message, receipt_handle)
493
494
  standard_message.deleted = True
494
495
  LOG.debug(
495
496
  "deleting message %s from queue %s",
@@ -724,6 +725,18 @@ class SqsQueue:
724
725
 
725
726
  return expired
726
727
 
728
+ def _pre_delete_checks(self, standard_message: SqsMessage, receipt_handle: str) -> None:
729
+ """
730
+ Runs any potential checks if a message that has been successfully identified via a receipt handle
731
+ is indeed supposed to be deleted.
732
+ For example, a receipt handle that has expired might not lead to deletion.
733
+
734
+ :param standard_message: The message to be deleted
735
+ :param receipt_handle: The handle associated with the message
736
+ :return: None. Potential violations raise errors.
737
+ """
738
+ pass
739
+
727
740
 
728
741
  class StandardQueue(SqsQueue):
729
742
  visible: InterruptiblePriorityQueue[SqsMessage]
@@ -1001,9 +1014,15 @@ class FifoQueue(SqsQueue):
1001
1014
  for message in self.delayed:
1002
1015
  message.delay_seconds = value
1003
1016
 
1017
+ def _pre_delete_checks(self, message: SqsMessage, receipt_handle: str) -> None:
1018
+ _, _, _, last_received = extract_receipt_handle_info(receipt_handle)
1019
+ if time.time() - float(last_received) > message.visibility_timeout:
1020
+ raise InvalidParameterValueException(
1021
+ f"Value {receipt_handle} for parameter ReceiptHandle is invalid. Reason: The receipt handle has expired."
1022
+ )
1023
+
1004
1024
  def remove(self, receipt_handle: str):
1005
1025
  self.validate_receipt_handle(receipt_handle)
1006
- decode_receipt_handle(receipt_handle)
1007
1026
 
1008
1027
  super().remove(receipt_handle)
1009
1028
 
@@ -3,7 +3,7 @@ import itertools
3
3
  import json
4
4
  import re
5
5
  import time
6
- from typing import Literal, Optional, Tuple
6
+ from typing import Literal, NamedTuple, Optional, Tuple
7
7
  from urllib.parse import urlparse
8
8
 
9
9
  from localstack.aws.api.sqs import QueueAttributeName, ReceiptHandleIsInvalid
@@ -116,16 +116,25 @@ def parse_queue_url(queue_url: str) -> Tuple[str, Optional[str], str]:
116
116
  return account_id, region, queue_name
117
117
 
118
118
 
119
- def decode_receipt_handle(receipt_handle: str) -> str:
119
+ class ReceiptHandleInformation(NamedTuple):
120
+ identifier: str
121
+ queue_arn: str
122
+ message_id: str
123
+ last_received: str
124
+
125
+
126
+ def extract_receipt_handle_info(receipt_handle: str) -> ReceiptHandleInformation:
120
127
  try:
121
128
  handle = base64.b64decode(receipt_handle).decode("utf-8")
122
- _, queue_arn, message_id, last_received = handle.split(" ")
123
- parse_arn(queue_arn) # raises a ValueError if it is not an arn
124
- return queue_arn
125
- except (IndexError, ValueError):
129
+ parts = handle.split(" ")
130
+ if len(parts) != 4:
131
+ raise ValueError(f'The input receipt handle "{receipt_handle}" is incomplete.')
132
+ parse_arn(parts[1])
133
+ return ReceiptHandleInformation(*parts)
134
+ except (IndexError, ValueError) as e:
126
135
  raise ReceiptHandleIsInvalid(
127
136
  f'The input receipt handle "{receipt_handle}" is not a valid receipt handle.'
128
- )
137
+ ) from e
129
138
 
130
139
 
131
140
  def encode_receipt_handle(queue_arn, message) -> str:
@@ -177,11 +177,7 @@ class SSMParameterProvider(ResourceProvider[SSMParameterProperties]):
177
177
 
178
178
  ssm.put_parameter(Overwrite=True, Tags=[], **update_config_props)
179
179
 
180
- return ProgressEvent(
181
- status=OperationStatus.SUCCESS,
182
- resource_model=model,
183
- custom_context=request.custom_context,
184
- )
180
+ return self.read(request)
185
181
 
186
182
  def update_tags(self, ssm, model, new_tags):
187
183
  current_tags = ssm.list_tags_for_resource(
@@ -7,6 +7,7 @@ from jsonpath_ng.jsonpath import Index
7
7
  from localstack.services.events.utils import to_json_str
8
8
 
9
9
  _PATTERN_SINGLETON_ARRAY_ACCESS_OUTPUT: Final[str] = r"\[\d+\]$"
10
+ _PATTERN_SLICE_OR_WILDCARD_ACCESS = r"\$(?:\.[^[]+\[(?:\*|\d*:\d*)\]|\[\*\])(?:\.[^[]+)*$"
10
11
 
11
12
 
12
13
  def _is_singleton_array_access(path: str) -> bool:
@@ -14,6 +15,12 @@ def _is_singleton_array_access(path: str) -> bool:
14
15
  return bool(re.search(_PATTERN_SINGLETON_ARRAY_ACCESS_OUTPUT, path))
15
16
 
16
17
 
18
+ def _contains_slice_or_wildcard_array(path: str) -> bool:
19
+ # Returns true if the json path contains a slice or wildcard in the array.
20
+ # Slices at the root are discarded, but wildcard at the root is allowed.
21
+ return bool(re.search(_PATTERN_SLICE_OR_WILDCARD_ACCESS, path))
22
+
23
+
17
24
  class NoSuchJsonPathError(Exception):
18
25
  json_path: Final[str]
19
26
  data: Final[Any]
@@ -42,6 +49,8 @@ def extract_json(path: str, data: Any) -> Any:
42
49
 
43
50
  matches = input_expr.find(data)
44
51
  if not matches:
52
+ if _contains_slice_or_wildcard_array(path):
53
+ return []
45
54
  raise NoSuchJsonPathError(json_path=path, data=data)
46
55
 
47
56
  if len(matches) > 1 or isinstance(matches[0].path, Index):
@@ -648,6 +648,19 @@ class TransformerUtility:
648
648
  ),
649
649
  "version_uuid",
650
650
  ),
651
+ KeyValueBasedTransformer(
652
+ lambda k, v: (
653
+ v
654
+ if (
655
+ isinstance(k, str)
656
+ and k == "RotationLambdaARN"
657
+ and isinstance(v, str)
658
+ and re.match(PATTERN_ARN, v)
659
+ )
660
+ else None
661
+ ),
662
+ "lambda-arn",
663
+ ),
651
664
  SortingTransformer("VersionStages"),
652
665
  SortingTransformer("Versions", lambda e: e.get("CreatedDate")),
653
666
  ]
@@ -31,6 +31,7 @@ if TYPE_CHECKING:
31
31
  from mypy_boto3_cloudwatch import CloudWatchClient
32
32
  from mypy_boto3_codebuild import CodeBuildClient
33
33
  from mypy_boto3_codecommit import CodeCommitClient
34
+ from mypy_boto3_codeconnections import CodeConnectionsClient
34
35
  from mypy_boto3_codedeploy import CodeDeployClient
35
36
  from mypy_boto3_codepipeline import CodePipelineClient
36
37
  from mypy_boto3_codestar_connections import CodeStarconnectionsClient
@@ -109,6 +110,7 @@ if TYPE_CHECKING:
109
110
  from mypy_boto3_timestream_query import TimestreamQueryClient
110
111
  from mypy_boto3_timestream_write import TimestreamWriteClient
111
112
  from mypy_boto3_transcribe import TranscribeServiceClient
113
+ from mypy_boto3_verifiedpermissions import VerifiedPermissionsClient
112
114
  from mypy_boto3_wafv2 import WAFV2Client
113
115
  from mypy_boto3_xray import XRayClient
114
116
 
@@ -139,6 +141,9 @@ class TypedServiceClientFactory(abc.ABC):
139
141
  cloudwatch: Union["CloudWatchClient", "MetadataRequestInjector[CloudWatchClient]"]
140
142
  codebuild: Union["CodeBuildClient", "MetadataRequestInjector[CodeBuildClient]"]
141
143
  codecommit: Union["CodeCommitClient", "MetadataRequestInjector[CodeCommitClient]"]
144
+ codeconnections: Union[
145
+ "CodeConnectionsClient", "MetadataRequestInjector[CodeConnectionsClient]"
146
+ ]
142
147
  codedeploy: Union["CodeDeployClient", "MetadataRequestInjector[CodeDeployClient]"]
143
148
  codepipeline: Union["CodePipelineClient", "MetadataRequestInjector[CodePipelineClient]"]
144
149
  codestar_connections: Union[
@@ -255,6 +260,9 @@ class TypedServiceClientFactory(abc.ABC):
255
260
  "TimestreamWriteClient", "MetadataRequestInjector[TimestreamWriteClient]"
256
261
  ]
257
262
  transcribe: Union["TranscribeServiceClient", "MetadataRequestInjector[TranscribeServiceClient]"]
263
+ verifiedpermissions: Union[
264
+ "VerifiedPermissionsClient", "MetadataRequestInjector[VerifiedPermissionsClient]"
265
+ ]
258
266
  wafv2: Union["WAFV2Client", "MetadataRequestInjector[WAFV2Client]"]
259
267
  xray: Union["XRayClient", "MetadataRequestInjector[XRayClient]"]
260
268
 
@@ -156,14 +156,14 @@ def container_ports_can_be_bound(
156
156
  except Exception as e:
157
157
  if "port is already allocated" not in str(e) and "address already in use" not in str(e):
158
158
  LOG.warning(
159
- "Unexpected error when attempting to determine container port status: %s", e
159
+ "Unexpected error when attempting to determine container port status", exc_info=e
160
160
  )
161
161
  return False
162
162
  # TODO(srw): sometimes the command output from the docker container is "None", particularly when this function is
163
163
  # invoked multiple times consecutively. Work out why.
164
164
  if to_str(result[0] or "").strip() != "test123":
165
165
  LOG.warning(
166
- "Unexpected output when attempting to determine container port status: %s", result[0]
166
+ "Unexpected output when attempting to determine container port status: %s", result
167
167
  )
168
168
  return True
169
169
 
localstack/version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '4.3.1.dev6'
21
- __version_tuple__ = version_tuple = (4, 3, 1, 'dev6')
20
+ __version__ = version = '4.3.1.dev28'
21
+ __version_tuple__ = version_tuple = (4, 3, 1, 'dev28')
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: localstack-core
3
- Version: 4.3.1.dev6
3
+ Version: 4.3.1.dev28
4
4
  Summary: The core library and runtime of LocalStack
5
5
  Author-email: LocalStack Contributors <info@localstack.cloud>
6
+ License-Expression: Apache-2.0
6
7
  Project-URL: Homepage, https://localstack.cloud
7
8
  Project-URL: Documentation, https://docs.localstack.cloud
8
9
  Project-URL: Repository, https://github.com/localstack/localstack.git
9
10
  Project-URL: Issues, https://github.com/localstack/localstack/issues
10
11
  Classifier: Programming Language :: Python :: 3
11
12
  Classifier: Programming Language :: Python :: 3.11
12
- Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Topic :: Internet
14
14
  Classifier: Topic :: Software Development :: Testing
15
15
  Classifier: Topic :: System :: Emulators
@@ -31,8 +31,8 @@ Requires-Dist: requests>=2.20.0
31
31
  Requires-Dist: semver>=2.10
32
32
  Requires-Dist: tailer>=0.4.1
33
33
  Provides-Extra: base-runtime
34
- Requires-Dist: boto3==1.37.23; extra == "base-runtime"
35
- Requires-Dist: botocore==1.37.23; extra == "base-runtime"
34
+ Requires-Dist: boto3==1.37.28; extra == "base-runtime"
35
+ Requires-Dist: botocore==1.37.28; extra == "base-runtime"
36
36
  Requires-Dist: awscrt>=0.13.14; extra == "base-runtime"
37
37
  Requires-Dist: cbor2>=5.5.0; extra == "base-runtime"
38
38
  Requires-Dist: dnspython>=1.16.0; extra == "base-runtime"
@@ -93,5 +93,5 @@ Requires-Dist: ruff>=0.3.3; extra == "dev"
93
93
  Requires-Dist: rstr>=3.2.0; extra == "dev"
94
94
  Provides-Extra: typehint
95
95
  Requires-Dist: localstack-core[dev]; extra == "typehint"
96
- Requires-Dist: boto3-stubs[acm,acm-pca,amplify,apigateway,apigatewayv2,appconfig,appconfigdata,application-autoscaling,appsync,athena,autoscaling,backup,batch,ce,cloudcontrol,cloudformation,cloudfront,cloudtrail,cloudwatch,codebuild,codecommit,codedeploy,codepipeline,codestar-connections,cognito-identity,cognito-idp,dms,docdb,dynamodb,dynamodbstreams,ec2,ecr,ecs,efs,eks,elasticache,elasticbeanstalk,elbv2,emr,emr-serverless,es,events,firehose,fis,glacier,glue,iam,identitystore,iot,iot-data,iotanalytics,iotwireless,kafka,kinesis,kinesisanalytics,kinesisanalyticsv2,kms,lakeformation,lambda,logs,managedblockchain,mediaconvert,mediastore,mq,mwaa,neptune,opensearch,organizations,pi,pinpoint,pipes,qldb,qldb-session,rds,rds-data,redshift,redshift-data,resource-groups,resourcegroupstaggingapi,route53,route53resolver,s3,s3control,sagemaker,sagemaker-runtime,secretsmanager,serverlessrepo,servicediscovery,ses,sesv2,sns,sqs,ssm,sso-admin,stepfunctions,sts,timestream-query,timestream-write,transcribe,wafv2,xray]; extra == "typehint"
96
+ Requires-Dist: boto3-stubs[acm,acm-pca,amplify,apigateway,apigatewayv2,appconfig,appconfigdata,application-autoscaling,appsync,athena,autoscaling,backup,batch,ce,cloudcontrol,cloudformation,cloudfront,cloudtrail,cloudwatch,codebuild,codecommit,codeconnections,codedeploy,codepipeline,codestar-connections,cognito-identity,cognito-idp,dms,docdb,dynamodb,dynamodbstreams,ec2,ecr,ecs,efs,eks,elasticache,elasticbeanstalk,elbv2,emr,emr-serverless,es,events,firehose,fis,glacier,glue,iam,identitystore,iot,iot-data,iotanalytics,iotwireless,kafka,kinesis,kinesisanalytics,kinesisanalyticsv2,kms,lakeformation,lambda,logs,managedblockchain,mediaconvert,mediastore,mq,mwaa,neptune,opensearch,organizations,pi,pinpoint,pipes,qldb,qldb-session,rds,rds-data,redshift,redshift-data,resource-groups,resourcegroupstaggingapi,route53,route53resolver,s3,s3control,sagemaker,sagemaker-runtime,secretsmanager,serverlessrepo,servicediscovery,ses,sesv2,sns,sqs,ssm,sso-admin,stepfunctions,sts,timestream-query,timestream-write,transcribe,verifiedpermissions,wafv2,xray]; extra == "typehint"
97
97
  Dynamic: license-file