localstack-core 4.9.3.dev26__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 (68) hide show
  1. localstack/cli/exceptions.py +1 -1
  2. localstack/cli/localstack.py +4 -4
  3. localstack/cli/lpm.py +3 -4
  4. localstack/cli/profiles.py +1 -2
  5. localstack/config.py +12 -12
  6. localstack/packages/api.py +9 -8
  7. localstack/packages/core.py +2 -2
  8. localstack/testing/pytest/cloudformation/fixtures.py +3 -3
  9. localstack/testing/pytest/container.py +4 -5
  10. localstack/testing/pytest/fixtures.py +20 -19
  11. localstack/testing/pytest/marking.py +5 -4
  12. localstack/testing/pytest/stepfunctions/utils.py +4 -3
  13. localstack/testing/pytest/util.py +1 -1
  14. localstack/testing/pytest/validation_tracking.py +1 -2
  15. localstack/utils/analytics/events.py +2 -2
  16. localstack/utils/analytics/metadata.py +1 -2
  17. localstack/utils/analytics/metrics/counter.py +6 -8
  18. localstack/utils/analytics/publisher.py +1 -2
  19. localstack/utils/analytics/service_request_aggregator.py +2 -2
  20. localstack/utils/archives.py +11 -11
  21. localstack/utils/aws/arns.py +7 -7
  22. localstack/utils/aws/aws_responses.py +7 -7
  23. localstack/utils/aws/aws_stack.py +2 -3
  24. localstack/utils/aws/message_forwarding.py +1 -2
  25. localstack/utils/aws/request_context.py +4 -5
  26. localstack/utils/batch_policy.py +3 -3
  27. localstack/utils/bootstrap.py +7 -7
  28. localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
  29. localstack/utils/collections.py +7 -8
  30. localstack/utils/config_listener.py +1 -1
  31. localstack/utils/container_networking.py +2 -3
  32. localstack/utils/container_utils/container_client.py +115 -131
  33. localstack/utils/container_utils/docker_cmd_client.py +42 -42
  34. localstack/utils/container_utils/docker_sdk_client.py +63 -62
  35. localstack/utils/diagnose.py +2 -3
  36. localstack/utils/docker_utils.py +3 -4
  37. localstack/utils/functions.py +3 -2
  38. localstack/utils/http.py +4 -5
  39. localstack/utils/json.py +3 -3
  40. localstack/utils/kinesis/kinesis_connector.py +2 -1
  41. localstack/utils/net.py +6 -6
  42. localstack/utils/no_exit_argument_parser.py +2 -2
  43. localstack/utils/numbers.py +2 -2
  44. localstack/utils/objects.py +6 -5
  45. localstack/utils/patch.py +2 -1
  46. localstack/utils/run.py +10 -9
  47. localstack/utils/scheduler.py +11 -11
  48. localstack/utils/server/tcp_proxy.py +2 -2
  49. localstack/utils/serving.py +2 -3
  50. localstack/utils/strings.py +10 -11
  51. localstack/utils/sync.py +2 -1
  52. localstack/utils/tagging.py +1 -4
  53. localstack/utils/testutil.py +5 -4
  54. localstack/utils/threads.py +2 -2
  55. localstack/utils/time.py +1 -2
  56. localstack/utils/urls.py +1 -3
  57. localstack/version.py +2 -2
  58. {localstack_core-4.9.3.dev26.dist-info → localstack_core-4.9.3.dev27.dist-info}/METADATA +2 -2
  59. {localstack_core-4.9.3.dev26.dist-info → localstack_core-4.9.3.dev27.dist-info}/RECORD +67 -67
  60. localstack_core-4.9.3.dev27.dist-info/plux.json +1 -0
  61. localstack_core-4.9.3.dev26.dist-info/plux.json +0 -1
  62. {localstack_core-4.9.3.dev26.data → localstack_core-4.9.3.dev27.data}/scripts/localstack +0 -0
  63. {localstack_core-4.9.3.dev26.data → localstack_core-4.9.3.dev27.data}/scripts/localstack-supervisor +0 -0
  64. {localstack_core-4.9.3.dev26.data → localstack_core-4.9.3.dev27.data}/scripts/localstack.bat +0 -0
  65. {localstack_core-4.9.3.dev26.dist-info → localstack_core-4.9.3.dev27.dist-info}/WHEEL +0 -0
  66. {localstack_core-4.9.3.dev26.dist-info → localstack_core-4.9.3.dev27.dist-info}/entry_points.txt +0 -0
  67. {localstack_core-4.9.3.dev26.dist-info → localstack_core-4.9.3.dev27.dist-info}/licenses/LICENSE.txt +0 -0
  68. {localstack_core-4.9.3.dev26.dist-info → localstack_core-4.9.3.dev27.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ class CLIError(ClickException):
12
12
  def format_message(self) -> str:
13
13
  return click.style(f"❌ Error: {self.message}", fg="red")
14
14
 
15
- def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None:
15
+ def show(self, file: t.IO[t.Any] | None = None) -> None:
16
16
  if file is None:
17
17
  file = get_text_stderr()
18
18
 
@@ -3,7 +3,7 @@ import logging
3
3
  import os
4
4
  import sys
5
5
  import traceback
6
- from typing import Optional, TypedDict
6
+ from typing import TypedDict
7
7
 
8
8
  import click
9
9
  import requests
@@ -320,8 +320,8 @@ class DockerStatus(TypedDict, total=False):
320
320
  image_tag: str
321
321
  image_id: str
322
322
  image_created: str
323
- container_name: Optional[str]
324
- container_ip: Optional[str]
323
+ container_name: str | None
324
+ container_ip: str | None
325
325
 
326
326
 
327
327
  def _print_docker_status(format_: str) -> None:
@@ -699,7 +699,7 @@ def cmd_logs(follow: bool, tail: int) -> None:
699
699
  metavar="N",
700
700
  )
