localstack-core 4.7.1.dev49__py3-none-any.whl → 4.10.1.dev12__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.
- localstack/aws/api/cloudformation/__init__.py +18 -4
- localstack/aws/api/cloudwatch/__init__.py +41 -1
- localstack/aws/api/config/__init__.py +4 -0
- localstack/aws/api/core.py +6 -2
- localstack/aws/api/dynamodb/__init__.py +30 -0
- localstack/aws/api/ec2/__init__.py +1522 -65
- localstack/aws/api/iam/__init__.py +7 -0
- localstack/aws/api/kinesis/__init__.py +19 -0
- localstack/aws/api/kms/__init__.py +6 -0
- localstack/aws/api/lambda_/__init__.py +13 -0
- localstack/aws/api/logs/__init__.py +15 -0
- localstack/aws/api/redshift/__init__.py +9 -3
- localstack/aws/api/route53/__init__.py +5 -0
- localstack/aws/api/s3/__init__.py +12 -0
- localstack/aws/api/s3control/__init__.py +54 -0
- localstack/aws/api/ssm/__init__.py +2 -0
- localstack/aws/api/transcribe/__init__.py +17 -0
- localstack/aws/client.py +7 -2
- localstack/aws/forwarder.py +52 -5
- localstack/aws/handlers/analytics.py +1 -1
- localstack/aws/handlers/internal_requests.py +6 -1
- localstack/aws/handlers/logging.py +12 -2
- localstack/aws/handlers/metric_handler.py +41 -1
- localstack/aws/handlers/service.py +40 -20
- localstack/aws/mocking.py +2 -2
- localstack/aws/patches.py +2 -2
- localstack/aws/protocol/parser.py +459 -32
- localstack/aws/protocol/serializer.py +689 -69
- localstack/aws/protocol/service_router.py +120 -20
- localstack/aws/protocol/validate.py +1 -1
- localstack/aws/scaffold.py +1 -1
- localstack/aws/skeleton.py +4 -2
- localstack/aws/spec-patches.json +58 -0
- localstack/aws/spec.py +37 -16
- localstack/cli/exceptions.py +1 -1
- localstack/cli/localstack.py +6 -6
- localstack/cli/lpm.py +3 -4
- localstack/cli/plugins.py +1 -1
- localstack/cli/profiles.py +1 -2
- localstack/config.py +25 -18
- localstack/constants.py +4 -29
- localstack/dev/kubernetes/__main__.py +130 -7
- localstack/dev/run/configurators.py +1 -4
- localstack/dev/run/paths.py +1 -1
- localstack/dns/plugins.py +5 -1
- localstack/dns/server.py +13 -4
- localstack/logging/format.py +3 -3
- localstack/packages/api.py +9 -8
- localstack/packages/core.py +2 -2
- localstack/packages/plugins.py +0 -8
- localstack/runtime/analytics.py +3 -0
- localstack/runtime/hooks.py +1 -1
- localstack/runtime/init.py +2 -2
- localstack/runtime/main.py +5 -5
- localstack/runtime/patches.py +2 -2
- localstack/services/apigateway/helpers.py +1 -4
- localstack/services/apigateway/legacy/helpers.py +7 -8
- localstack/services/apigateway/legacy/integration.py +4 -3
- localstack/services/apigateway/legacy/invocations.py +6 -5
- localstack/services/apigateway/legacy/provider.py +148 -68
- localstack/services/apigateway/legacy/templates.py +1 -1
- localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py +7 -2
- localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py +1 -2
- 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/template_mapping.py +2 -2
- localstack/services/apigateway/next_gen/execute_api/test_invoke.py +114 -9
- localstack/services/apigateway/next_gen/provider.py +5 -0
- localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +1 -1
- localstack/services/cloudformation/api_utils.py +4 -8
- localstack/services/cloudformation/cfn_utils.py +1 -1
- localstack/services/cloudformation/engine/entities.py +14 -4
- localstack/services/cloudformation/engine/template_deployer.py +6 -4
- localstack/services/cloudformation/engine/transformers.py +6 -4
- localstack/services/cloudformation/engine/v2/change_set_model.py +201 -13
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +52 -3
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +117 -76
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +205 -52
- localstack/services/cloudformation/engine/v2/change_set_model_transform.py +350 -116
- localstack/services/cloudformation/engine/v2/change_set_model_validator.py +56 -14
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
- localstack/services/cloudformation/engine/v2/resolving.py +7 -5
- localstack/services/cloudformation/engine/yaml_parser.py +9 -2
- localstack/services/cloudformation/provider.py +7 -5
- localstack/services/cloudformation/resource_provider.py +7 -1
- localstack/services/cloudformation/resources.py +24149 -0
- localstack/services/cloudformation/service_models.py +2 -2
- localstack/services/cloudformation/v2/entities.py +19 -9
- localstack/services/cloudformation/v2/provider.py +336 -106
- localstack/services/cloudformation/v2/types.py +13 -7
- localstack/services/cloudformation/v2/utils.py +4 -1
- localstack/services/cloudwatch/alarm_scheduler.py +4 -1
- localstack/services/cloudwatch/provider.py +18 -13
- 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/server.py +2 -2
- localstack/services/dynamodb/v2/provider.py +42 -0
- localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
- localstack/services/edge.py +1 -1
- 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/provider.py +17 -14
- localstack/services/events/target.py +17 -9
- localstack/services/events/v1/provider.py +5 -5
- localstack/services/firehose/provider.py +14 -4
- 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/provider.py +86 -3
- localstack/services/kms/provider.py +14 -5
- localstack/services/lambda_/api_utils.py +6 -3
- localstack/services/lambda_/invocation/docker_runtime_executor.py +1 -1
- localstack/services/lambda_/invocation/event_manager.py +1 -1
- localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
- localstack/services/lambda_/invocation/lambda_models.py +10 -7
- localstack/services/lambda_/invocation/lambda_service.py +5 -1
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +4 -3
- localstack/services/lambda_/provider_utils.py +1 -1
- 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 +8 -2
- localstack/services/opensearch/versions.py +56 -7
- localstack/services/plugins.py +11 -7
- localstack/services/providers.py +10 -2
- localstack/services/redshift/provider.py +0 -21
- localstack/services/s3/constants.py +5 -2
- localstack/services/s3/cors.py +4 -4
- localstack/services/s3/models.py +1 -1
- localstack/services/s3/notifications.py +55 -39
- localstack/services/s3/presigned_url.py +35 -54
- localstack/services/s3/provider.py +73 -15
- localstack/services/s3/utils.py +42 -22
- localstack/services/s3/validation.py +46 -32
- localstack/services/s3/website_hosting.py +4 -2
- localstack/services/ses/provider.py +18 -8
- localstack/services/sns/constants.py +7 -1
- localstack/services/sns/executor.py +9 -2
- localstack/services/sns/provider.py +8 -5
- localstack/services/sns/publisher.py +31 -16
- localstack/services/sns/v2/models.py +167 -0
- localstack/services/sns/v2/provider.py +867 -0
- localstack/services/sns/v2/utils.py +130 -0
- localstack/services/sqs/constants.py +1 -1
- localstack/services/sqs/developer_api.py +205 -0
- localstack/services/sqs/models.py +48 -5
- localstack/services/sqs/provider.py +38 -311
- localstack/services/sqs/query_api.py +6 -2
- localstack/services/sqs/utils.py +121 -2
- localstack/services/ssm/provider.py +1 -1
- localstack/services/stepfunctions/asl/component/intrinsic/member.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py +5 -11
- localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py +1 -1
- localstack/services/stepfunctions/asl/eval/environment.py +1 -1
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
- localstack/services/stepfunctions/backend/execution.py +2 -1
- localstack/services/stores.py +1 -1
- localstack/services/transcribe/provider.py +6 -1
- localstack/state/codecs.py +61 -0
- localstack/state/core.py +11 -5
- localstack/state/pickle.py +10 -49
- localstack/testing/aws/cloudformation_utils.py +1 -1
- localstack/testing/pytest/cloudformation/fixtures.py +3 -3
- localstack/testing/pytest/cloudformation/transformers.py +0 -0
- localstack/testing/pytest/container.py +4 -5
- localstack/testing/pytest/fixtures.py +33 -31
- localstack/testing/pytest/in_memory_localstack.py +0 -4
- localstack/testing/pytest/marking.py +38 -11
- 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 +6 -1
- localstack/utils/analytics/events.py +2 -2
- localstack/utils/analytics/metadata.py +6 -4
- localstack/utils/analytics/metrics/counter.py +8 -15
- localstack/utils/analytics/publisher.py +1 -2
- localstack/utils/analytics/service_providers.py +19 -0
- localstack/utils/analytics/service_request_aggregator.py +2 -2
- localstack/utils/archives.py +11 -11
- localstack/utils/asyncio.py +2 -2
- localstack/utils/aws/arns.py +24 -29
- localstack/utils/aws/aws_responses.py +8 -8
- localstack/utils/aws/aws_stack.py +2 -3
- localstack/utils/aws/dead_letter_queue.py +1 -5
- localstack/utils/aws/message_forwarding.py +1 -2
- localstack/utils/aws/request_context.py +4 -5
- localstack/utils/aws/resources.py +1 -1
- localstack/utils/aws/templating.py +1 -1
- localstack/utils/batch_policy.py +3 -3
- localstack/utils/bootstrap.py +21 -13
- 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 +135 -136
- localstack/utils/container_utils/docker_cmd_client.py +85 -69
- localstack/utils/container_utils/docker_sdk_client.py +69 -66
- localstack/utils/crypto.py +10 -10
- localstack/utils/diagnose.py +3 -4
- localstack/utils/docker_utils.py +9 -5
- localstack/utils/files.py +33 -13
- localstack/utils/functions.py +4 -3
- localstack/utils/http.py +11 -11
- localstack/utils/json.py +20 -6
- localstack/utils/kinesis/kinesis_connector.py +2 -1
- localstack/utils/net.py +15 -9
- localstack/utils/no_exit_argument_parser.py +2 -2
- localstack/utils/numbers.py +9 -2
- localstack/utils/objects.py +7 -6
- localstack/utils/patch.py +10 -3
- localstack/utils/run.py +12 -11
- localstack/utils/scheduler.py +11 -11
- localstack/utils/server/tcp_proxy.py +2 -2
- localstack/utils/serving.py +3 -4
- localstack/utils/strings.py +15 -16
- localstack/utils/sync.py +126 -1
- localstack/utils/tagging.py +8 -6
- localstack/utils/testutil.py +8 -8
- localstack/utils/threads.py +2 -2
- localstack/utils/time.py +12 -4
- localstack/utils/urls.py +1 -3
- localstack/utils/xray/traceid.py +1 -1
- localstack/version.py +16 -3
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/METADATA +18 -14
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/RECORD +248 -239
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/entry_points.txt +8 -4
- localstack_core-4.10.1.dev12.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.dev49.dist-info/plux.json +0 -1
- {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack +0 -0
- {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/WHEEL +0 -0
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/top_level.txt +0 -0
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:
|
|
@@ -114,7 +115,7 @@ def run(
|
|
|
114
115
|
return process
|
|
115
116
|
except subprocess.CalledProcessError as e:
|
|
116
117
|
if print_error:
|
|
117
|
-
print("ERROR: '
|
|
118
|
+
print(f"ERROR: '{cmd}': exit code {e.returncode}; output: {e.output}")
|
|
118
119
|
sys.stdout.flush()
|
|
119
120
|
raise e
|
|
120
121
|
|
|
@@ -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 {}
|
|
@@ -268,7 +269,7 @@ class ShellCommandThread(FuncThread):
|
|
|
268
269
|
if self.strip_color:
|
|
269
270
|
# strip color codes
|
|
270
271
|
line = re.sub(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))", "", line)
|
|
271
|
-
return "
|
|
272
|
+
return f"{line.strip()}\r\n"
|
|
272
273
|
|
|
273
274
|
def filter_line(line):
|
|
274
275
|
"""Return True if this line should be filtered, i.e., not printed"""
|
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()
|
|
@@ -44,9 +43,9 @@ class Server(abc.ABC):
|
|
|
44
43
|
|
|
45
44
|
@property
|
|
46
45
|
def url(self):
|
|
47
|
-
return "
|
|
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
|
|
@@ -42,7 +41,7 @@ def to_bytes(obj: Union[str, bytes], encoding: str = DEFAULT_ENCODING, errors="s
|
|
|
42
41
|
|
|
43
42
|
def truncate(data: str, max_length: int = 100) -> str:
|
|
44
43
|
data = str(data or "")
|
|
45
|
-
return ("
|
|
44
|
+
return (f"{data[:max_length]}...") if len(data) > max_length else data
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
def is_string(s, include_unicode=True, exclude_binary=False):
|
|
@@ -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
|
|
|
@@ -104,11 +103,11 @@ def convert_to_printable_chars(value: Union[list, dict, str]) -> str:
|
|
|
104
103
|
|
|
105
104
|
|
|
106
105
|
def first_char_to_lower(s: str) -> str:
|
|
107
|
-
return s and "
|
|
106
|
+
return s and f"{s[0].lower()}{s[1:]}"
|
|
108
107
|
|
|
109
108
|
|
|
110
109
|
def first_char_to_upper(s: str) -> str:
|
|
111
|
-
return s and "
|
|
110
|
+
return s and f"{s[0].upper()}{s[1:]}"
|
|
112
111
|
|
|
113
112
|
|
|
114
113
|
def str_to_bool(value):
|
|
@@ -121,13 +120,13 @@ def str_to_bool(value):
|
|
|
121
120
|
|
|
122
121
|
def str_insert(string, index, content):
|
|
123
122
|
"""Insert a substring into an existing string at a certain index."""
|
|
124
|
-
return "
|
|
123
|
+
return f"{string[:index]}{content}{string[index:]}"
|
|
125
124
|
|
|
126
125
|
|
|
127
126
|
def str_remove(string, index, end_index=None):
|
|
128
127
|
"""Remove a substring from an existing string at a certain from-to index range."""
|
|
129
128
|
end_index = end_index or (index + 1)
|
|
130
|
-
return "
|
|
129
|
+
return f"{string[:index]}{string[end_index:]}"
|
|
131
130
|
|
|
132
131
|
|
|
133
132
|
def str_startswith_ignore_case(value: str, prefix: str) -> bool:
|
|
@@ -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
|
localstack/utils/sync.py
CHANGED
|
@@ -4,7 +4,8 @@ import functools
|
|
|
4
4
|
import threading
|
|
5
5
|
import time
|
|
6
6
|
from collections import defaultdict
|
|
7
|
-
from
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import Literal, TypeVar
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ShortCircuitWaitException(Exception):
|
|
@@ -140,3 +141,127 @@ class SynchronizedDefaultDict(defaultdict):
|
|
|
140
141
|
def __str__(self):
|
|
141
142
|
with self._lock:
|
|
142
143
|
return super().__str__()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class Once:
|
|
147
|
+
"""
|
|
148
|
+
An object that will perform an action exactly once.
|
|
149
|
+
Inspired by Golang's [sync.Once](https://pkg.go.dev/sync#Once) operation.
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
### Example 1
|
|
153
|
+
|
|
154
|
+
Multiple threads using `Once::do` to ensure only 1 line is printed.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import threading
|
|
158
|
+
import time
|
|
159
|
+
import random
|
|
160
|
+
|
|
161
|
+
greet_once = Once()
|
|
162
|
+
def greet():
|
|
163
|
+
print("This should happen only once.")
|
|
164
|
+
|
|
165
|
+
greet_threads = []
|
|
166
|
+
for _ in range(10):
|
|
167
|
+
t = threading.Thread(target=lambda: greet_once.do(greet))
|
|
168
|
+
greet_threads.append(t)
|
|
169
|
+
t.start()
|
|
170
|
+
|
|
171
|
+
for t in greet_threads:
|
|
172
|
+
t.join()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
### Example 2
|
|
177
|
+
|
|
178
|
+
Ensuring idemponent calling to prevent exceptions on multiple calls.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
import os
|
|
182
|
+
|
|
183
|
+
class Service:
|
|
184
|
+
close_once: sync.Once
|
|
185
|
+
|
|
186
|
+
def start(self):
|
|
187
|
+
with open("my-service.txt) as f:
|
|
188
|
+
myfile.write("Started service")
|
|
189
|
+
|
|
190
|
+
def close(self):
|
|
191
|
+
# Ensure we only ever delete the file once on close
|
|
192
|
+
self.close_once.do(lambda: os.remove("my-service.txt"))
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
_is_done: bool = False
|
|
200
|
+
_mu: threading.Lock = threading.Lock()
|
|
201
|
+
|
|
202
|
+
def do(self, fn: Callable[[], None]):
|
|
203
|
+
"""
|
|
204
|
+
`do` calls the function `fn()` if-and-only-if `do` has never been called before.
|
|
205
|
+
|
|
206
|
+
This ensures idempotent and thread-safe execution.
|
|
207
|
+
|
|
208
|
+
If the function raises an exception, `do` considers `fn` as done, where subsequent calls are still no-ops.
|
|
209
|
+
"""
|
|
210
|
+
if self._is_done:
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
with self._mu:
|
|
214
|
+
if not self._is_done:
|
|
215
|
+
try:
|
|
216
|
+
fn()
|
|
217
|
+
finally:
|
|
218
|
+
self._is_done = True
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def once_func(fn: Callable[..., T]) -> Callable[..., T | None]:
|
|
222
|
+
"""
|
|
223
|
+
Wraps and returns a function that can only ever execute once.
|
|
224
|
+
|
|
225
|
+
The first call to the returned function will permanently set the result.
|
|
226
|
+
If the wrapped function raises an exception, this will be re-raised on each subsequent call.
|
|
227
|
+
|
|
228
|
+
This function can be used either as a decorator or called directly.
|
|
229
|
+
|
|
230
|
+
Direct usage:
|
|
231
|
+
```python
|
|
232
|
+
delete_file = once_func(os.remove)
|
|
233
|
+
|
|
234
|
+
delete_file("myfile.txt") # deletes the file
|
|
235
|
+
delete_file("myfile.txt") # does nothing
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
As a decorator:
|
|
239
|
+
```python
|
|
240
|
+
@once_func
|
|
241
|
+
def delete_file():
|
|
242
|
+
os.remove("myfile.txt")
|
|
243
|
+
|
|
244
|
+
delete_file() # deletes the file
|
|
245
|
+
delete_file() # does nothing
|
|
246
|
+
```
|
|
247
|
+
"""
|
|
248
|
+
once = Once()
|
|
249
|
+
|
|
250
|
+
result, exception = None, None
|
|
251
|
+
|
|
252
|
+
def _do(*args, **kwargs):
|
|
253
|
+
nonlocal result, exception
|
|
254
|
+
try:
|
|
255
|
+
result = fn(*args, **kwargs)
|
|
256
|
+
except Exception as e:
|
|
257
|
+
exception = e
|
|
258
|
+
raise
|
|
259
|
+
|
|
260
|
+
@functools.wraps(fn)
|
|
261
|
+
def wrapper(*args, **kwargs):
|
|
262
|
+
once.do(lambda: _do(*args, **kwargs))
|
|
263
|
+
if exception is not None:
|
|
264
|
+
raise exception
|
|
265
|
+
return result
|
|
266
|
+
|
|
267
|
+
return wrapper
|
localstack/utils/tagging.py
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
class TaggingService:
|
|
2
|
+
key_field: str
|
|
3
|
+
value_field: str
|
|
2
4
|
|
|
5
|
+
tags: dict[str, dict[str, str]]
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
def __init__(self, key_field: str = None, value_field: str = None):
|
|
7
|
+
def __init__(self, key_field: str = "Key", value_field: str = "Value"):
|
|
6
8
|
"""
|
|
7
9
|
:param key_field: the field name representing the tag key as used by botocore specs
|
|
8
10
|
:param value_field: the field name representing the tag value as used by botocore specs
|
|
9
11
|
"""
|
|
10
|
-
self.key_field = key_field
|
|
11
|
-
self.value_field = value_field
|
|
12
|
+
self.key_field = key_field
|
|
13
|
+
self.value_field = value_field
|
|
12
14
|
|
|
13
15
|
self.tags = {}
|
|
14
16
|
|
|
15
|
-
def list_tags_for_resource(self, arn: str, root_name:
|
|
17
|
+
def list_tags_for_resource(self, arn: str, root_name: str | None = None):
|
|
16
18
|
root_name = root_name or "Tags"
|
|
17
19
|
|
|
18
20
|
result = []
|
localstack/utils/testutil.py
CHANGED
|
@@ -7,7 +7,8 @@ import re
|
|
|
7
7
|
import shutil
|
|
8
8
|
import tempfile
|
|
9
9
|
import time
|
|
10
|
-
from
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import Any
|
|
11
12
|
|
|
12
13
|
from localstack.aws.api.lambda_ import Runtime
|
|
13
14
|
from localstack.aws.connect import connect_externally_to, connect_to
|
|
@@ -20,7 +21,7 @@ from localstack.utils.urls import localstack_host
|
|
|
20
21
|
try:
|
|
21
22
|
from typing import Literal
|
|
22
23
|
except ImportError:
|
|
23
|
-
from
|
|
24
|
+
from typing import Literal
|
|
24
25
|
|
|
25
26
|
import boto3
|
|
26
27
|
import requests
|
|
@@ -94,7 +95,7 @@ def create_lambda_archive(
|
|
|
94
95
|
chmod_r(script_file, 0o777)
|
|
95
96
|
# copy libs
|
|
96
97
|
for lib in libs:
|
|
97
|
-
paths = [lib, "
|
|
98
|
+
paths = [lib, f"{lib}.py"]
|
|
98
99
|
try:
|
|
99
100
|
module = importlib.import_module(lib)
|
|
100
101
|
paths.append(module.__file__)
|
|
@@ -382,7 +383,7 @@ def assert_object(expected_object, all_objects):
|
|
|
382
383
|
all_objects = [all_objects]
|
|
383
384
|
found = find_object(expected_object, all_objects)
|
|
384
385
|
if not found:
|
|
385
|
-
raise Exception("Expected object not found:
|
|
386
|
+
raise Exception(f"Expected object not found: {expected_object} in list {all_objects}")
|
|
386
387
|
|
|
387
388
|
|
|
388
389
|
def find_object(expected_object, object_list):
|
|
@@ -522,8 +523,7 @@ def check_expected_lambda_log_events_length(
|
|
|
522
523
|
events = [line for line in events if line not in ["\x1b[0m", "\\x1b[0m"]]
|
|
523
524
|
if len(events) != expected_length:
|
|
524
525
|
print(
|
|
525
|
-
"Invalid # of Lambda
|
|
526
|
-
% (
|
|
526
|
+
"Invalid # of Lambda {} log events: {} / {}: {}".format(
|
|
527
527
|
function_name,
|
|
528
528
|
len(events),
|
|
529
529
|
expected_length,
|
|
@@ -549,7 +549,7 @@ def list_all_log_events(log_group_name: str, logs_client=None) -> list[dict]:
|
|
|
549
549
|
def get_lambda_log_events(
|
|
550
550
|
function_name,
|
|
551
551
|
delay_time=DEFAULT_GET_LOG_EVENTS_DELAY,
|
|
552
|
-
regex_filter:
|
|
552
|
+
regex_filter: str | None = None,
|
|
553
553
|
log_group=None,
|
|
554
554
|
logs_client=None,
|
|
555
555
|
):
|
|
@@ -597,7 +597,7 @@ def list_all_resources(
|
|
|
597
597
|
page_function: Callable[[dict], Any],
|
|
598
598
|
last_token_attr_name: str,
|
|
599
599
|
list_attr_name: str,
|
|
600
|
-
next_token_attr_name:
|
|
600
|
+
next_token_attr_name: str | None = None,
|
|
601
601
|
) -> list:
|
|
602
602
|
"""
|
|
603
603
|
List all available resources by loading all available pages using `page_function`.
|
localstack/utils/threads.py
CHANGED
|
@@ -3,9 +3,9 @@ import inspect
|
|
|
3
3
|
import logging
|
|
4
4
|
import threading
|
|
5
5
|
import traceback
|
|
6
|
+
from collections.abc import Callable
|
|
6
7
|
from concurrent.futures import Future
|
|
7
8
|
from multiprocessing.dummy import Pool
|
|
8
|
-
from typing import Callable, Optional
|
|
9
9
|
|
|
10
10
|
LOG = logging.getLogger(__name__)
|
|
11
11
|
|
|
@@ -26,7 +26,7 @@ class FuncThread(threading.Thread):
|
|
|
26
26
|
params=None,
|
|
27
27
|
quiet=False,
|
|
28
28
|
on_stop: Callable[["FuncThread"], None] = None,
|
|
29
|
-
name:
|
|
29
|
+
name: str | None = None,
|
|
30
30
|
daemon=True,
|
|
31
31
|
):
|
|
32
32
|
global counter
|
localstack/utils/time.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from datetime import date, datetime, timezone, tzinfo
|
|
3
|
-
from
|
|
3
|
+
from zoneinfo import ZoneInfo
|
|
4
4
|
|
|
5
5
|
TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S"
|
|
6
6
|
TIMESTAMP_FORMAT_TZ = "%Y-%m-%dT%H:%M:%SZ"
|
|
@@ -42,6 +42,11 @@ def epoch_timestamp() -> float:
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def parse_timestamp(ts_str: str) -> datetime:
|
|
45
|
+
"""
|
|
46
|
+
Parse the incoming date string into a timezone aware datetime object
|
|
47
|
+
:param ts_str:
|
|
48
|
+
:return:
|
|
49
|
+
"""
|
|
45
50
|
for ts_format in [
|
|
46
51
|
TIMESTAMP_FORMAT,
|
|
47
52
|
TIMESTAMP_FORMAT_TZ,
|
|
@@ -49,13 +54,16 @@ def parse_timestamp(ts_str: str) -> datetime:
|
|
|
49
54
|
TIMESTAMP_READABLE_FORMAT,
|
|
50
55
|
]:
|
|
51
56
|
try:
|
|
52
|
-
|
|
57
|
+
value = datetime.strptime(ts_str, ts_format)
|
|
58
|
+
if value.tzinfo is None:
|
|
59
|
+
value = value.replace(tzinfo=ZoneInfo("UTC"))
|
|
60
|
+
return value
|
|
53
61
|
except ValueError:
|
|
54
62
|
pass
|
|
55
|
-
raise Exception("Unable to parse timestamp string with any known formats:
|
|
63
|
+
raise Exception(f"Unable to parse timestamp string with any known formats: {ts_str}")
|
|
56
64
|
|
|
57
65
|
|
|
58
|
-
def now(millis: bool = False, tz:
|
|
66
|
+
def now(millis: bool = False, tz: tzinfo | None = None) -> int:
|
|
59
67
|
return mktime(datetime.now(tz=tz), millis=millis)
|
|
60
68
|
|
|
61
69
|
|
localstack/utils/urls.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
from localstack import config
|
|
4
2
|
from localstack.config import HostAndPort
|
|
5
3
|
|
|
@@ -12,7 +10,7 @@ def hostname_from_url(url: str) -> str:
|
|
|
12
10
|
return url.split("://")[-1].split("/")[0].split(":")[0]
|
|
13
11
|
|
|
14
12
|
|
|
15
|
-
def localstack_host(custom_port:
|
|
13
|
+
def localstack_host(custom_port: int | None = None) -> HostAndPort:
|
|
16
14
|
"""
|
|
17
15
|
Determine the host and port to return to the user based on:
|
|
18
16
|
- the user's configuration (e.g environment variable overrides)
|
localstack/utils/xray/traceid.py
CHANGED
localstack/version.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '4.
|
|
21
|
-
__version_tuple__ = version_tuple = (4,
|
|
31
|
+
__version__ = version = '4.10.1.dev12'
|
|
32
|
+
__version_tuple__ = version_tuple = (4, 10, 1, 'dev12')
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|