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
localstack/utils/files.py CHANGED
@@ -80,9 +80,26 @@ def save_file(file, content, append=False, permissions=None):
80
80
  f.flush()
81
81
 
82
82
 
83
- def load_file(file_path: str, default=None, mode=None):
83
+ def load_file(
84
+ file_path: str | os.PathLike,
85
+ default: str | bytes | None = None,
86
+ mode: str | None = None,
87
+ strict: bool = False,
88
+ ) -> str | bytes | None:
89
+ """
90
+ Return file contents
91
+
92
+ :param file_path: path of the file
93
+ :param default: if strict=False then return this value if the file does not exist
94
+ :param mode: mode to open the file with (e.g. `r`, `rw`)
95
+ :param strict: raise an error if the file path is not a file
96
+ :return: the file contents
97
+ """
84
98
  if not os.path.isfile(file_path):
85
- return default
99
+ if strict:
100
+ raise FileNotFoundError(file_path)
101
+ else:
102
+ return default
86
103
  if not mode:
87
104
  mode = "r"
88
105
  with open(file_path, mode) as f:
@@ -288,7 +305,7 @@ def cleanup_tmp_files():
288
305
  del TMP_FILES[:]
289
306
 
290
307
 
291
- def new_tmp_file(suffix: str = None, dir: str = None) -> str:
308
+ def new_tmp_file(suffix: str | None = None, dir: str | None = None) -> str:
292
309
  """Return a path to a new temporary file."""
293
310
  tmp_file, tmp_path = tempfile.mkstemp(suffix=suffix, dir=dir)
294
311
  os.close(tmp_file)
@@ -296,8 +313,15 @@ def new_tmp_file(suffix: str = None, dir: str = None) -> str:
296
313
  return tmp_path
297
314
 
298
315
 
299
- def new_tmp_dir(dir: str = None):
300
- folder = new_tmp_file(dir=dir)
301
- rm_rf(folder)
302
- mkdir(folder)
316
+ def new_tmp_dir(dir: str | None = None, mode: int = 0o777) -> str:
317
+ """
318
+ Create a new temporary directory with the specified permissions. The directory is added to the tracked temporary
319
+ files.
320
+ :param dir: parent directory for the temporary directory to be created. Systems's default otherwise.
321
+ :param mode: file permission for the directory (default: 0o777)
322
+ :return: the absolute path of the created directory
323
+ """
324
+ folder = tempfile.mkdtemp(dir=dir)
325
+ TMP_FILES.append(folder)
326
+ idempotent_chmod(folder, mode=mode)
303
327
  return folder
@@ -3,7 +3,8 @@
3
3
  import functools
4
4
  import inspect
5
5
  import logging
6
- from typing import Any, Callable, Optional
6
+ from collections.abc import Callable
7
+ from typing import Any
7
8
 
8
9
  LOG = logging.getLogger(__name__)
9
10
 
@@ -20,7 +21,7 @@ def run_safe(_python_lambda, *args, _default=None, **kwargs):
20
21
 
21
22
  def call_safe(
22
23
  func: Callable, args: tuple = None, kwargs: dict = None, exception_message: str = None
23
- ) -> Optional[Any]:
24
+ ) -> Any | None:
24
25
  """
25
26
  Call the given function with the given arguments, and if it fails, log the given exception_message.
26
27
  If logging.DEBUG is set for the logger, then we also log the traceback.
localstack/utils/http.py CHANGED
@@ -2,7 +2,6 @@ import logging
2
2
  import math
3
3
  import os
4
4
  import re
5
- from typing import Optional, Union
6
5
  from urllib.parse import parse_qs, parse_qsl, urlencode, urlparse, urlunparse
7
6
 
8
7
  import requests
@@ -54,7 +53,7 @@ def create_chunked_data(data, chunk_size: int = 80):
54
53
  return ret
55
54
 
56
55
 
57
- def canonicalize_headers(headers: Union[dict, CaseInsensitiveDict]) -> dict:
56
+ def canonicalize_headers(headers: dict | CaseInsensitiveDict) -> dict:
58
57
  if not headers:
59
58
  return headers
60
59
 
@@ -103,7 +102,7 @@ def add_query_params_to_url(uri: str, query_params: dict) -> str:
103
102
 
104
103
 
105
104
  def make_http_request(
106
- url: str, data: Union[bytes, str] = None, headers: dict[str, str] = None, method: str = "GET"
105
+ url: str, data: bytes | str = None, headers: dict[str, str] = None, method: str = "GET"
107
106
  ) -> Response:
108
107
  return requests.request(
109
108
  url=url, method=method, headers=headers, data=data, auth=NetrcBypassAuth(), verify=False
@@ -179,7 +178,7 @@ def download(
179
178
  path: str,
180
179
  verify_ssl: bool = True,
181
180
  timeout: float = None,
182
- request_headers: Optional[dict] = None,
181
+ request_headers: dict | None = None,
183
182
  quiet: bool = False,
184
183
  ) -> None:
185
184
  """Downloads file at url to the given path. Raises TimeoutError if the optional timeout (in secs) is reached.