701
701
  @publish_invocation
702
- def cmd_wait(timeout: Optional[float] = None) -> None:
702
+ def cmd_wait(timeout: float | None = None) -> None:
703
703
  """
704
704
  Wait for the LocalStack runtime to be up and running.
705
705
 
localstack/cli/lpm.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import itertools
2
2
  import logging
3
3
  from multiprocessing.pool import ThreadPool
4
- from typing import Optional
5
4
 
6
5
  import click
7
6
  from rich.console import Console
@@ -75,9 +74,9 @@ def _do_install_package(package: Package, version: str = None, target: InstallTa
75
74
  )
76
75
  def install(
77
76
  package: list[str],
78
- parallel: Optional[int] = 1,
79
- version: Optional[str] = None,
80
- target: Optional[str] = None,
77
+ parallel: int | None = 1,
78
+ version: str | None = None,
79
+ target: str | None = None,
81
80
  ):
82
81
  """Install one or more packages."""
83
82
  try:
@@ -1,7 +1,6 @@
1
1
  import argparse
2
2
  import os
3
3
  import sys
4
- from typing import Optional
5
4
 
6
5
  # important: this needs to be free of localstack imports
7
6
 
@@ -44,7 +43,7 @@ def set_and_remove_profile_from_sys_argv():
44
43
  os.environ["CONFIG_PROFILE"] = profile.strip()
45
44
 
46
45
 
47
- def parse_p_argument(args) -> Optional[str]:
46
+ def parse_p_argument(args) -> str | None:
48
47
  """
49
48
  Lightweight arg parsing to find the first occurrence of ``-p <config>``, or ``-p=<config>`` and return the value of
50
49
  ``<config>`` from the given arguments.
localstack/config.py CHANGED
@@ -10,7 +10,7 @@ import time
10
10
  import warnings
11
11
  from collections import defaultdict
12
12
  from collections.abc import Mapping
13
- from typing import Any, Optional, TypeVar, Union
13
+ from typing import Any, TypeVar
14
14
 
15
15
  from localstack import constants
