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.
- localstack/aws/api/acm/__init__.py +122 -122
- localstack/aws/api/apigateway/__init__.py +560 -559
- localstack/aws/api/cloudcontrol/__init__.py +63 -63
- localstack/aws/api/cloudformation/__init__.py +1041 -969
- localstack/aws/api/cloudwatch/__init__.py +408 -368
- localstack/aws/api/config/__init__.py +788 -786
- localstack/aws/api/core.py +4 -0
- localstack/aws/api/dynamodb/__init__.py +753 -759
- localstack/aws/api/dynamodbstreams/__init__.py +74 -74
- localstack/aws/api/ec2/__init__.py +9713 -8573
- localstack/aws/api/es/__init__.py +453 -453
- localstack/aws/api/events/__init__.py +552 -552
- localstack/aws/api/firehose/__init__.py +541 -543
- localstack/aws/api/iam/__init__.py +646 -572
- localstack/aws/api/kinesis/__init__.py +251 -144
- localstack/aws/api/kms/__init__.py +343 -333
- localstack/aws/api/lambda_/__init__.py +585 -571
- localstack/aws/api/logs/__init__.py +682 -666
- localstack/aws/api/opensearch/__init__.py +814 -785
- localstack/aws/api/pipes/__init__.py +336 -336
- localstack/aws/api/redshift/__init__.py +1192 -1164
- localstack/aws/api/resource_groups/__init__.py +175 -175
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +67 -67
- localstack/aws/api/route53/__init__.py +256 -254
- localstack/aws/api/route53resolver/__init__.py +396 -396
- localstack/aws/api/s3/__init__.py +1358 -1345
- localstack/aws/api/s3control/__init__.py +616 -584
- localstack/aws/api/scheduler/__init__.py +118 -118
- localstack/aws/api/secretsmanager/__init__.py +193 -193
- localstack/aws/api/ses/__init__.py +227 -227
- localstack/aws/api/sns/__init__.py +115 -115
- localstack/aws/api/sqs/__init__.py +100 -100
- localstack/aws/api/ssm/__init__.py +1978 -1970
- localstack/aws/api/stepfunctions/__init__.py +323 -323
- localstack/aws/api/sts/__init__.py +90 -66
- localstack/aws/api/support/__init__.py +112 -112
- localstack/aws/api/swf/__init__.py +378 -386
- localstack/aws/api/transcribe/__init__.py +425 -425
- localstack/aws/client.py +7 -2
- localstack/aws/forwarder.py +52 -5
- localstack/aws/handlers/analytics.py +1 -1
- localstack/aws/handlers/logging.py +12 -2
- localstack/aws/handlers/metric_handler.py +41 -1
- localstack/aws/handlers/service.py +43 -10
- localstack/aws/protocol/parser.py +440 -21
- localstack/aws/protocol/serializer.py +684 -64
- localstack/aws/protocol/service_router.py +120 -20
- localstack/aws/scaffold.py +15 -17
- localstack/aws/skeleton.py +4 -2
- localstack/aws/spec-patches.json +58 -0
- localstack/aws/spec.py +33 -13
- localstack/cli/exceptions.py +1 -1
- localstack/cli/localstack.py +10 -5
- localstack/cli/lpm.py +3 -4
- localstack/cli/profiles.py +1 -2
- localstack/config.py +18 -12
- localstack/constants.py +4 -29
- localstack/dev/kubernetes/__main__.py +39 -4
- localstack/dev/run/paths.py +1 -1
- localstack/dns/plugins.py +5 -1
- localstack/dns/server.py +12 -3
- localstack/packages/api.py +9 -8
- localstack/packages/core.py +2 -2
- localstack/packages/plugins.py +0 -8
- localstack/runtime/init.py +1 -1
- localstack/services/apigateway/helpers.py +5 -9
- localstack/services/apigateway/legacy/provider.py +85 -12
- localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +3 -0
- localstack/services/apigateway/next_gen/execute_api/integrations/http.py +3 -3
- localstack/services/apigateway/next_gen/execute_api/test_invoke.py +50 -6
- localstack/services/apigateway/next_gen/provider.py +5 -0
- localstack/services/apigateway/patches.py +0 -9
- localstack/services/cloudformation/engine/entities.py +12 -1
- localstack/services/cloudformation/engine/v2/change_set_model.py +0 -3
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +14 -0
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +13 -15
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +118 -24
- localstack/services/cloudformation/engine/v2/change_set_model_transform.py +4 -1
- localstack/services/cloudformation/engine/v2/change_set_model_validator.py +5 -14
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
- localstack/services/cloudformation/engine/v2/resolving.py +6 -4
- localstack/services/cloudformation/engine/yaml_parser.py +9 -2
- localstack/services/cloudformation/provider.py +2 -2
- localstack/services/cloudformation/resource_provider.py +5 -1
- localstack/services/cloudformation/resources.py +24149 -0
- localstack/services/cloudformation/v2/entities.py +6 -3
- localstack/services/cloudformation/v2/provider.py +178 -33
- localstack/services/cloudformation/v2/types.py +8 -4
- localstack/services/cloudwatch/provider_v2.py +25 -28
- localstack/services/dynamodb/packages.py +2 -1
- localstack/services/dynamodb/provider.py +42 -0
- localstack/services/dynamodb/v2/provider.py +42 -0
- localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
- localstack/services/es/provider.py +2 -2
- localstack/services/events/event_rule_engine.py +31 -13
- localstack/services/events/models.py +4 -5
- localstack/services/events/target.py +17 -9
- localstack/services/iam/provider.py +11 -116
- localstack/services/iam/resources/policy_simulator.py +133 -0
- localstack/services/kinesis/models.py +15 -2
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kinesis/provider.py +77 -0
- localstack/services/kms/models.py +34 -4
- localstack/services/kms/provider.py +107 -21
- localstack/services/lambda_/api_utils.py +3 -1
- localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +1 -1
- localstack/services/lambda_/runtimes.py +8 -3
- localstack/services/logs/provider.py +36 -19
- localstack/services/moto.py +2 -1
- localstack/services/opensearch/cluster.py +15 -7
- localstack/services/opensearch/packages.py +26 -7
- localstack/services/opensearch/provider.py +6 -1
- localstack/services/opensearch/versions.py +56 -7
- localstack/services/s3/constants.py +5 -2
- localstack/services/s3/cors.py +4 -4
- localstack/services/s3/notifications.py +1 -1
- localstack/services/s3/presigned_url.py +27 -43
- localstack/services/s3/provider.py +68 -12
- localstack/services/s3/utils.py +42 -11
- localstack/services/ses/provider.py +16 -7
- localstack/services/sns/constants.py +7 -1
- localstack/services/sns/v2/models.py +190 -0
- localstack/services/sns/v2/provider.py +992 -2
- localstack/services/sns/v2/utils.py +138 -0
- localstack/services/sqs/developer_api.py +205 -0
- localstack/services/sqs/models.py +79 -13
- localstack/services/sqs/provider.py +8 -309
- localstack/services/sqs/query_api.py +1 -1
- localstack/services/sqs/utils.py +121 -2
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
- localstack/testing/aws/cloudformation_utils.py +1 -1
- localstack/testing/pytest/cloudformation/fixtures.py +3 -3
- localstack/testing/pytest/container.py +4 -5
- localstack/testing/pytest/fixtures.py +20 -19
- localstack/testing/pytest/in_memory_localstack.py +0 -4
- localstack/testing/pytest/marking.py +13 -4
- localstack/testing/pytest/stepfunctions/utils.py +4 -3
- localstack/testing/pytest/util.py +1 -1
- localstack/testing/pytest/validation_tracking.py +1 -2
- localstack/testing/snapshots/transformer_utility.py +7 -0
- localstack/testing/testselection/matching.py +0 -1
- localstack/utils/analytics/events.py +2 -2
- localstack/utils/analytics/metadata.py +1 -2
- localstack/utils/analytics/metrics/counter.py +6 -8
- localstack/utils/analytics/publisher.py +1 -2
- localstack/utils/analytics/service_request_aggregator.py +2 -2
- localstack/utils/archives.py +11 -11
- localstack/utils/aws/arns.py +17 -9
- localstack/utils/aws/aws_responses.py +7 -7
- localstack/utils/aws/aws_stack.py +2 -3
- localstack/utils/aws/client_types.py +0 -8
- localstack/utils/aws/message_forwarding.py +1 -2
- localstack/utils/aws/request_context.py +4 -5
- localstack/utils/batch_policy.py +3 -3
- localstack/utils/bootstrap.py +7 -7
- localstack/utils/catalog/catalog.py +139 -0
- localstack/utils/catalog/catalog_loader.py +119 -0
- localstack/utils/catalog/common.py +58 -0
- localstack/utils/catalog/plugins.py +28 -0
- localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
- localstack/utils/collections.py +7 -8
- localstack/utils/config_listener.py +1 -1
- localstack/utils/container_networking.py +2 -3
- localstack/utils/container_utils/container_client.py +115 -131
- localstack/utils/container_utils/docker_cmd_client.py +42 -42
- localstack/utils/container_utils/docker_sdk_client.py +63 -62
- localstack/utils/crypto.py +109 -0
- localstack/utils/diagnose.py +2 -3
- localstack/utils/docker_utils.py +3 -4
- localstack/utils/files.py +31 -7
- localstack/utils/functions.py +3 -2
- localstack/utils/http.py +4 -5
- localstack/utils/json.py +19 -5
- localstack/utils/kinesis/kinesis_connector.py +2 -1
- localstack/utils/net.py +6 -6
- localstack/utils/no_exit_argument_parser.py +2 -2
- localstack/utils/numbers.py +9 -2
- localstack/utils/objects.py +6 -5
- localstack/utils/patch.py +2 -1
- localstack/utils/run.py +10 -9
- localstack/utils/scheduler.py +11 -11
- localstack/utils/server/tcp_proxy.py +2 -2
- localstack/utils/serving.py +2 -3
- localstack/utils/strings.py +10 -11
- localstack/utils/sync.py +126 -1
- localstack/utils/tagging.py +1 -4
- localstack/utils/testutil.py +5 -4
- localstack/utils/threads.py +2 -2
- localstack/utils/time.py +11 -3
- localstack/utils/urls.py +1 -3
- localstack/version.py +2 -2
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/METADATA +19 -13
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/RECORD +203 -199
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/entry_points.txt +4 -2
- localstack_core-4.10.1.dev42.dist-info/plux.json +1 -0
- localstack/packages/terraform.py +0 -46
- localstack/services/cloudformation/deploy.html +0 -144
- localstack/services/cloudformation/deploy_ui.py +0 -47
- localstack/services/cloudformation/plugins.py +0 -12
- localstack_core-4.7.1.dev139.dist-info/plux.json +0 -1
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/WHEEL +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/licenses/LICENSE.txt +0 -0
- {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(
|
|
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
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
localstack/utils/functions.py
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import functools
|
|
4
4
|
import inspect
|
|
5
5
|
import logging
|
|
6
|
-
from
|
|
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
|
-
) ->
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
66
|
+
path: str | os.PathLike
|
|
67
67
|
|
|
68
|
-
def __init__(self, path:
|
|
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',
|
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
|
|
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 =
|
|
53
|
+
IntOrPort = int | Port
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
def is_port_open(
|
|
57
|
-
port_or_url:
|
|
57
|
+
port_or_url: int | str,
|
|
58
58
|
http_path: str = None,
|
|
59
59
|
expect_success: bool = True,
|
|
60
|
-
protocols:
|
|
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) ->
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
localstack/utils/numbers.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
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) ->
|
|
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))
|
localstack/utils/objects.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import re
|
|
3
3
|
import threading
|
|
4
|
-
from
|
|
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 =
|
|
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:
|
|
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) ->
|
|
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:
|
|
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
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
|
|
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:
|
|
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:
|
|
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
|
-
) ->
|
|
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:
|
|
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:
|
|
215
|
+
cmd: str | list[str],
|
|
215
216
|
params: Any = None,
|
|
216
|
-
outfile:
|
|
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:
|
|
227
|
-
cwd:
|
|
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 {}
|
localstack/utils/scheduler.py
CHANGED
|
@@ -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
|
|
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:
|
|
17
|
+
period: float | None = None,
|
|
18
18
|
fixed_rate: bool = True,
|
|
19
|
-
start:
|
|
19
|
+
start: float | None = None,
|
|
20
20
|
on_error: Callable[[Exception], None] = None,
|
|
21
|
-
args:
|
|
22
|
-
kwargs:
|
|
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:
|
|
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:
|
|
97
|
+
period: float | None = None,
|
|
98
98
|
fixed_rate: bool = True,
|
|
99
|
-
start:
|
|
99
|
+
start: float | None = None,
|
|
100
100
|
on_error: Callable[[Exception], None] = None,
|
|
101
|
-
args:
|
|
102
|
-
kwargs:
|
|
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
|
|
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
|
localstack/utils/serving.py
CHANGED
|
@@ -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:
|
|
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) ->
|
|
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
|
"""
|
localstack/utils/strings.py
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|