@@ -291,7 +290,7 @@ def download_github_artifact(url: str, target_file: str, timeout: int = None):
291
290
  Optionally allows to define a timeout in seconds."""
292
291
 
293
292
  def do_download(
294
- download_url: str, request_headers: Optional[dict] = None, print_error: bool = False
293
+ download_url: str, request_headers: dict | None = None, print_error: bool = False
295
294
  ):
296
295
  try:
297
296
  download(download_url, target_file, timeout=timeout, request_headers=request_headers)
localstack/utils/json.py CHANGED
@@ -5,7 +5,7 @@ import logging
5
5
  import os
6
6
  from datetime import date, datetime
7
7
  from json import JSONDecodeError
8
- from typing import Any, Union
8
+ from typing import Any
9
9
 
10
10
  from localstack.config import HostAndPort
11
11
 
@@ -63,9 +63,9 @@ class FileMappedDocument(dict):
63
63
  concurrent writes, run load(). To save and overwrite the current document on disk, run save().
64
64
  """
65
65
 
66
- path: Union[str, os.PathLike]
66
+ path: str | os.PathLike
67
67
 
68
- def __init__(self, path: Union[str, os.PathLike], mode=0o664):
68
+ def __init__(self, path: str | os.PathLike, mode=0o664):
69
69
  super().__init__()
70
70
  self.path = path
71
71
  self.mode = mode
@@ -169,10 +169,24 @@ def extract_jsonpath(value, path):
169
169
  return result
170
170
 
171
171
 
172
- def assign_to_path(target, path: str, value, delimiter: str = "."):
172
+ def assign_to_path(target: dict, path: str, value: any, delimiter: str = ".") -> dict:
173
+ """Assign the given value to a dict. If the path doesn't exist in the target dict, it will be created.
174
+ The delimiter can be used to provide a path with a different delimiter.
175
+
176
+ Examples:
177
+ - assign_to_path({}, "a", "b") => {"a": "b"}
178
+ - assign_to_path({}, "a.b.c", "d") => {"a": {"b": {"c": "d"}}}
179
+ - assign_to_path({}, "a.b/c", "d", delimiter="/") => {"a.b": {"c": "d"}}
180
+
181
+ """
173
182
  parts = path.strip(delimiter).split(delimiter)
183
+
184
+ if len(parts) == 1:
185
+ target[parts[0]] = value
186
+ return target
187
+
174
188
  path_to_parent = delimiter.join(parts[:-1])
175
- parent = extract_from_jsonpointer_path(target, path_to_parent, auto_create=True)
189
+ parent = extract_from_jsonpointer_path(target, path_to_parent, delimiter, auto_create=True)
176
190
  if not isinstance(parent, dict):
