localstack-core 4.9.3.dev25__py3-none-any.whl → 4.9.3.dev27__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 (70) hide show
  1. localstack/aws/handlers/metric_handler.py +41 -1
  2. localstack/cli/exceptions.py +1 -1
  3. localstack/cli/localstack.py +4 -4
  4. localstack/cli/lpm.py +3 -4
  5. localstack/cli/profiles.py +1 -2
  6. localstack/config.py +18 -12
  7. localstack/constants.py +4 -0
  8. localstack/packages/api.py +9 -8
  9. localstack/packages/core.py +2 -2
  10. localstack/testing/pytest/cloudformation/fixtures.py +3 -3
  11. localstack/testing/pytest/container.py +4 -5
  12. localstack/testing/pytest/fixtures.py +20 -19
  13. localstack/testing/pytest/marking.py +5 -4
  14. localstack/testing/pytest/stepfunctions/utils.py +4 -3
  15. localstack/testing/pytest/util.py +1 -1
  16. localstack/testing/pytest/validation_tracking.py +1 -2
  17. localstack/utils/analytics/events.py +2 -2
  18. localstack/utils/analytics/metadata.py +1 -2
  19. localstack/utils/analytics/metrics/counter.py +6 -8
  20. localstack/utils/analytics/publisher.py +1 -2
  21. localstack/utils/analytics/service_request_aggregator.py +2 -2
  22. localstack/utils/archives.py +11 -11
  23. localstack/utils/aws/arns.py +7 -7
  24. localstack/utils/aws/aws_responses.py +7 -7
  25. localstack/utils/aws/aws_stack.py +2 -3
  26. localstack/utils/aws/message_forwarding.py +1 -2
  27. localstack/utils/aws/request_context.py +4 -5
  28. localstack/utils/batch_policy.py +3 -3
  29. localstack/utils/bootstrap.py +7 -7
  30. localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
  31. localstack/utils/collections.py +7 -8
  32. localstack/utils/config_listener.py +1 -1
  33. localstack/utils/container_networking.py +2 -3
  34. localstack/utils/container_utils/container_client.py +115 -131
  35. localstack/utils/container_utils/docker_cmd_client.py +42 -42
  36. localstack/utils/container_utils/docker_sdk_client.py +63 -62
  37. localstack/utils/diagnose.py +2 -3
  38. localstack/utils/docker_utils.py +3 -4
  39. localstack/utils/functions.py +3 -2
  40. localstack/utils/http.py +4 -5
  41. localstack/utils/json.py +3 -3
  42. localstack/utils/kinesis/kinesis_connector.py +2 -1
  43. localstack/utils/net.py +6 -6
  44. localstack/utils/no_exit_argument_parser.py +2 -2
  45. localstack/utils/numbers.py +2 -2
  46. localstack/utils/objects.py +6 -5
  47. localstack/utils/patch.py +2 -1
  48. localstack/utils/run.py +10 -9
  49. localstack/utils/scheduler.py +11 -11
  50. localstack/utils/server/tcp_proxy.py +2 -2
  51. localstack/utils/serving.py +2 -3
  52. localstack/utils/strings.py +10 -11
  53. localstack/utils/sync.py +2 -1
  54. localstack/utils/tagging.py +1 -4
  55. localstack/utils/testutil.py +5 -4
  56. localstack/utils/threads.py +2 -2
  57. localstack/utils/time.py +1 -2
  58. localstack/utils/urls.py +1 -3
  59. localstack/version.py +2 -2
  60. {localstack_core-4.9.3.dev25.dist-info → localstack_core-4.9.3.dev27.dist-info}/METADATA +2 -2
  61. {localstack_core-4.9.3.dev25.dist-info → localstack_core-4.9.3.dev27.dist-info}/RECORD +69 -69
  62. localstack_core-4.9.3.dev27.dist-info/plux.json +1 -0
  63. localstack_core-4.9.3.dev25.dist-info/plux.json +0 -1
  64. {localstack_core-4.9.3.dev25.data → localstack_core-4.9.3.dev27.data}/scripts/localstack +0 -0
  65. {localstack_core-4.9.3.dev25.data → localstack_core-4.9.3.dev27.data}/scripts/localstack-supervisor +0 -0
  66. {localstack_core-4.9.3.dev25.data → localstack_core-4.9.3.dev27.data}/scripts/localstack.bat +0 -0
  67. {localstack_core-4.9.3.dev25.dist-info → localstack_core-4.9.3.dev27.dist-info}/WHEEL +0 -0
  68. {localstack_core-4.9.3.dev25.dist-info → localstack_core-4.9.3.dev27.dist-info}/entry_points.txt +0 -0
  69. {localstack_core-4.9.3.dev25.dist-info → localstack_core-4.9.3.dev27.dist-info}/licenses/LICENSE.txt +0 -0
  70. {localstack_core-4.9.3.dev25.dist-info → localstack_core-4.9.3.dev27.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ import tempfile
8
8
  import time
9
9
  import zipfile
10
10
  from subprocess import Popen
11
- from typing import IO, Literal, Optional, Union
11
+ from typing import IO, Literal
12
12
 
13
13
  from localstack.constants import MAVEN_REPO_URL
14
14
  from localstack.utils.files import load_file, mkdir, new_tmp_file, rm_rf, save_file
@@ -22,7 +22,7 @@ from .strings import truncate
22
22
  LOG = logging.getLogger(__name__)
23
23
 
24
24
 
25
- StrPath = Union[str, os.PathLike]
25
+ StrPath = str | os.PathLike
26
26
 
27
27
 
28
28
  def is_zip_file(content):
@@ -30,13 +30,13 @@ def is_zip_file(content):
30
30
  return zipfile.is_zipfile(stream)
31
31
 
32
32
 
33
- def get_unzipped_size(zip_file: Union[str, IO[bytes]]):
33
+ def get_unzipped_size(zip_file: str | IO[bytes]):
34
34
  """Returns the size of the unzipped file."""
35
35
  with zipfile.ZipFile(zip_file, "r") as zip_ref:
36
36
  return sum(f.file_size for f in zip_ref.infolist())
37
37
 
38
38
 
39
- def unzip(path: str, target_dir: str, overwrite: bool = True) -> Optional[Union[str, Popen]]:
39
+ def unzip(path: str, target_dir: str, overwrite: bool = True) -> str | Popen | None:
40
40
  from localstack.utils.platform import is_debian
41
41
 
42
42
  use_native_cmd = is_debian() or is_command_available("unzip")
@@ -99,7 +99,7 @@ def create_zip_file_python(
99
99
  base_dir: StrPath,
100
100
  zip_file: StrPath,
101
101
  mode: Literal["r", "w", "x", "a"] = "w",
102
- content_root: Optional[str] = None,
102
+ content_root: str | None = None,
103
103
  ):
104
104
  with zipfile.ZipFile(zip_file, mode) as zip_file:
105
105
  for root, dirs, files in os.walk(base_dir):
@@ -122,7 +122,7 @@ def add_file_to_jar(class_file, class_url, target_jar, base_dir=None):
122
122
 
123
123
 
124
124
  def update_jar_manifest(
125
- jar_file_name: str, parent_dir: str, search: Union[str, re.Pattern], replace: str
125
+ jar_file_name: str, parent_dir: str, search: str | re.Pattern, replace: str
126
126
  ):
127
127
  manifest_file_path = "META-INF/MANIFEST.MF"
128
128
  jar_path = os.path.join(parent_dir, jar_file_name)
@@ -174,10 +174,10 @@ def upgrade_jar_file(base_dir: str, file_glob: str, maven_asset: str):
174
174
  def download_and_extract(
175
175
  archive_url: str,
176
176
  target_dir: str,
177
- retries: Optional[int] = 0,
178
- sleep: Optional[int] = 3,
179
- tmp_archive: Optional[str] = None,
180
- checksum_url: Optional[str] = None,
177
+ retries: int | None = 0,
178
+ sleep: int | None = 3,
179
+ tmp_archive: str | None = None,
180
+ checksum_url: str | None = None,
181
181
  ) -> None:
182
182
  """
183
183
  Download and extract an archive to a target directory with optional checksum verification.
@@ -250,7 +250,7 @@ def download_and_extract_with_retry(
250
250
  archive_url,
251
251
  tmp_archive,
252
252
  target_dir,
253
- checksum_url: Optional[str] = None,
253
+ checksum_url: str | None = None,
254
254
  ):
255
255
  try:
256
256
  download_and_extract(
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  import re
3
3
  from functools import cache
4
- from typing import Optional, TypedDict
4
+ from typing import TypedDict
5
5
 
6
6
  from botocore.utils import ArnParser, InvalidArnException
7
7
 
@@ -27,7 +27,7 @@ PARTITION_NAMES = list(REGION_PREFIX_TO_PARTITION.values()) + [DEFAULT_PARTITION
27
27
  ARN_PARTITION_REGEX = r"^arn:(" + "|".join(sorted(PARTITION_NAMES)) + ")"
28
28
 
29
29
 
30
- def get_partition(region: Optional[str]) -> str:
30
+ def get_partition(region: str | None) -> str:
31
31
  if not region:
32
32
  return DEFAULT_PARTITION
33
33
  if region in PARTITION_NAMES:
@@ -65,28 +65,28 @@ def parse_arn(arn: str) -> ArnData:
65
65
  return _arn_parser.parse_arn(arn)
66
66
 
67
67
 
68
- def extract_account_id_from_arn(arn: str) -> Optional[str]:
68
+ def extract_account_id_from_arn(arn: str) -> str | None:
69
69
  try:
70
70
  return parse_arn(arn).get("account")
71
71
  except InvalidArnException:
72
72
  return None
73
73
 
74
74
 
75
- def extract_region_from_arn(arn: str) -> Optional[str]:
75
+ def extract_region_from_arn(arn: str) -> str | None:
76
76
  try:
77
77
  return parse_arn(arn).get("region")
78
78
  except InvalidArnException:
79
79
  return None
80
80
 
81
81
 
82
- def extract_service_from_arn(arn: str) -> Optional[str]:
82
+ def extract_service_from_arn(arn: str) -> str | None:
83
83
  try:
84
84
  return parse_arn(arn).get("service")
85
85
  except InvalidArnException:
86
86
  return None
87
87
 
88
88
 
89
- def extract_resource_from_arn(arn: str) -> Optional[str]:
89
+ def extract_resource_from_arn(arn: str) -> str | None:
90
90
  try:
91
91
  return parse_arn(arn).get("resource")
92
92
  except InvalidArnException:
@@ -285,7 +285,7 @@ def lambda_event_source_mapping_arn(uuid: str, account_id: str, region_name: str
285
285
  def lambda_function_or_layer_arn(
286
286
  type: str,
287
287
  entity_name: str,
288
- version: Optional[str],
288
+ version: str | None,
289
289
  account_id: str,
290
290
  region_name: str,
291
291
  ) -> str:
@@ -3,7 +3,7 @@ import datetime
3
3
  import json
4
4
  import re
5
5
  from binascii import crc32
6
- from typing import Any, Optional, Union
6
+ from typing import Any
7
7
  from urllib.parse import parse_qs
8
8
 
9
9
  import xmltodict
@@ -36,10 +36,10 @@ def requests_error_response_json(message, code=500, error_type="InternalFailure"
36
36
 
37
37
  def requests_error_response_xml(
38
38
  message: str,
39
- code: Optional[int] = 400,
40
- code_string: Optional[str] = "InvalidParameter",
41
- service: Optional[str] = None,
42
- xmlns: Optional[str] = None,
39
+ code: int | None = 400,
40
+ code_string: str | None = "InvalidParameter",
41
+ service: str | None = None,
42
+ xmlns: str | None = None,
43
43
  ):
44
44
  response = RequestsResponse()
45
45
  xmlns = xmlns or f"http://{service}.amazonaws.com/doc/2010-03-31/"
@@ -100,7 +100,7 @@ def requests_error_response_xml_signature_calculation(
100
100
 
101
101
  def requests_error_response(
102
102
  req_headers: dict,
103
- message: Union[str, bytes],
103
+ message: str | bytes,
104
104
  code: int = 500,
105
105
  error_type: str = "InternalFailure",
106
106
  service: str = None,
@@ -201,7 +201,7 @@ def parse_query_string(url_or_qs: str, multi_values=False) -> dict[str, str]:
201
201
  return result
202
202
 
203
203
 
204
- def calculate_crc32(content: Union[str, bytes]) -> int:
204
+ def calculate_crc32(content: str | bytes) -> int:
205
205
  return crc32(to_bytes(content)) & 0xFFFFFFFF
206
206
 
207
207
 
@@ -2,7 +2,6 @@ import logging
2
2
  import re
3
3
  import socket
4
4
  from functools import lru_cache
5
- from typing import Union
6
5
 
7
6
  import boto3
8
7
 
@@ -46,7 +45,7 @@ def get_boto3_region() -> str:
46
45
  return boto3.session.Session().region_name
47
46
 
48
47
 
49
- def get_local_service_url(service_name_or_port: Union[str, int]) -> str:
48
+ def get_local_service_url(service_name_or_port: str | int) -> str:
50
49
  """Return the local service URL for the given service name or port."""
51
50
  # TODO(srw): we don't need to differentiate on service name any more, so remove the argument
52
51
  if isinstance(service_name_or_port, int):
@@ -68,7 +67,7 @@ def get_s3_hostname():
68
67
 
69
68
 
70
69
  def fix_account_id_in_arns(
71
- response, replacement: str, colon_delimiter: str = ":", existing: Union[str, list[str]] = None
70
+ response, replacement: str, colon_delimiter: str = ":", existing: str | list[str] = None
72
71
  ):
73
72
  """Fix the account ID in the ARNs returned in the given Flask response or string"""
74
73
  from moto.core import DEFAULT_ACCOUNT_ID
@@ -3,7 +3,6 @@ import json
3
3
  import logging
4
4
  import re
5
5
  import uuid
6
- from typing import Optional
7
6
 
8
7
  from moto.events.models import events_backends
9
8
 
@@ -214,7 +213,7 @@ def list_of_parameters_to_object(items):
214
213
  return {item.get("Key"): item.get("Value") for item in items}
215
214
 
216
215
 
217
- def send_event_to_api_destination(target_arn, event, http_parameters: Optional[dict] = None):
216
+ def send_event_to_api_destination(target_arn, event, http_parameters: dict | None = None):
218
217
  """Send an event to an EventBridge API destination
219
218
  See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-api-destinations.html"""
220
219
 
@@ -4,7 +4,6 @@ This module has utilities relating to creating/parsing AWS requests.
4
4
 
5
5
  import logging
6
6
  import re
7
- from typing import Optional
8
7
 
9
8
  from rolo import Request as RoloRequest
10
9
 
@@ -30,7 +29,7 @@ def get_account_id_from_request(request: RoloRequest) -> str:
30
29
  return get_account_id_from_access_key_id(access_key_id)
31
30
 
32
31
 
33
- def extract_region_from_auth_header(headers) -> Optional[str]:
32
+ def extract_region_from_auth_header(headers) -> str | None:
34
33
  auth = headers.get("Authorization") or ""
35
34
  region = re.sub(r".*Credential=[^/]+/[^/]+/([^/]+)/.*", r"\1", auth)
36
35
  if region == auth:
@@ -38,12 +37,12 @@ def extract_region_from_auth_header(headers) -> Optional[str]:
38
37
  return region
39
38
 
40
39
 
41
- def extract_account_id_from_auth_header(headers) -> Optional[str]:
40
+ def extract_account_id_from_auth_header(headers) -> str | None:
42
41
  if access_key_id := extract_access_key_id_from_auth_header(headers):
43
42
  return get_account_id_from_access_key_id(access_key_id)
44
43
 
45
44
 
46
- def extract_access_key_id_from_auth_header(headers: dict[str, str]) -> Optional[str]:
45
+ def extract_access_key_id_from_auth_header(headers: dict[str, str]) -> str | None:
47
46
  auth = headers.get("Authorization") or ""
48
47
 
49
48
  if auth.startswith("AWS4-"):
@@ -67,7 +66,7 @@ def extract_region_from_headers(headers) -> str:
67
66
  return extract_region_from_auth_header(headers) or AWS_REGION_US_EAST_1
68
67
 
69
68
 
70
- def extract_service_name_from_auth_header(headers: dict) -> Optional[str]:
69
+ def extract_service_name_from_auth_header(headers: dict) -> str | None:
71
70
  try:
72
71
  auth_header = headers.get("authorization", "")
73
72
  credential_scope = auth_header.split(",")[0].split()[1]
@@ -1,6 +1,6 @@
1
1
  import copy
2
2
  import time
3
- from typing import Generic, Optional, TypeVar, overload
3
+ from typing import Generic, TypeVar, overload
4
4
 
5
5
  from pydantic import Field
6
6
  from pydantic.dataclasses import dataclass
@@ -47,8 +47,8 @@ class Batcher(Generic[T]):
47
47
  assert batcher.flush() == ["item1", "item2", "item3", "item4"]
48
48
  """
49
49
 
50
- max_count: Optional[int] = Field(default=None, description="Maximum number of items", ge=0)
51
- max_window: Optional[float] = Field(
50
+ max_count: int | None = Field(default=None, description="Maximum number of items", ge=0)
51
+ max_window: float | None = Field(
52
52
  default=None, description="Maximum time window in seconds", ge=0
53
53
  )
54
54
 
@@ -9,9 +9,9 @@ import shlex
9
9
  import signal
10
10
  import threading
11
11
  import time
12
- from collections.abc import Iterable
12
+ from collections.abc import Callable, Iterable
13
13
  from functools import wraps
14
- from typing import Any, Callable, Optional, Union
14
+ from typing import Any
15
15
 
16
16
  from localstack import config, constants
17
17
  from localstack.config import (
@@ -175,7 +175,7 @@ def get_docker_image_details(image_name: str = None) -> dict[str, str]:
175
175
  return result
176
176
 
177
177
 
178
- def get_image_environment_variable(env_name: str) -> Optional[str]:
178
+ def get_image_environment_variable(env_name: str) -> str | None:
179
179
  image_name = get_docker_image_to_start()
180
180
  image_info = DOCKER_CLIENT.inspect_image(image_name)
181
181
  image_envs = image_info["Config"]["Env"]
@@ -544,7 +544,7 @@ class ContainerConfigurators:
544
544
 
545
545
  @staticmethod
546
546
  def gateway_listen(
547
- port: Union[int, Iterable[int], HostAndPort, Iterable[HostAndPort]],
547
+ port: int | Iterable[int] | HostAndPort | Iterable[HostAndPort],
548
548
  ):
549
549
  """
550
550
  Uses the given ports to configure GATEWAY_LISTEN. For instance, ``gateway_listen([4566, 443])`` would
@@ -1000,7 +1000,7 @@ class RunningContainer:
1000
1000
  return
1001
1001
  raise
1002
1002
 
1003
- def inspect(self) -> dict[str, Union[dict, str]]:
1003
+ def inspect(self) -> dict[str, dict | str]:
1004
1004
  return self.container_client.inspect_container(container_name_or_id=self.id)
1005
1005
 
1006
1006
  def attach(self):
@@ -1028,7 +1028,7 @@ class ContainerLogPrinter:
1028
1028
  self.callback = callback
1029
1029
 
1030
1030
  self._closed = threading.Event()
1031
- self._stream: Optional[CancellableStream] = None
1031
+ self._stream: CancellableStream | None = None
1032
1032
 
1033
1033
  def _can_start_streaming(self):
1034
1034
  if self._closed.is_set():
@@ -1338,7 +1338,7 @@ def start_infra_in_docker_detached(console, cli_params: dict[str, Any] = None):
1338
1338
  console.log("detaching")
1339
1339
 
1340
1340
 
1341
- def wait_container_is_ready(timeout: Optional[float] = None):
1341
+ def wait_container_is_ready(timeout: float | None = None):
1342
1342
  """Blocks until the localstack main container is running and the ready marker has been printed."""
1343
1343
  container_name = config.MAIN_CONTAINER_NAME
1344
1344
  started = time.time()
@@ -2,7 +2,7 @@ import logging
2
2
  import time
3
3
  from datetime import datetime, timezone
4
4
  from itertools import islice
5
- from typing import Optional, TypedDict
5
+ from typing import TypedDict
6
6
 
7
7
  from werkzeug import Response as WerkzeugResponse
8
8
 
@@ -20,8 +20,8 @@ LOG = logging.getLogger(__name__)
20
20
  class SqsMetricBatchData(TypedDict, total=False):
21
21
  MetricName: str
22
22
  QueueName: str
23
- Value: Optional[int]
24
- Unit: Optional[str]
23
+ Value: int | None
24
+ Unit: str | None
25
25
 
26
26
 
27
27
  def dimension_lambda(kwargs):
@@ -30,7 +30,7 @@ def dimension_lambda(kwargs):
30
30
 
31
31
 
32
32
  def publish_lambda_metric(
33
- metric, value, kwargs, account_id: Optional[str] = None, region_name: Optional[str] = None
33
+ metric, value, kwargs, account_id: str | None = None, region_name: str | None = None
34
34
  ):
35
35
  # publish metric only if CloudWatch service is available
36
36
  if not is_api_enabled("cloudwatch"):
@@ -155,7 +155,7 @@ def store_cloudwatch_logs(
155
155
  log_stream_name,
156
156
  log_output,
157
157
  start_time=None,
158
- auto_create_group: Optional[bool] = True,
158
+ auto_create_group: bool | None = True,
159
159
  ):
160
160
  if not is_api_enabled("logs"):
161
161
  return
@@ -5,10 +5,9 @@ and manipulate python collection (dicts, list, sets).
5
5
 
6
6
  import logging
7
7
  import re
8
- from collections.abc import Iterable, Iterator, Mapping, Sized
8
+ from collections.abc import Callable, Iterable, Iterator, Mapping, Sized
9
9
  from typing import (
10
10
  Any,
11
- Callable,
12
11
  Optional,
13
12
  TypedDict,
14
13
  TypeVar,
@@ -116,7 +115,7 @@ class PaginatedList(list[_ListType]):
116
115
  next_token: str = None,
117
116
  page_size: int = None,
118
117
  filter_function: Callable[[_ListType], bool] = None,
119
- ) -> tuple[list[_ListType], Optional[str]]:
118
+ ) -> tuple[list[_ListType], str | None]:
120
119
  if filter_function is not None:
121
120
  result_list = list(filter(filter_function, self))
122
121
  else:
@@ -148,7 +147,7 @@ class PaginatedList(list[_ListType]):
148
147
  class CustomExpiryTTLCache(cachetools.TTLCache):
149
148
  """TTLCache that allows to set custom expiry times for individual keys."""
150
149
 
151
- def set_expiry(self, key: Any, ttl: Union[float, int]) -> float:
150
+ def set_expiry(self, key: Any, ttl: float | int) -> float:
152
151
  """Set the expiry of the given key in a TTLCache to (<current_time> + <ttl>)"""
153
152
  with self.timer as time:
154
153
  # note: need to access the internal dunder API here
@@ -315,7 +314,7 @@ def is_list_or_tuple(obj) -> bool:
315
314
  return isinstance(obj, (list, tuple))
316
315
 
317
316
 
318
- def ensure_list(obj: Any, wrap_none=False) -> Optional[list]:
317
+ def ensure_list(obj: Any, wrap_none=False) -> list | None:
319
318
  """Wrap the given object in a list, or return the object itself if it already is a list."""
320
319
  if obj is None and not wrap_none:
321
320
  return obj
@@ -414,7 +413,7 @@ def items_equivalent(list1, list2, comparator):
414
413
  return True
415
414
 
416
415
 
417
- def is_none_or_empty(obj: Union[Optional[str], Optional[list]]) -> bool:
416
+ def is_none_or_empty(obj: str | None | list | None) -> bool:
418
417
  return (
419
418
  obj is None
420
419
  or (isinstance(obj, str) and obj.strip() == "")
@@ -475,7 +474,7 @@ def convert_to_typed_dict(typed_dict: type[T], obj: dict, strict: bool = False)
475
474
  return result
476
475
 
477
476
 
478
- def dict_multi_values(elements: Union[list, dict]) -> dict[str, list[Any]]:
477
+ def dict_multi_values(elements: list | dict) -> dict[str, list[Any]]:
479
478
  """
480
479
  Return a dictionary with the original keys from the list of dictionary and the
481
480
  values are the list of values of the original dictionary.
@@ -516,7 +515,7 @@ def split_list_by(
516
515
  return truthy, falsy
517
516
 
518
517
 
519
- def is_comma_delimited_list(string: str, item_regex: Optional[str] = None) -> bool:
518
+ def is_comma_delimited_list(string: str, item_regex: str | None = None) -> bool:
520
519
  """
521
520
  Checks if the given string is a comma-delimited list of items.
522
521
  The optional `item_regex` parameter specifies the regex pattern for each item in the list.
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  import re
4
- from typing import Callable
4
+ from collections.abc import Callable
5
5
 
6
6
  from requests.models import Response
7
7
 
@@ -2,7 +2,6 @@ import logging
2
2
  import os
3
3
  import re
4
4
  from functools import lru_cache
5
- from typing import Optional
6
5
 
7
6
  from localstack import config, constants
8
7
  from localstack.utils.container_utils.container_client import ContainerException
@@ -13,7 +12,7 @@ LOG = logging.getLogger(__name__)
13
12
 
14
13
 
15
14
  @lru_cache
16
- def get_main_container_network() -> Optional[str]:
15
+ def get_main_container_network() -> str | None:
17
16
  """
18
17
  Gets the main network of the LocalStack container (if we run in one, bridge otherwise)
19
18
  If there are multiple networks connected to the LocalStack container, we choose the first as "main" network
@@ -50,7 +49,7 @@ def get_main_container_network() -> Optional[str]:
50
49
 
51
50
 
52
51
  @lru_cache
53
- def get_endpoint_for_network(network: Optional[str] = None) -> str:
52
+ def get_endpoint_for_network(network: str | None = None) -> str:
54
53
  """
55
54
  Get the LocalStack endpoint (= IP address) on the given network.
56
55
  If a network is given, it will return the IP address/hostname of LocalStack on that network