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
|
@@ -59,11 +59,7 @@ def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: dict, error
|
|
|
59
59
|
except Exception as e:
|
|
60
60
|
error = e
|
|
61
61
|
if error or not result_code or result_code >= 400:
|
|
62
|
-
msg = "Unable to send message to dead letter queue
|
|
63
|
-
queue_url,
|
|
64
|
-
result_code,
|
|
65
|
-
error,
|
|
66
|
-
)
|
|
62
|
+
msg = f"Unable to send message to dead letter queue {queue_url} (code {result_code}): {error}"
|
|
67
63
|
if "InvalidMessageContents" in str(error):
|
|
68
64
|
msg += f" - messages: {messages}"
|
|
69
65
|
LOG.info(msg)
|
|
@@ -3,7 +3,6 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
5
5
|
import uuid
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
from moto.events.models import events_backends
|
|
9
8
|
|
|
@@ -214,7 +213,7 @@ def list_of_parameters_to_object(items):
|
|
|
214
213
|
return {item.get("Key"): item.get("Value") for item in items}
|
|
215
214
|
|
|
216
215
|
|
|
217
|
-
def send_event_to_api_destination(target_arn, event, http_parameters:
|
|
216
|
+
def send_event_to_api_destination(target_arn, event, http_parameters: dict | None = None):
|
|
218
217
|
"""Send an event to an EventBridge API destination
|
|
219
218
|
See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-api-destinations.html"""
|
|
220
219
|
|
|
@@ -4,7 +4,6 @@ This module has utilities relating to creating/parsing AWS requests.
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import re
|
|
7
|
-
from typing import Optional
|
|
8
7
|
|
|
9
8
|
from rolo import Request as RoloRequest
|
|
10
9
|
|
|
@@ -30,7 +29,7 @@ def get_account_id_from_request(request: RoloRequest) -> str:
|
|
|
30
29
|
return get_account_id_from_access_key_id(access_key_id)
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
def extract_region_from_auth_header(headers) ->
|
|
32
|
+
def extract_region_from_auth_header(headers) -> str | None:
|
|
34
33
|
auth = headers.get("Authorization") or ""
|
|
35
34
|
region = re.sub(r".*Credential=[^/]+/[^/]+/([^/]+)/.*", r"\1", auth)
|
|
36
35
|
if region == auth:
|
|
@@ -38,12 +37,12 @@ def extract_region_from_auth_header(headers) -> Optional[str]:
|
|
|
38
37
|
return region
|
|
39
38
|
|
|
40
39
|
|
|
41
|
-
def extract_account_id_from_auth_header(headers) ->
|
|
40
|
+
def extract_account_id_from_auth_header(headers) -> str | None:
|
|
42
41
|
if access_key_id := extract_access_key_id_from_auth_header(headers):
|
|
43
42
|
return get_account_id_from_access_key_id(access_key_id)
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
def extract_access_key_id_from_auth_header(headers: dict[str, str]) ->
|
|
45
|
+
def extract_access_key_id_from_auth_header(headers: dict[str, str]) -> str | None:
|
|
47
46
|
auth = headers.get("Authorization") or ""
|
|
48
47
|
|
|
49
48
|
if auth.startswith("AWS4-"):
|
|
@@ -67,7 +66,7 @@ def extract_region_from_headers(headers) -> str:
|
|
|
67
66
|
return extract_region_from_auth_header(headers) or AWS_REGION_US_EAST_1
|
|
68
67
|
|
|
69
68
|
|
|
70
|
-
def extract_service_name_from_auth_header(headers: dict) ->
|
|
69
|
+
def extract_service_name_from_auth_header(headers: dict) -> str | None:
|
|
71
70
|
try:
|
|
72
71
|
auth_header = headers.get("authorization", "")
|
|
73
72
|
credential_scope = auth_header.split(",")[0].split()[1]
|
|
@@ -86,7 +86,7 @@ def create_api_gateway(
|
|
|
86
86
|
resources = resources or []
|
|
87
87
|
stage_name = stage_name or "testing"
|
|
88
88
|
usage_plan_name = usage_plan_name or "Basic Usage"
|
|
89
|
-
description = description or 'Test description for API "
|
|
89
|
+
description = description or f'Test description for API "{name}"'
|
|
90
90
|
|
|
91
91
|
LOG.info('Creating API resources under API Gateway "%s".', name)
|
|
92
92
|
api = client.create_rest_api(name=name, description=description)
|
localstack/utils/batch_policy.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import time
|
|
3
|
-
from typing import Generic,
|
|
3
|
+
from typing import Generic, TypeVar, overload
|
|
4
4
|
|
|
5
5
|
from pydantic import Field
|
|
6
6
|
from pydantic.dataclasses import dataclass
|
|
@@ -47,8 +47,8 @@ class Batcher(Generic[T]):
|
|
|
47
47
|
assert batcher.flush() == ["item1", "item2", "item3", "item4"]
|
|
48
48
|
"""
|
|
49
49
|
|
|
50
|
-
max_count:
|
|
51
|
-
max_window:
|
|
50
|
+
max_count: int | None = Field(default=None, description="Maximum number of items", ge=0)
|
|
51
|
+
max_window: float | None = Field(
|
|
52
52
|
default=None, description="Maximum time window in seconds", ge=0
|
|
53
53
|
)
|
|
54
54
|
|
localstack/utils/bootstrap.py
CHANGED
|
@@ -9,9 +9,9 @@ import shlex
|
|
|
9
9
|
import signal
|
|
10
10
|
import threading
|
|
11
11
|
import time
|
|
12
|
-
from collections.abc import Iterable
|
|
12
|
+
from collections.abc import Callable, Iterable
|
|
13
13
|
from functools import wraps
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import Any
|
|
15
15
|
|
|
16
16
|
from localstack import config, constants
|
|
17
17
|
from localstack.config import (
|
|
@@ -175,7 +175,7 @@ def get_docker_image_details(image_name: str = None) -> dict[str, str]:
|
|
|
175
175
|
return result
|
|
176
176
|
|
|
177
177
|
|
|
178
|
-
def get_image_environment_variable(env_name: str) ->
|
|
178
|
+
def get_image_environment_variable(env_name: str) -> str | None:
|
|
179
179
|
image_name = get_docker_image_to_start()
|
|
180
180
|
image_info = DOCKER_CLIENT.inspect_image(image_name)
|
|
181
181
|
image_envs = image_info["Config"]["Env"]
|
|
@@ -427,7 +427,7 @@ def validate_localstack_config(name: str):
|
|
|
427
427
|
|
|
428
428
|
def port_exposed(port):
|
|
429
429
|
for exposed in docker_ports:
|
|
430
|
-
if re.match(
|
|
430
|
+
if re.match(rf"^([0-9]+-)?{port}(-[0-9]+)?$", exposed):
|
|
431
431
|
return True
|
|
432
432
|
|
|
433
433
|
if not port_exposed(edge_port):
|
|
@@ -455,7 +455,7 @@ def get_docker_image_to_start():
|
|
|
455
455
|
|
|
456
456
|
def extract_port_flags(user_flags, port_mappings: PortMappings):
|
|
457
457
|
regex = r"-p\s+([0-9]+)(\-([0-9]+))?:([0-9]+)(\-([0-9]+))?"
|
|
458
|
-
matches = re.match("
|
|
458
|
+
matches = re.match(f".*{regex}", user_flags)
|
|
459
459
|
if matches:
|
|
460
460
|
for match in re.findall(regex, user_flags):
|
|
461
461
|
start = int(match[0])
|
|
@@ -544,7 +544,7 @@ class ContainerConfigurators:
|
|
|
544
544
|
|
|
545
545
|
@staticmethod
|
|
546
546
|
def gateway_listen(
|
|
547
|
-
port:
|
|
547
|
+
port: int | Iterable[int] | HostAndPort | Iterable[HostAndPort],
|
|
548
548
|
):
|
|
549
549
|
"""
|
|
550
550
|
Uses the given ports to configure GATEWAY_LISTEN. For instance, ``gateway_listen([4566, 443])`` would
|
|
@@ -1000,7 +1000,7 @@ class RunningContainer:
|
|
|
1000
1000
|
return
|
|
1001
1001
|
raise
|
|
1002
1002
|
|
|
1003
|
-
def inspect(self) -> dict[str,
|
|
1003
|
+
def inspect(self) -> dict[str, dict | str]:
|
|
1004
1004
|
return self.container_client.inspect_container(container_name_or_id=self.id)
|
|
1005
1005
|
|
|
1006
1006
|
def attach(self):
|
|
@@ -1028,7 +1028,7 @@ class ContainerLogPrinter:
|
|
|
1028
1028
|
self.callback = callback
|
|
1029
1029
|
|
|
1030
1030
|
self._closed = threading.Event()
|
|
1031
|
-
self._stream:
|
|
1031
|
+
self._stream: CancellableStream | None = None
|
|
1032
1032
|
|
|
1033
1033
|
def _can_start_streaming(self):
|
|
1034
1034
|
if self._closed.is_set():
|
|
@@ -1114,8 +1114,8 @@ class LocalstackContainerServer(Server):
|
|
|
1114
1114
|
|
|
1115
1115
|
def do_run(self):
|
|
1116
1116
|
if self.is_container_running():
|
|
1117
|
-
raise
|
|
1118
|
-
'LocalStack container named "
|
|
1117
|
+
raise ContainerRunning(
|
|
1118
|
+
f'LocalStack container named "{self.container.name}" is already running'
|
|
1119
1119
|
)
|
|
1120
1120
|
|
|
1121
1121
|
config.dirs.mkdirs()
|
|
@@ -1151,12 +1151,19 @@ class ContainerExists(Exception):
|
|
|
1151
1151
|
pass
|
|
1152
1152
|
|
|
1153
1153
|
|
|
1154
|
+
class ContainerRunning(Exception):
|
|
1155
|
+
pass
|
|
1156
|
+
|
|
1157
|
+
|
|
1154
1158
|
def prepare_docker_start():
|
|
1155
1159
|
# prepare environment for docker start
|
|
1156
1160
|
container_name = config.MAIN_CONTAINER_NAME
|
|
1157
1161
|
|
|
1158
1162
|
if DOCKER_CLIENT.is_container_running(container_name):
|
|
1159
|
-
raise
|
|
1163
|
+
raise ContainerRunning(f'LocalStack container named "{container_name}" is already running')
|
|
1164
|
+
|
|
1165
|
+
if container_name in DOCKER_CLIENT.get_all_container_names():
|
|
1166
|
+
raise ContainerExists(f'LocalStack container named "{container_name}" already exists')
|
|
1160
1167
|
|
|
1161
1168
|
config.dirs.mkdirs()
|
|
1162
1169
|
|
|
@@ -1308,7 +1315,8 @@ def start_infra_in_docker_detached(console, cli_params: dict[str, Any] = None):
|
|
|
1308
1315
|
console.log("preparing environment")
|
|
1309
1316
|
try:
|
|
1310
1317
|
prepare_docker_start()
|
|
1311
|
-
except
|
|
1318
|
+
except ContainerRunning as e:
|
|
1319
|
+
# starting in detached mode is idempotent, return if container is already running
|
|
1312
1320
|
console.print(str(e))
|
|
1313
1321
|
return
|
|
1314
1322
|
|
|
@@ -1330,7 +1338,7 @@ def start_infra_in_docker_detached(console, cli_params: dict[str, Any] = None):
|
|
|
1330
1338
|
console.log("detaching")
|
|
1331
1339
|
|
|
1332
1340
|
|
|
1333
|
-
def wait_container_is_ready(timeout:
|
|
1341
|
+
def wait_container_is_ready(timeout: float | None = None):
|
|
1334
1342
|
"""Blocks until the localstack main container is running and the ready marker has been printed."""
|
|
1335
1343
|
container_name = config.MAIN_CONTAINER_NAME
|
|
1336
1344
|
started = time.time()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
|
|
4
|
+
from plux import Plugin
|
|
5
|
+
|
|
6
|
+
from localstack.services.cloudformation.resource_provider import (
|
|
7
|
+
plugin_manager as cfn_plugin_manager,
|
|
8
|
+
)
|
|
9
|
+
from localstack.utils.catalog.catalog_loader import RemoteCatalogLoader
|
|
10
|
+
from localstack.utils.catalog.common import (
|
|
11
|
+
AwsServiceOperationsSupportInLatest,
|
|
12
|
+
AwsServicesSupportInLatest,
|
|
13
|
+
AwsServiceSupportAtRuntime,
|
|
14
|
+
CloudFormationResourcesSupportAtRuntime,
|
|
15
|
+
CloudFormationResourcesSupportInLatest,
|
|
16
|
+
LocalstackEmulatorType,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
ServiceName = str
|
|
20
|
+
ServiceOperations = set[str]
|
|
21
|
+
ProviderName = str
|
|
22
|
+
CfnResourceName = str
|
|
23
|
+
CfnResourceMethodName = str
|
|
24
|
+
AwsServicesSupportStatus = (
|
|
25
|
+
AwsServiceSupportAtRuntime | AwsServicesSupportInLatest | AwsServiceOperationsSupportInLatest
|
|
26
|
+
)
|
|
27
|
+
CfnResourceSupportStatus = (
|
|
28
|
+
CloudFormationResourcesSupportInLatest | CloudFormationResourcesSupportAtRuntime
|
|
29
|
+
)
|
|
30
|
+
CfnResourceCatalog = dict[LocalstackEmulatorType, dict[CfnResourceName, set[CfnResourceMethodName]]]
|
|
31
|
+
|
|
32
|
+
LOG = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CatalogPlugin(Plugin):
|
|
36
|
+
namespace = "localstack.utils.catalog"
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _get_cfn_resources_catalog(cloudformation_resources: dict) -> CfnResourceCatalog:
|
|
40
|
+
cfn_resources_catalog = {}
|
|
41
|
+
for emulator_type, resources in cloudformation_resources.items():
|
|
42
|
+
cfn_resources_catalog[emulator_type] = {}
|
|
43
|
+
for resource_name, resource in resources.items():
|
|
44
|
+
cfn_resources_catalog[emulator_type][resource_name] = set(resource.methods)
|
|
45
|
+
return cfn_resources_catalog
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def _get_services_at_runtime() -> set[ServiceName]:
|
|
49
|
+
from localstack.services.plugins import SERVICE_PLUGINS
|
|
50
|
+
|
|
51
|
+
return set(SERVICE_PLUGINS.list_available())
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _get_cfn_resources_available_at_runtime() -> set[CfnResourceName]:
|
|
55
|
+
return set(cfn_plugin_manager.list_names())
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def get_aws_service_status(
|
|
59
|
+
self, service_name: str, operation_name: str | None = None
|
|
60
|
+
) -> AwsServicesSupportStatus | None:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def get_cloudformation_resource_status(
|
|
65
|
+
self, resource_name: str, service_name: str, is_pro_resource: bool = False
|
|
66
|
+
) -> CfnResourceSupportStatus | AwsServicesSupportInLatest | None:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class AwsCatalogRuntimePlugin(CatalogPlugin):
|
|
71
|
+
name = "aws-catalog-runtime-only"
|
|
72
|
+
|
|
73
|
+
def get_aws_service_status(
|
|
74
|
+
self, service_name: str, operation_name: str | None = None
|
|
75
|
+
) -> AwsServicesSupportStatus | None:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
def get_cloudformation_resource_status(
|
|
79
|
+
self, resource_name: str, service_name: str, is_pro_resource: bool = False
|
|
80
|
+
) -> CfnResourceSupportStatus | AwsServicesSupportInLatest | None:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AwsCatalogRemoteStatePlugin(CatalogPlugin):
|
|
85
|
+
name = "aws-catalog-remote-state"
|
|
86
|
+
current_emulator_type: LocalstackEmulatorType = LocalstackEmulatorType.COMMUNITY
|
|
87
|
+
services_in_latest: dict[ServiceName, dict[LocalstackEmulatorType, ServiceOperations]] = {}
|
|
88
|
+
services_at_runtime: set[ServiceName] = set()
|
|
89
|
+
cfn_resources_in_latest: CfnResourceCatalog = {}
|
|
90
|
+
cfn_resources_at_runtime: set[CfnResourceName] = set()
|
|
91
|
+
|
|
92
|
+
def __init__(self, remote_catalog_loader: RemoteCatalogLoader | None = None) -> None:
|
|
93
|
+
catalog_loader = remote_catalog_loader or RemoteCatalogLoader()
|
|
94
|
+
remote_catalog = catalog_loader.get_remote_catalog()
|
|
95
|
+
for service_name, emulators in remote_catalog.services.items():
|
|
96
|
+
for emulator_type, service_provider in emulators.items():
|
|
97
|
+
self.services_in_latest.setdefault(service_name, {})[emulator_type] = set(
|
|
98
|
+
service_provider.operations
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
self.cfn_resources_in_latest = self._get_cfn_resources_catalog(
|
|
102
|
+
remote_catalog.cloudformation_resources
|
|
103
|
+
)
|
|
104
|
+
self.cfn_resources_at_runtime = self._get_cfn_resources_available_at_runtime()
|
|
105
|
+
self.services_at_runtime = self._get_services_at_runtime()
|
|
106
|
+
|
|
107
|
+
def get_aws_service_status(
|
|
108
|
+
self, service_name: str, operation_name: str | None = None
|
|
109
|
+
) -> AwsServicesSupportStatus | None:
|
|
110
|
+
if not self.services_in_latest:
|
|
111
|
+
return None
|
|
112
|
+
if service_name not in self.services_in_latest:
|
|
113
|
+
return AwsServicesSupportInLatest.NOT_SUPPORTED
|
|
114
|
+
if self.current_emulator_type not in self.services_in_latest[service_name]:
|
|
115
|
+
return AwsServicesSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE
|
|
116
|
+
if not operation_name:
|
|
117
|
+
return AwsServicesSupportInLatest.SUPPORTED
|
|
118
|
+
if operation_name in self.services_in_latest[service_name][self.current_emulator_type]:
|
|
119
|
+
return AwsServiceOperationsSupportInLatest.SUPPORTED
|
|
120
|
+
for emulator_type in self.services_in_latest[service_name]:
|
|
121
|
+
if emulator_type is self.current_emulator_type:
|
|
122
|
+
continue
|
|
123
|
+
if operation_name in self.services_in_latest[service_name][emulator_type]:
|
|
124
|
+
return AwsServiceOperationsSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE
|
|
125
|
+
return AwsServiceOperationsSupportInLatest.NOT_SUPPORTED
|
|
126
|
+
|
|
127
|
+
def get_cloudformation_resource_status(
|
|
128
|
+
self, resource_name: str, service_name: str, is_pro_resource: bool = False
|
|
129
|
+
) -> CfnResourceSupportStatus | AwsServicesSupportInLatest | None:
|
|
130
|
+
if resource_name in self.cfn_resources_at_runtime:
|
|
131
|
+
return CloudFormationResourcesSupportAtRuntime.AVAILABLE
|
|
132
|
+
if service_name in self.services_at_runtime:
|
|
133
|
+
if resource_name in self.cfn_resources_in_latest[self.current_emulator_type]:
|
|
134
|
+
return CloudFormationResourcesSupportInLatest.SUPPORTED
|
|
135
|
+
else:
|
|
136
|
+
return CloudFormationResourcesSupportInLatest.NOT_SUPPORTED
|
|
137
|
+
if service_name in self.services_in_latest:
|
|
138
|
+
return self.get_aws_service_status(service_name, operation_name=None)
|
|
139
|
+
return AwsServicesSupportInLatest.NOT_SUPPORTED
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from json import JSONDecodeError
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from localstack import config, constants
|
|
10
|
+
from localstack.utils.catalog.common import AwsRemoteCatalog
|
|
11
|
+
from localstack.utils.http import get_proxies
|
|
12
|
+
from localstack.utils.json import FileMappedDocument
|
|
13
|
+
|
|
14
|
+
LOG = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
AWS_CATALOG_FILE_NAME = "aws_catalog.json"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RemoteCatalogVersionResponse(BaseModel):
|
|
20
|
+
emulator_type: str
|
|
21
|
+
version: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AwsCatalogLoaderException(Exception):
|
|
25
|
+
def __init__(self, msg: str, *args):
|
|
26
|
+
super().__init__(msg, *args)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RemoteCatalogLoader:
|
|
30
|
+
supported_schema_version = "v1"
|
|
31
|
+
api_endpoint_catalog = f"{constants.API_ENDPOINT}/license/catalog"
|
|
32
|
+
catalog_file_path = Path(config.dirs.cache) / AWS_CATALOG_FILE_NAME
|
|
33
|
+
|
|
34
|
+
def get_remote_catalog(self) -> AwsRemoteCatalog:
|
|
35
|
+
catalog_doc = FileMappedDocument(self.catalog_file_path)
|
|
36
|
+
cached_catalog = AwsRemoteCatalog(**catalog_doc) if catalog_doc else None
|
|
37
|
+
if cached_catalog:
|
|
38
|
+
cached_catalog_version = cached_catalog.localstack.version
|
|
39
|
+
if not self._should_update_cached_catalog(cached_catalog_version):
|
|
40
|
+
return cached_catalog
|
|
41
|
+
catalog = self._get_catalog_from_platform()
|
|
42
|
+
self._save_catalog_to_cache(catalog_doc, catalog)
|
|
43
|
+
return catalog
|
|
44
|
+
|
|
45
|
+
def _get_latest_localstack_version(self) -> str:
|
|
46
|
+
try:
|
|
47
|
+
proxies = get_proxies()
|
|
48
|
+
response = requests.get(
|
|
49
|
+
f"{self.api_endpoint_catalog}/aws/version",
|
|
50
|
+
verify=not config.is_env_true("SSL_NO_VERIFY"),
|
|
51
|
+
proxies=proxies,
|
|
52
|
+
)
|
|
53
|
+
if response.ok:
|
|
54
|
+
return RemoteCatalogVersionResponse.model_validate(response.content).version
|
|
55
|
+
self._raise_server_error(response)
|
|
56
|
+
except requests.exceptions.RequestException as e:
|
|
57
|
+
raise AwsCatalogLoaderException(
|
|
58
|
+
f"An unexpected network error occurred when trying to fetch latest localstack version: {e}"
|
|
59
|
+
) from e
|
|
60
|
+
|
|
61
|
+
def _should_update_cached_catalog(self, current_catalog_version: str) -> bool:
|
|
62
|
+
try:
|
|
63
|
+
latest_version = self._get_latest_localstack_version()
|
|
64
|
+
return latest_version != current_catalog_version
|
|
65
|
+
except Exception as e:
|
|
66
|
+
LOG.warning(
|
|
67
|
+
"Failed to retrieve the latest catalog version, cached catalog update skipped: %s",
|
|
68
|
+
e,
|
|
69
|
+
)
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
def _save_catalog_to_cache(self, catalog_doc: FileMappedDocument, catalog: AwsRemoteCatalog):
|
|
73
|
+
catalog_doc.clear()
|
|
74
|
+
catalog_doc.update(catalog.model_dump())
|
|
75
|
+
catalog_doc.save()
|
|
76
|
+
|
|
77
|
+
def _get_catalog_from_platform(self) -> AwsRemoteCatalog:
|
|
78
|
+
try:
|
|
79
|
+
proxies = get_proxies()
|
|
80
|
+
response = requests.post(
|
|
81
|
+
self.api_endpoint_catalog,
|
|
82
|
+
verify=not config.is_env_true("SSL_NO_VERIFY"),
|
|
83
|
+
proxies=proxies,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if response.ok:
|
|
87
|
+
return self._parse_catalog(response.content)
|
|
88
|
+
self._raise_server_error(response)
|
|
89
|
+
except requests.exceptions.RequestException as e:
|
|
90
|
+
raise AwsCatalogLoaderException(
|
|
91
|
+
f"An unexpected network error occurred when trying to fetch remote catalog: {e}"
|
|
92
|
+
) from e
|
|
93
|
+
|
|
94
|
+
def _parse_catalog(self, document: bytes) -> AwsRemoteCatalog | None:
|
|
95
|
+
try:
|
|
96
|
+
catalog_json = json.loads(document)
|
|
97
|
+
except JSONDecodeError as e:
|
|
98
|
+
raise AwsCatalogLoaderException(f"Could not de-serialize json catalog: {e}") from e
|
|
99
|
+
remote_catalog = AwsRemoteCatalog.model_validate(catalog_json)
|
|
100
|
+
if remote_catalog.schema_version != self.supported_schema_version:
|
|
101
|
+
raise AwsCatalogLoaderException(
|
|
102
|
+
f"Unsupported schema version: '{remote_catalog.schema_version}'. Only '{self.supported_schema_version}' is supported"
|
|
103
|
+
)
|
|
104
|
+
return remote_catalog
|
|
105
|
+
|
|
106
|
+
def _raise_server_error(self, response: requests.Response):
|
|
107
|
+
try:
|
|
108
|
+
server_error = response.json()
|
|
109
|
+
if error_message := server_error.get("message"):
|
|
110
|
+
raise AwsCatalogLoaderException(
|
|
111
|
+
f"Unexpected AWS catalog server error: {response.text}"
|
|
112
|
+
)
|
|
113
|
+
raise AwsCatalogLoaderException(
|
|
114
|
+
f"A server error occurred while calling remote catalog API (HTTP {response.status_code}): {error_message}"
|
|
115
|
+
)
|
|
116
|
+
except Exception:
|
|
117
|
+
raise AwsCatalogLoaderException(
|
|
118
|
+
f"An unexpected server error occurred while calling remote catalog API (HTTP {response.status_code}): {response.text}"
|
|
119
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CloudFormationResource(BaseModel):
|
|
7
|
+
methods: list[str]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AwsServiceCatalog(BaseModel):
|
|
11
|
+
provider: str
|
|
12
|
+
operations: list[str]
|
|
13
|
+
plans: list[str]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LocalStackMetadata(BaseModel):
|
|
17
|
+
version: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AwsRemoteCatalog(BaseModel):
|
|
21
|
+
schema_version: str
|
|
22
|
+
localstack: LocalStackMetadata
|
|
23
|
+
services: dict[str, dict[str, AwsServiceCatalog]]
|
|
24
|
+
cloudformation_resources: dict[str, dict[str, CloudFormationResource]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LocalstackEmulatorType(StrEnum):
|
|
28
|
+
COMMUNITY = "community"
|
|
29
|
+
PRO = "pro"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AwsServiceSupportAtRuntime(StrEnum):
|
|
33
|
+
AVAILABLE = "AVAILABLE"
|
|
34
|
+
AVAILABLE_WITH_LICENSE_UPGRADE = "AVAILABLE_WITH_LICENSE_UPGRADE"
|
|
35
|
+
NOT_IMPLEMENTED = "NOT_IMPLEMENTED"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AwsServicesSupportInLatest(StrEnum):
|
|
39
|
+
SUPPORTED = "SUPPORTED"
|
|
40
|
+
SUPPORTED_WITH_LICENSE_UPGRADE = "SUPPORTED_WITH_LICENSE_UPGRADE"
|
|
41
|
+
NOT_SUPPORTED = "NOT_SUPPORTED"
|
|
42
|
+
NON_DEFAULT_PROVIDER = "NON_DEFAULT_PROVIDER"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AwsServiceOperationsSupportInLatest(StrEnum):
|
|
46
|
+
SUPPORTED = "SUPPORTED"
|
|
47
|
+
SUPPORTED_WITH_LICENSE_UPGRADE = "SUPPORTED_WITH_LICENSE_UPGRADE"
|
|
48
|
+
NOT_SUPPORTED = "NOT_SUPPORTED"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CloudFormationResourcesSupportInLatest(StrEnum):
|
|
52
|
+
SUPPORTED = "SUPPORTED"
|
|
53
|
+
NOT_SUPPORTED = "NOT_SUPPORTED"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CloudFormationResourcesSupportAtRuntime(StrEnum):
|
|
57
|
+
AVAILABLE = "AVAILABLE"
|
|
58
|
+
NOT_IMPLEMENTED = "NOT_IMPLEMENTED"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from plux import PluginManager
|
|
4
|
+
|
|
5
|
+
from localstack.utils.catalog.catalog import CatalogPlugin
|
|
6
|
+
from localstack.utils.objects import singleton_factory
|
|
7
|
+
|
|
8
|
+
LOG = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@singleton_factory
|
|
12
|
+
def get_aws_catalog() -> CatalogPlugin:
|
|
13
|
+
plugin_manager = PluginManager(CatalogPlugin.namespace)
|
|
14
|
+
try:
|
|
15
|
+
plugin_name = "aws-catalog-remote-state-with-license"
|
|
16
|
+
if not plugin_manager.exists(plugin_name):
|
|
17
|
+
plugin_name = "aws-catalog-remote-state"
|
|
18
|
+
return plugin_manager.load(plugin_name)
|
|
19
|
+
except Exception as e:
|
|
20
|
+
LOG.debug(
|
|
21
|
+
"Failed to load catalog plugin with the latest LocalStack services support data, falling back to catalog without remote state: %s",
|
|
22
|
+
e,
|
|
23
|
+
)
|
|
24
|
+
# Try to load runtime catalog from pro version first
|
|
25
|
+
fallback_plugin_name = "aws-catalog-runtime-only-with-license"
|
|
26
|
+
if not plugin_manager.exists(fallback_plugin_name):
|
|
27
|
+
fallback_plugin_name = "aws-catalog-runtime-only"
|
|
28
|
+
return plugin_manager.load(fallback_plugin_name)
|
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
import time
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
4
|
from itertools import islice
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import TypedDict
|
|
6
6
|
|
|
7
7
|
from werkzeug import Response as WerkzeugResponse
|
|
8
8
|
|
|
@@ -20,8 +20,8 @@ LOG = logging.getLogger(__name__)
|
|
|
20
20
|
class SqsMetricBatchData(TypedDict, total=False):
|
|
21
21
|
MetricName: str
|
|
22
22
|
QueueName: str
|
|
23
|
-
Value:
|
|
24
|
-
Unit:
|
|
23
|
+
Value: int | None
|
|
24
|
+
Unit: str | None
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def dimension_lambda(kwargs):
|
|
@@ -30,7 +30,7 @@ def dimension_lambda(kwargs):
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def publish_lambda_metric(
|
|
33
|
-
metric, value, kwargs, account_id:
|
|
33
|
+
metric, value, kwargs, account_id: str | None = None, region_name: str | None = None
|
|
34
34
|
):
|
|
35
35
|
# publish metric only if CloudWatch service is available
|
|
36
36
|
if not is_api_enabled("cloudwatch"):
|
|
@@ -155,7 +155,7 @@ def store_cloudwatch_logs(
|
|
|
155
155
|
log_stream_name,
|
|
156
156
|
log_output,
|
|
157
157
|
start_time=None,
|
|
158
|
-
auto_create_group:
|
|
158
|
+
auto_create_group: bool | None = True,
|
|
159
159
|
):
|
|
160
160
|
if not is_api_enabled("logs"):
|
|
161
161
|
return
|
localstack/utils/collections.py
CHANGED
|
@@ -5,10 +5,9 @@ and manipulate python collection (dicts, list, sets).
|
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
7
|
import re
|
|
8
|
-
from collections.abc import Iterable, Iterator, Mapping, Sized
|
|
8
|
+
from collections.abc import Callable, Iterable, Iterator, Mapping, Sized
|
|
9
9
|
from typing import (
|
|
10
10
|
Any,
|
|
11
|
-
Callable,
|
|
12
11
|
Optional,
|
|
13
12
|
TypedDict,
|
|
14
13
|
TypeVar,
|
|
@@ -116,7 +115,7 @@ class PaginatedList(list[_ListType]):
|
|
|
116
115
|
next_token: str = None,
|
|
117
116
|
page_size: int = None,
|
|
118
117
|
filter_function: Callable[[_ListType], bool] = None,
|
|
119
|
-
) -> tuple[list[_ListType],
|
|
118
|
+
) -> tuple[list[_ListType], str | None]:
|
|
120
119
|
if filter_function is not None:
|
|
121
120
|
result_list = list(filter(filter_function, self))
|
|
122
121
|
else:
|
|
@@ -148,7 +147,7 @@ class PaginatedList(list[_ListType]):
|
|
|
148
147
|
class CustomExpiryTTLCache(cachetools.TTLCache):
|
|
149
148
|
"""TTLCache that allows to set custom expiry times for individual keys."""
|
|
150
149
|
|
|
151
|
-
def set_expiry(self, key: Any, ttl:
|
|
150
|
+
def set_expiry(self, key: Any, ttl: float | int) -> float:
|
|
152
151
|
"""Set the expiry of the given key in a TTLCache to (<current_time> + <ttl>)"""
|
|
153
152
|
with self.timer as time:
|
|
154
153
|
# note: need to access the internal dunder API here
|
|
@@ -315,7 +314,7 @@ def is_list_or_tuple(obj) -> bool:
|
|
|
315
314
|
return isinstance(obj, (list, tuple))
|
|
316
315
|
|
|
317
316
|
|
|
318
|
-
def ensure_list(obj: Any, wrap_none=False) ->
|
|
317
|
+
def ensure_list(obj: Any, wrap_none=False) -> list | None:
|
|
319
318
|
"""Wrap the given object in a list, or return the object itself if it already is a list."""
|
|
320
319
|
if obj is None and not wrap_none:
|
|
321
320
|
return obj
|
|
@@ -414,7 +413,7 @@ def items_equivalent(list1, list2, comparator):
|
|
|
414
413
|
return True
|
|
415
414
|
|
|
416
415
|
|
|
417
|
-
def is_none_or_empty(obj:
|
|
416
|
+
def is_none_or_empty(obj: str | None | list | None) -> bool:
|
|
418
417
|
return (
|
|
419
418
|
obj is None
|
|
420
419
|
or (isinstance(obj, str) and obj.strip() == "")
|
|
@@ -475,7 +474,7 @@ def convert_to_typed_dict(typed_dict: type[T], obj: dict, strict: bool = False)
|
|
|
475
474
|
return result
|
|
476
475
|
|
|
477
476
|
|
|
478
|
-
def dict_multi_values(elements:
|
|
477
|
+
def dict_multi_values(elements: list | dict) -> dict[str, list[Any]]:
|
|
479
478
|
"""
|
|
480
479
|
Return a dictionary with the original keys from the list of dictionary and the
|
|
481
480
|
values are the list of values of the original dictionary.
|
|
@@ -516,7 +515,7 @@ def split_list_by(
|
|
|
516
515
|
return truthy, falsy
|
|
517
516
|
|
|
518
517
|
|
|
519
|
-
def is_comma_delimited_list(string: str, item_regex:
|
|
518
|
+
def is_comma_delimited_list(string: str, item_regex: str | None = None) -> bool:
|
|
520
519
|
"""
|
|
521
520
|
Checks if the given string is a comma-delimited list of items.
|
|
522
521
|
The optional `item_regex` parameter specifies the regex pattern for each item in the list.
|