177
191
  LOG.debug(
178
192
  'Unable to find parent (type %s) for path "%s" in object: %s',
@@ -6,7 +6,8 @@ import re
6
6
  import socket
7
7
  import tempfile
8
8
  import threading
9
- from typing import Any, Callable
9
+ from collections.abc import Callable
10
+ from typing import Any
10
11
 
11
12
  from amazon_kclpy import kcl
12
13
  from amazon_kclpy.v2 import processor
localstack/utils/net.py CHANGED
@@ -5,7 +5,7 @@ import socket
5
5
  import threading
6
6
  from collections.abc import MutableMapping
7
7
  from contextlib import closing
8
- from typing import Any, NamedTuple, Optional, Union
8
+ from typing import Any, NamedTuple
9
9
  from urllib.parse import urlparse
10
10
 
11
11
  import dns.resolver
@@ -50,14 +50,14 @@ class Port(NamedTuple):
50
50
 
51
51
 
52
52
  # simple helper type to encapsulate int/Port argument types
53
- IntOrPort = Union[int, Port]
53
+ IntOrPort = int | Port
54
54
 
55
55
 
56
56
  def is_port_open(
57
- port_or_url: Union[int, str],
57
+ port_or_url: int | str,
58
58
  http_path: str = None,
59
59
  expect_success: bool = True,
60
- protocols: Optional[Union[str, list[str]]] = None,
60
+ protocols: str | list[str] | None = None,
61
61
  quiet: bool = True,
62
62
  ):
63
63
  from localstack.utils.http import safe_requests
@@ -275,7 +275,7 @@ def get_free_tcp_port_range(num_ports: int, max_attempts: int = 50) -> "PortRang
275
275
  raise PortNotAvailableException("reached max_attempts when trying to find port range")
276
276
 
277
277
 
278
- def resolve_hostname(hostname: str) -> Optional[str]:
278
+ def resolve_hostname(hostname: str) -> str | None:
279
279
  """Resolve the given hostname and return its IP address, or None if it cannot be resolved."""
280
280
  try:
281
281
  return socket.gethostbyname(hostname)
@@ -362,7 +362,7 @@ class PortRange:
362
362
  """
363
363
  return range(self.start, self.end + 1)
364
364
 
365
- def reserve_port(self, port: Optional[IntOrPort] = None, duration: Optional[int] = None) -> int:
365
+ def reserve_port(self, port: IntOrPort | None = None, duration: int | None = None) -> int:
366
366
  """
367
367
  Reserves the given port (if it is still free). If the given port is None, it reserves a free port from the
368
368
  configured port range for external services. If a port is given, it has to be within the configured
@@ -1,6 +1,6 @@
1
1
  import argparse
2
2
  import logging
3
- from typing import NoReturn, Optional
3
+ from typing import NoReturn
4
4
 
5
5
  LOG = logging.getLogger(__name__)
6
6
 
@@ -12,7 +12,7 @@ class NoExitArgumentParser(argparse.ArgumentParser):
12
12
  * ArgumentParser subclassing example: https://stackoverflow.com/a/59072378/6875981
13
13
  """
14
14
 
15
- def exit(self, status: int = ..., message: Optional[str] = ...) -> NoReturn:
15
+ def exit(self, status: int = ..., message: str | None = ...) -> NoReturn:
16
16
  LOG.warning("Error in argument parser but preventing exit: %s", message)
17
17
 
18
18
  def error(self, message: str) -> NoReturn:
@@ -1,4 +1,4 @@
1
- from typing import Any, Union
1
+ from typing import Any
2
2
 
3
3
 
4
4
  def format_number(number: float, decimals: int = 2):
@@ -11,6 +11,13 @@ def format_number(number: float, decimals: int = 2):
11
11
 
12
12
 
13
13
  def is_number(s: Any) -> bool:
14
+ # booleans inherit from int
15
+ #
16
+ # >>> a.__class__.__mro__
17
+ # (<class 'bool'>, <class 'int'>, <class 'object'>)
18
+ if s is False or s is True:
19
+ return False
20
+
14
21
  try:
15
22
  float(s) # for int, long and float
16
23
  return True
@@ -18,7 +25,7 @@ def is_number(s: Any) -> bool:
18
25
  return False
19
26
 
20
27
 
21
- def to_number(s: Any) -> Union[int, float]:
28
+ def to_number(s: Any) -> int | float:
22
29
  """Cast the string representation of the given object to a number (int or float), or raise ValueError."""
23
30
  try:
24
31
  return int(str(s))
@@ -1,12 +1,13 @@
1
1
  import functools
2
2
  import re
3
3
  import threading
4
- from typing import Any, Callable, Generic, Optional, TypeVar, Union
4
+ from collections.abc import Callable
5
+ from typing import Any, Generic, TypeVar
5
6
 
6
7
  from .collections import ensure_list
7
8
  from .strings import first_char_to_lower, first_char_to_upper
8
9
 
9
- ComplexType = Union[list, dict, object]
10
+ ComplexType = list | dict | object
10
11
 
11
12
  _T = TypeVar("_T")
12
13
 
@@ -16,7 +17,7 @@ class Value(Generic[_T]):
16
17
  Simple value container.
17
18
  """
18
19
 
19
- value: Optional[_T]
20
+ value: _T | None
20
21
 
21
22
  def __init__(self, value: _T = None) -> None:
22
23
  self.value = value
@@ -30,7 +31,7 @@ class Value(Generic[_T]):
30
31
  def is_set(self) -> bool:
31
32
  return self.value is not None
32
33
 
33
- def get(self) -> Optional[_T]:
34
+ def get(self) -> _T | None:
34
35
  return self.value
35
36
 
36
37
  def __bool__(self):
@@ -136,7 +137,7 @@ def fully_qualified_class_name(klass: type) -> str:
136
137
  return f"{klass.__module__}.{klass.__name__}"
137
138
 
138
139
 
139
- def not_none_or(value: Optional[Any], alternative: Any) -> Any:
140
+ def not_none_or(value: Any | None, alternative: Any) -> Any:
140
141
  """Return 'value' if it is not None, or 'alternative' otherwise."""
141
142
  return value if value is not None else alternative
142
143
 
localstack/utils/patch.py CHANGED
@@ -1,7 +1,8 @@
1
1
  import functools
2
2
  import inspect
3
3
  import types
4
- from typing import Any, Callable
4
+ from collections.abc import Callable
5
+ from typing import Any
5
6
 
6
7
 
7
8
  def get_defining_object(method):
localstack/utils/run.py CHANGED
@@ -7,9 +7,10 @@ import subprocess
7
7
  import sys
8
8
  import threading
9
9
  import time
10
+ from collections.abc import Callable
10
11
  from functools import lru_cache
11
12
  from queue import Queue
12
- from typing import Any, AnyStr, Callable, Optional, Union
13
+ from typing import Any, AnyStr
13
14
 
14
15
  from localstack import config
15
16
 
@@ -23,19 +24,19 @@ LOG = logging.getLogger(__name__)
23
24
 
24
25
 
25
26
  def run(
26
- cmd: Union[str, list[str]],
27
+ cmd: str | list[str],
27
28
  print_error=True,
28
29
  asynchronous=False,
29
30
  stdin=False,
30
31
  stderr=subprocess.STDOUT,
31
32
  outfile=None,
32
- env_vars: Optional[dict[AnyStr, AnyStr]] = None,
33
+ env_vars: dict[AnyStr, AnyStr] | None = None,
33
34
  inherit_cwd=False,
34
35
  inherit_env=True,
35
36
  tty=False,
36
37
  shell=True,
37
38
  cwd: str = None,
38
- ) -> Union[str, subprocess.Popen]:
39
+ ) -> str | subprocess.Popen:
39
40
  LOG.debug("Executing command: %s", cmd)