16
16
  from localstack.constants import (
@@ -209,13 +209,13 @@ class Directories:
209
209
  return str(self.__dict__)
210
210
 
211
211
 
212
- def eval_log_type(env_var_name: str) -> Union[str, bool]:
212
+ def eval_log_type(env_var_name: str) -> str | bool:
213
213
  """Get the log type from environment variable"""
214
214
  ls_log = os.environ.get(env_var_name, "").lower().strip()
215
215
  return ls_log if ls_log in LOG_LEVELS else False
216
216
 
217
217
 
218
- def parse_boolean_env(env_var_name: str) -> Optional[bool]:
218
+ def parse_boolean_env(env_var_name: str) -> bool | None:
219
219
  """Parse the value of the given env variable and return True/False, or None if it is not a boolean value."""
220
220
  value = os.environ.get(env_var_name, "").lower().strip()
221
221
  if value in TRUE_STRINGS:
@@ -650,7 +650,7 @@ class UniqueHostAndPortList(list[HostAndPort]):
650
650
  - Identical identical hosts and ports are de-duped
651
651
  """
652
652
 
653
- def __init__(self, iterable: Union[list[HostAndPort], None] = None):
653
+ def __init__(self, iterable: list[HostAndPort] | None = None):
654
654
  super().__init__(iterable or [])
655
655
  self._ensure_unique()
656
656
 
@@ -1509,10 +1509,10 @@ def get_protocol() -> str:
1509
1509
 
1510
1510
 
1511
1511
  def external_service_url(
1512
- host: Optional[str] = None,
1513
- port: Optional[int] = None,
1514
- protocol: Optional[str] = None,
1515
- subdomains: Optional[str] = None,
1512
+ host: str | None = None,
1513
+ port: int | None = None,
1514
+ protocol: str | None = None,
1515
+ subdomains: str | None = None,
1516
1516
  ) -> str:
1517
1517
  """Returns a service URL (e.g., SQS queue URL) to an external client (e.g., boto3) potentially running on another
1518
1518
  machine than LocalStack. The configurations LOCALSTACK_HOST and USE_SSL can customize these returned URLs.
@@ -1529,10 +1529,10 @@ def external_service_url(
1529
1529
 
1530
1530
 
1531
1531
  def internal_service_url(
1532
- host: Optional[str] = None,
1533
- port: Optional[int] = None,
1534
- protocol: Optional[str] = None,
1535
- subdomains: Optional[str] = None,
1532
+ host: str | None = None,
1533
+ port: int | None = None,
1534
+ protocol: str | None = None,
1535
+ subdomains: str | None = None,
1536
1536
  ) -> str:
1537
1537
  """Returns a service URL for internal use within LocalStack (i.e., same host).
1538
1538
  The configuration USE_SSL can customize these returned URLs but LOCALSTACK_HOST has no effect.
@@ -3,10 +3,11 @@ import functools
3
3
  import logging
4
4
  import os
5
5
  from collections import defaultdict
6
+ from collections.abc import Callable
6
7
  from enum import Enum
7
8
  from inspect import getmodule
8
- from threading import RLock
9
- from typing import Any, Callable, Generic, Optional, ParamSpec, TypeVar
9
+ from threading import Lock, RLock
10
+ from typing import Any, Generic, ParamSpec, TypeVar
10
11
 
11
12
  from plux import Plugin, PluginManager, PluginSpec # type: ignore
12
13
 
@@ -56,7 +57,7 @@ class PackageInstaller(abc.ABC):
56
57
  multiple versions).
57
58
  """
58
59
 
59
- def __init__(self, name: str, version: str, install_lock: Optional[RLock] = None):
60
+ def __init__(self, name: str, version: str, install_lock: Lock | None = None):
60
61
  """
61
62
  :param name: technical package name, f.e. "opensearch"
62
63
  :param version: version of the package to install
@@ -70,7 +71,7 @@ class PackageInstaller(abc.ABC):
70
71
  self.install_lock = install_lock or RLock()
71
72
  self._setup_for_target: dict[InstallTarget, bool] = defaultdict(lambda: False)
72
73
 
73
- def install(self, target: Optional[InstallTarget] = None) -> None:
74
+ def install(self, target: InstallTarget | None = None) -> None:
74
75
  """
75
76
  Performs the package installation.
76
77
 
@@ -210,7 +211,7 @@ class Package(abc.ABC, Generic[T]):
210
211
  """
211
212
  return self.get_installer(version).get_installed_dir()
212
213
 
213
- def install(self, version: str | None = None, target: Optional[InstallTarget] = None) -> None:
214
+ def install(self, version: str | None = None, target: InstallTarget | None = None) -> None:
214
215
  """
215
216
  Installs the package in the given version in the preferred target location.
216
217
  :param version: version of the package to install. If None, the default version of the package will be used.
@@ -274,7 +275,7 @@ class MultiPackageInstaller(PackageInstaller):
274
275
  assert len(package_installer) > 0
275
276
  self.package_installer = package_installer
276
277
 
277
- def install(self, target: Optional[InstallTarget] = None) -> None:
278
+ def install(self, target: InstallTarget | None = None) -> None:
278
279
  """
279
280
  Installs the different packages this installer is composed of.
280
281
 
@@ -356,7 +357,7 @@ class PackagesPluginManager(PluginManager[PackagesPlugin]): # type: ignore[misc
356
357
  )
357
358
 
358
359
  def get_packages(
359
- self, package_names: list[str], version: Optional[str] = None
360
+ self, package_names: list[str], version: str | None = None
360
361
  ) -> list[Package[PackageInstaller]]:
361
362
  # Plugin names are unique, but there could be multiple packages with the same name in different scopes
362
363
  plugin_specs_per_name = defaultdict(list)
@@ -390,7 +391,7 @@ T2 = TypeVar("T2")
390
391
  def package(
391
392
  name: str | None = None,
392
393
  scope: str = "community",
393
- should_load: Optional[Callable[[], bool]] = None,
394
+ should_load: Callable[[], bool] | None = None,
394
395
  ) -> Callable[[Callable[[], Package[Any] | list[Package[Any]]]], PluginSpec]:
395
396
  """
396
397
  Decorator for marking methods that create Package instances as a PackagePlugin.
@@ -4,7 +4,7 @@ import re
4
4
  from abc import ABC
5
5
  from functools import lru_cache
6
6
  from sys import version_info
7
- from typing import Any, Optional
7
+ from typing import Any
8
8
 
9
9
  import requests
10
10
 
@@ -238,7 +238,7 @@ class NodePackageInstaller(ExecutableInstaller):
238
238
  self,
239
239
  package_name: str,
240
240
  version: str,
241
- package_spec: Optional[str] = None,
241
+ package_spec: str | None = None,
242
242
  main_module: str = "main.js",
243
243
  ):
244
244
  """
@@ -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(
@@ -6,7 +6,8 @@ import os
6
6
  import re
7
7
  import textwrap
8
8
  import time
9
- from typing import TYPE_CHECKING, Any, Callable, Optional, Unpack
9
+ from collections.abc import Callable
10
+ from typing import TYPE_CHECKING, Any, Unpack
10
11
 
11
12
  import botocore.auth
12
13
  import botocore.config
@@ -357,8 +358,8 @@ def sqs_create_queue(aws_client):
357
358
  def sqs_receive_messages_delete(aws_client):
358
359
  def factory(
359
360
  queue_url: str,
360
- expected_messages: Optional[int] = None,
361
- wait_time: Optional[int] = 5,
361
+ expected_messages: int | None = None,
362
+ wait_time: int | None = 5,
362
363
  ):
363
364
  response = aws_client.sqs.receive_message(
364
365
  QueueUrl=queue_url,
@@ -706,7 +707,7 @@ def route53_hosted_zone(aws_client):
706
707
  def transcribe_create_job(s3_bucket, aws_client):
707
708
  job_names = []
708
709
 
709
- def _create_job(audio_file: str, params: Optional[dict[str, Any]] = None) -> str:
710
+ def _create_job(audio_file: str, params: dict[str, Any] | None = None) -> str:
710
711
  s3_key = "test-clip.wav"
711
712
 
712
713
  if not params:
@@ -1085,18 +1086,18 @@ def deploy_cfn_template(
1085
1086
 
1086
1087
  def _deploy(
1087
1088
  *,
1088
- is_update: Optional[bool] = False,
1089
- stack_name: Optional[str] = None,
1090
- change_set_name: Optional[str] = None,
1091
- template: Optional[str] = None,
1092
- template_path: Optional[str | os.PathLike] = None,
1093
- template_mapping: Optional[dict[str, Any]] = None,
1094
- parameters: Optional[dict[str, str]] = None,
1095
- role_arn: Optional[str] = None,
1096
- max_wait: Optional[int] = None,
1097
- delay_between_polls: Optional[int] = 2,
1098
- custom_aws_client: Optional[ServiceLevelClientFactory] = None,
1099
- raw_parameters: Optional[list[Parameter]] = None,
1089
+ is_update: bool | None = False,
1090
+ stack_name: str | None = None,
1091
+ change_set_name: str | None = None,
1092
+ template: str | None = None,
1093
+ template_path: str | os.PathLike | None = None,
1094
+ template_mapping: dict[str, Any] | None = None,
1095
+ parameters: dict[str, str] | None = None,
1096
+ role_arn: str | None = None,
1097
+ max_wait: int | None = None,
1098
+ delay_between_polls: int | None = 2,
1099
+ custom_aws_client: ServiceLevelClientFactory | None = None,
1100
+ raw_parameters: list[Parameter] | None = None,
1100
1101
  ) -> DeployResult:
1101
1102
  if is_update:
1102
1103
  assert stack_name
@@ -1262,7 +1263,7 @@ def _has_stack_status(cfn_client, statuses: list[str]):
1262
1263
 
1263
1264
  @pytest.fixture
1264
1265
  def is_change_set_finished(aws_client):
1265
- def _is_change_set_finished(change_set_id: str, stack_name: Optional[str] = None):
1266
+ def _is_change_set_finished(change_set_id: str, stack_name: str | None = None):
1266
1267
  def _inner():
1267
1268
  kwargs = {"ChangeSetName": change_set_id}
1268
1269
  if stack_name:
@@ -1993,7 +1994,7 @@ def setup_sender_email_address(ses_verify_identity):
1993
1994
  email address and verify them.
1994
1995
  """
1995
1996
 
1996
- def inner(sender_email_address: Optional[str] = None) -> str:
1997
+ def inner(sender_email_address: str | None = None) -> str:
1997
1998
  if is_aws_cloud():
1998
1999
  if sender_email_address is None:
1999
2000
  raise ValueError(
@@ -2252,7 +2253,7 @@ def assert_host_customisation(monkeypatch):
2252
2253
  def asserter(
2253
2254
  url: str,
2254
2255
  *,
2255
- custom_host: Optional[str] = None,
2256
+ custom_host: str | None = None,
2256
2257
  ):
2257
2258
  if custom_host is not None:
2258
2259
  assert custom_host in url, f"Could not find `{custom_host}` in `{url}`"
@@ -3,7 +3,8 @@ Custom pytest mark typings
3
3
  """
4
4
 
5
5
  import os
6
- from typing import TYPE_CHECKING, Callable, Optional
6
+ from collections.abc import Callable
7
+ from typing import TYPE_CHECKING
7
8
 
8
9
  import pytest
9
10
  from _pytest.config import PytestPluginManager
@@ -36,13 +37,13 @@ class SkipSnapshotVerifyMarker:
36
37
  def __call__(
37
38
  self,
38
39
  *,
39
- paths: "Optional[list[str]]" = None,
40
- condition: "Optional[Callable[[...], bool]]" = None,
40
+ paths: "list[str] | None" = None,
41
+ condition: "Callable[[...], bool] | None" = None,
41
42
  ): ...
42
43
 
43
44
 
44
45
  class MultiRuntimeMarker:
45
- def __call__(self, *, scenario: str, runtimes: Optional[list[str]] = None): ...
46
+ def __call__(self, *, scenario: str, runtimes: list[str] | None = None): ...
46
47
 
47
48
 
48
49
  class SnapshotMarkers:
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import logging
3
- from typing import Callable, Final, Optional
3
+ from collections.abc import Callable
4
+ from typing import Final
4
5
 
5
6
  from botocore.exceptions import ClientError
6
7
  from jsonpath_ng.ext import parse
@@ -402,8 +403,8 @@ def create_state_machine_with_iam_role(
402
403
  create_state_machine,
403
404
  snapshot,
404
405
  definition: Definition,
405
- logging_configuration: Optional[LoggingConfiguration] = None,
406
- state_machine_name: Optional[str] = None,
406
+ logging_configuration: LoggingConfiguration | None = None,
407
+ state_machine_name: str | None = None,
407
408
  state_machine_type: StateMachineType = StateMachineType.STANDARD,
408
409
  ):
409
410
  snf_role_arn = create_state_machine_iam_role(target_aws_client=target_aws_client)
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import pwd
3
+ from collections.abc import Callable
3
4
  from multiprocessing import Process, ProcessError
4
- from typing import Callable
5
5
 
6
6
 
7
7
  def run_as_os_user(target: Callable, uid: str | int, gid: str | int = None):
@@ -9,7 +9,6 @@ import datetime
9
9
  import json
10
10
  import os
11
11
  from pathlib import Path
12
- from typing import Optional
13
12
 
14
13
  import pytest
15
14
  from pluggy import Result
@@ -28,7 +27,7 @@ Stores information from call execution phase about whether the test failed.
28
27
  """
29
28
 
30
29
 
31
- def find_validation_data_for_item(item: pytest.Item) -> Optional[dict]:
30
+ def find_validation_data_for_item(item: pytest.Item) -> dict | None:
32
31
  base_path = os.path.join(item.fspath.dirname, item.fspath.purebasename)
33
32
  snapshot_path = f"{base_path}.validation.json"
34
33
 
@@ -1,8 +1,8 @@
1
1
  import abc
2
2
  import dataclasses
3
- from typing import Any, Union
3
+ from typing import Any
4
4
 
5
- EventPayload = Union[dict[str, Any], Any] # FIXME: better typing
5
+ EventPayload = dict[str, Any] | Any # FIXME: better typing
6
6
 
7
7
 
8
8
  @dataclasses.dataclass
@@ -2,7 +2,6 @@ import dataclasses
2
2
  import logging
3
3
  import os
4
4
  import platform
5
- from typing import Optional
6
5
 
7
6
  from localstack import config
8
7
  from localstack.constants import VERSION
@@ -201,7 +200,7 @@ def _generate_machine_id() -> str:
201
200
  return f"gen_{long_uid()[:12]}"
202
201
 
203
202
 
204
- def get_api_key_or_auth_token() -> Optional[str]:
203
+ def get_api_key_or_auth_token() -> str | None:
205
204
  # TODO: this is duplicated code from ext, but should probably migrate that to localstack
206
205
  auth_token = os.environ.get("LOCALSTACK_AUTH_TOKEN", "").strip("'\" ")
207
206
  if auth_token:
@@ -1,7 +1,7 @@
1
1
  import threading
2
2
  from collections import defaultdict
3
3
  from dataclasses import dataclass
4
- from typing import Any, Optional, Union
4
+ from typing import Any
5
5
 
6
6
  from localstack import config
7
7
 
@@ -38,7 +38,7 @@ class LabeledCounterPayload:
38
38
  value: int
39
39
  type: str
40
40
  schema_version: int
41
- labels: dict[str, Union[str, float]]
41
+ labels: dict[str, str | float]
42
42
 
43
43
  def as_dict(self) -> dict[str, Any]:
44
44
  payload_dict = {
@@ -140,10 +140,8 @@ class LabeledCounter(Metric):
140
140
 
141
141
  _type: str
142
142
  _labels: list[str]
143
- _label_values: tuple[Optional[Union[str, float]], ...]
144
- _counters_by_label_values: defaultdict[
145
- tuple[Optional[Union[str, float]], ...], ThreadSafeCounter
146
- ]
143
+ _label_values: tuple[str | float | None, ...]
144
+ _counters_by_label_values: defaultdict[tuple[str | float | None, ...], ThreadSafeCounter]
147
145
 
148
146
  def __init__(self, namespace: str, name: str, labels: list[str], schema_version: int = 1):
149
147
  super().__init__(namespace=namespace, name=name, schema_version=schema_version)
@@ -162,7 +160,7 @@ class LabeledCounter(Metric):
162
160
  self._counters_by_label_values = defaultdict(ThreadSafeCounter)
163
161
  MetricRegistry().register(self)
164
162
 
165
- def labels(self, **kwargs: Union[str, float, None]) -> ThreadSafeCounter:
163
+ def labels(self, **kwargs: str | float | None) -> ThreadSafeCounter:
166
164
  """
167
165
  Create a scoped counter instance with specific label values.
168
166
 
@@ -198,7 +196,7 @@ class LabeledCounter(Metric):
198
196
  )
199
197
 
200
198
  # Create labels dictionary
201
- labels_dict = dict(zip(self._labels, label_values))
199
+ labels_dict = dict(zip(self._labels, label_values, strict=False))
202
200
 
203
201
  payload.append(
204
202
  LabeledCounterPayload(
@@ -4,7 +4,6 @@ import logging
4
4
  import threading
5
5
  import time
6
6
  from queue import Full, Queue
7
- from typing import Optional
8
7
 
9
8
  from localstack import config
10
9
  from localstack.utils.threads import start_thread, start_worker_thread
@@ -93,7 +92,7 @@ class PublisherBuffer(EventHandler):
93
92
  self._stopping.set()
94
93
  self._command_queue.put(self._cmd_stop)
95
94
 
96
- def close_sync(self, timeout: Optional[float] = None):
95
+ def close_sync(self, timeout: float | None = None):
97
96
  self.close()
98
97
  return self._stopped.wait(timeout)
99
98
 
@@ -2,7 +2,7 @@ import datetime
2
2
  import logging
3
3
  import threading
4
4
  from collections import Counter
5
- from typing import NamedTuple, Optional
5
+ from typing import NamedTuple
6
6
 
7
7
  from localstack import config
8
8
  from localstack.runtime.shutdown import SHUTDOWN_HANDLERS
@@ -20,7 +20,7 @@ class ServiceRequestInfo(NamedTuple):
20
20
  service: str
21
21
  operation: str
22
22
  status_code: int
23
- err_type: Optional[str] = None
23
+ err_type: str | None = None
24
24
 
25
25
 
26
26
  class ServiceRequestAggregator:
@@ -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(