40
41
  env_dict = os.environ.copy() if inherit_env else {}
41
42
  if env_vars:
@@ -202,7 +203,7 @@ def get_os_user() -> str:
202
203
  return run("whoami").strip()
203
204
 
204
205
 
205
- def to_str(obj: Union[str, bytes], errors="strict"):
206
+ def to_str(obj: str | bytes, errors="strict"):
206
207
  return obj.decode(config.DEFAULT_ENCODING, errors) if isinstance(obj, bytes) else obj
207
208
 
208
209
 
@@ -211,9 +212,9 @@ class ShellCommandThread(FuncThread):
211
212
 
212
213
  def __init__(
213
214
  self,
214
- cmd: Union[str, list[str]],
215
+ cmd: str | list[str],
215
216
  params: Any = None,
216
- outfile: Union[str, int] = None,
217
+ outfile: str | int = None,
217
218
  env_vars: dict[str, str] = None,
218
219
  stdin: bool = False,
219
220
  auto_restart: bool = False,
@@ -223,8 +224,8 @@ class ShellCommandThread(FuncThread):
223
224
  log_listener: Callable = None,
224
225
  stop_listener: Callable = None,
225
226
  strip_color: bool = False,
226
- name: Optional[str] = None,
227
- cwd: Optional[str] = None,
227
+ name: str | None = None,
228
+ cwd: str | None = None,
228
229
  ):
229
230
  params = params if params is not None else {}
230
231
  env_vars = env_vars if env_vars is not None else {}
@@ -1,9 +1,9 @@
1
1
  import queue
2
2
  import threading
3
3
  import time
4
- from collections.abc import Mapping
4
+ from collections.abc import Callable, Mapping
5
5
  from concurrent.futures import Executor
6
- from typing import Any, Callable, Optional, Union
6
+ from typing import Any
7
7
 
8
8
 
9
9
  class ScheduledTask:
@@ -14,12 +14,12 @@ class ScheduledTask:
14
14
  def __init__(
15
15
  self,
16
16
  task: Callable,
17
- period: Optional[float] = None,
17
+ period: float | None = None,
18
18
  fixed_rate: bool = True,
19
- start: Optional[float] = None,
19
+ start: float | None = None,
20
20
  on_error: Callable[[Exception], None] = None,
21
- args: Optional[Union[tuple, list]] = None,
22
- kwargs: Optional[Mapping[str, Any]] = None,
21
+ args: tuple | list | None = None,
22
+ kwargs: Mapping[str, Any] | None = None,
23
23
  ) -> None:
24
24
  super().__init__()
25
25
  self.task = task
@@ -76,7 +76,7 @@ class Scheduler:
76
76
 
77
77
  POISON = (-1, "__POISON__")
78
78
 
79
- def __init__(self, executor: Optional[Executor] = None) -> None:
79
+ def __init__(self, executor: Executor | None = None) -> None:
80
80
  """
81
81
  Creates a new Scheduler. If an executor is passed, then that executor will be used to run the scheduled tasks
82
82
  asynchronously, otherwise they will be executed synchronously inside the event loop. Running tasks
@@ -94,12 +94,12 @@ class Scheduler:
94
94
  def schedule(
95
95
  self,
96
96
  func: Callable,
97
- period: Optional[float] = None,
97
+ period: float | None = None,
98
98
  fixed_rate: bool = True,
99
- start: Optional[float] = None,
99
+ start: float | None = None,
100
100
  on_error: Callable[[Exception], None] = None,
101
- args: Optional[Union[tuple, list[Any]]] = None,
102
- kwargs: Optional[Mapping[str, Any]] = None,
101
+ args: tuple | list[Any] | None = None,
102
+ kwargs: Mapping[str, Any] | None = None,
103
103
  ) -> ScheduledTask:
104
104
  """
105
105
  Schedules a given task (function call).
@@ -1,8 +1,8 @@
1
1
  import logging
2
2
  import select
3
3
  import socket
4
+ from collections.abc import Callable
4
5
  from concurrent.futures import ThreadPoolExecutor
5
- from typing import Callable
6
6
 
7
7
  from localstack.utils.serving import Server
8
8
 
@@ -93,7 +93,7 @@ class TCPProxy(Server):
93
93
  try:
94
94
  src_socket, _ = self._server_socket.accept()
95
95
  self._thread_pool.submit(self._handle_request, src_socket)
96
- except socket.timeout:
96
+ except TimeoutError:
97
97
  pass
98
98
  except OSError as e:
99
99
  # avoid creating an error message if OSError is thrown due to socket closing
@@ -1,7 +1,6 @@
1
1
  import abc
2
2
  import logging
3
3
  import threading
4
- from typing import Optional
5
4
 
6
5
  from localstack.utils.net import is_port_open
7
6
  from localstack.utils.sync import poll_condition
@@ -21,7 +20,7 @@ class Server(abc.ABC):
21
20
 
22
21
  def __init__(self, port: int, host: str = "localhost") -> None:
23
22
  super().__init__()
24
- self._thread: Optional[FuncThread] = None
23
+ self._thread: FuncThread | None = None
25
24
 
26
25
  self._lifecycle_lock = threading.RLock()
27
26
  self._stopped = threading.Event()
@@ -46,7 +45,7 @@ class Server(abc.ABC):
46
45
  def url(self):
47
46
  return f"{self.protocol}://{self.host}:{self.port}"
48
47
 
49
- def get_error(self) -> Optional[Exception]:
48
+ def get_error(self) -> Exception | None:
50
49
  """
51
50
  If the thread running the server returned with an Exception, then this function will return that exception.
52
51
  """
@@ -7,7 +7,6 @@ import re
7
7
  import string
8
8
  import uuid
9
9
  import zlib
10
- from typing import Union
11
10
 
12
11
  from localstack.config import DEFAULT_ENCODING
13
12
 
@@ -28,13 +27,13 @@ REGEX_UNPRINTABLE_CHARS = re.compile(
28
27
  )
29
28
 
30
29
 
31
- def to_str(obj: Union[str, bytes], encoding: str = DEFAULT_ENCODING, errors="strict") -> str:
30
+ def to_str(obj: str | bytes, encoding: str = DEFAULT_ENCODING, errors="strict") -> str:
32
31
  """If ``obj`` is an instance of ``binary_type``, return
33
32
  ``obj.decode(encoding, errors)``, otherwise return ``obj``"""
34
33
  return obj.decode(encoding, errors) if isinstance(obj, bytes) else obj
35
34
 
36
35
 
37
- def to_bytes(obj: Union[str, bytes], encoding: str = DEFAULT_ENCODING, errors="strict") -> bytes:
36
+ def to_bytes(obj: str | bytes, encoding: str = DEFAULT_ENCODING, errors="strict") -> bytes:
38
37
  """If ``obj`` is an instance of ``text_type``, return
39
38
  ``obj.encode(encoding, errors)``, otherwise return ``obj``"""
40
39
  return obj.encode(encoding, errors) if isinstance(obj, str) else obj
@@ -86,7 +85,7 @@ def canonicalize_bool_to_str(val: bool) -> str:
86
85
  return "true" if str(val).lower() == "true" else "false"
87
86
 
88
87
 
89
- def convert_to_printable_chars(value: Union[list, dict, str]) -> str:
88
+ def convert_to_printable_chars(value: list | dict | str) -> str:
90
89
  """Removes all unprintable characters from the given string."""
91
90
  from localstack.utils.objects import recurse_object
92
91
 
@@ -148,19 +147,19 @@ def long_uid() -> str:
148
147
  return str(uuid.uuid4())
149
148
 
150
149
 
151
- def md5(string: Union[str, bytes]) -> str:
150
+ def md5(string: str | bytes) -> str:
152
151
  m = hashlib.md5()
153
152
  m.update(to_bytes(string))
154
153
  return m.hexdigest()
155
154
 
156
155
 
157
- def checksum_crc32(string: Union[str, bytes]) -> str:
156
+ def checksum_crc32(string: str | bytes) -> str:
158
157
  bytes = to_bytes(string)
159
158
  checksum = zlib.crc32(bytes)
160
159
  return base64.b64encode(checksum.to_bytes(4, "big")).decode()
161
160
 
162
161
 
163
- def checksum_crc32c(string: Union[str, bytes]):
162
+ def checksum_crc32c(string: str | bytes):
164
163
  # import botocore locally here to avoid a dependency of the CLI to botocore
165
164
  from botocore.httpchecksum import CrtCrc32cChecksum
166
165
 
@@ -169,7 +168,7 @@ def checksum_crc32c(string: Union[str, bytes]):
169
168
  return base64.b64encode(checksum.digest()).decode()
170
169
 
171
170
 
172
- def checksum_crc64nvme(string: Union[str, bytes]):
171
+ def checksum_crc64nvme(string: str | bytes):
173
172
  # import botocore locally here to avoid a dependency of the CLI to botocore
174
173
  from botocore.httpchecksum import CrtCrc64NvmeChecksum
175
174
 
@@ -178,12 +177,12 @@ def checksum_crc64nvme(string: Union[str, bytes]):
178
177
  return base64.b64encode(checksum.digest()).decode()
179
178
 
180
179
 
181
- def hash_sha1(string: Union[str, bytes]) -> str:
180
+ def hash_sha1(string: str | bytes) -> str:
182
181
  digest = hashlib.sha1(to_bytes(string)).digest()
183
182
  return base64.b64encode(digest).decode()
184
183
 
185
184
 
186
- def hash_sha256(string: Union[str, bytes]) -> str:
185
+ def hash_sha256(string: str | bytes) -> str:
187
186
  digest = hashlib.sha256(to_bytes(string)).digest()
188
187
  return base64.b64encode(digest).decode()
189
188
 
@@ -192,7 +191,7 @@ def base64_to_hex(b64_string: str) -> bytes:
192
191
  return binascii.hexlify(base64.b64decode(b64_string))
193
192
 
194
193
 
195
- def base64_decode(data: Union[str, bytes]) -> bytes:
194
+ def base64_decode(data: str | bytes) -> bytes:
196
195
  """Decode base64 data - with optional padding, and able to handle urlsafe encoding (containing -/_)."""
197
196
  data = to_str(data)
198
197
  missing_padding = len(data) % 4