aws-lambda-powertools 3.31.0__tar.gz → 3.31.1__tar.gz
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.
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/PKG-INFO +2 -2
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/events_appsync/appsync_events.py +1 -1
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/dependant.py +8 -1
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/version.py +1 -1
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/base.py +59 -23
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/config.py +64 -8
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py +70 -34
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/dynamodb.py +46 -5
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/record.py +2 -1
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/event_bridge_event.py +1 -1
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/event_source.py +24 -5
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/dynamodb.py +2 -1
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/pyproject.toml +2 -2
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/setup.py +3 -3
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/LICENSE +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/README.md +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/THIRD-PARTY-LICENSES +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/api_gateway.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/appsync.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/bedrock_agent.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/bedrock_agent_function.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/content_types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/depends.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/events_appsync/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/events_appsync/_registry.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/events_appsync/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/events_appsync/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/events_appsync/functions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/events_appsync/router.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/events_appsync/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/exception_handling.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/graphql_appsync/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/graphql_appsync/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/graphql_appsync/router.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/http_resolver.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/lambda_function_url.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/middlewares/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/middlewares/async_utils.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/middlewares/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/middlewares/schema_validation.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/compat.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/config.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/constants.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/encoders.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/merge.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/models.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/params.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/pydantic_loader.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/schema_generator.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/swagger_ui/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/swagger_ui/swagger-ui-bundle.min.js +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/swagger_ui/swagger-ui.min.css +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/openapi/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/request.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/router.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/util.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/event_handler/vpc_lattice.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/exceptions/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/buffer/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/buffer/cache.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/buffer/config.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/buffer/functions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/buffer/handler.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/constants.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/correlation_paths.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/filters.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/formatter.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/formatters/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/formatters/datadog.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/lambda_context.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/logger.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/logging/utils.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/functions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/metric.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/metrics.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/cloudwatch_emf/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/cloudwatch_emf/cloudwatch.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/cloudwatch_emf/constants.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/cloudwatch_emf/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/cloudwatch_emf/metric_properties.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/cloudwatch_emf/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/cold_start.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/datadog/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/datadog/datadog.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/datadog/metrics.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/provider/datadog/warnings.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/metrics/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/middleware_factory/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/middleware_factory/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/middleware_factory/factory.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/package_logger.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/py.typed +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/cache_dict.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/constants.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/cookies.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/dynamodb_deserializer.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/functions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/headers_serializer.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/json_encoder.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/lazy_import.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/shared/user_agent.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/tracing/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/tracing/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/tracing/extensions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/tracing/tracer.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/batch/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/batch/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/batch/decorators.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/batch/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/batch/sqs_fifo_partial_processor.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/batch/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/circuit_breaker.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/circuit_breaker_alpha/states.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/active_mq_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/alb_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/api_gateway_websocket_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/appsync/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/appsync/scalar_types_utils.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/appsync_authorizer_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/appsync_resolver_events_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/aws_config_rule_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/bedrock_agent_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/bedrock_agent_function_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/cloud_watch_alarm_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/cloud_watch_custom_widget_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/cloud_watch_logs_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/cloudformation_custom_resource_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/code_deploy_lifecycle_hook_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/code_pipeline_job_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/cognito_user_pool_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/common.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/connect_contact_flow_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/iot_registry_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/kafka_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/kinesis_firehose_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/kinesis_stream_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/lambda_function_url_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/rabbit_mq_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/s3_batch_operation_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/s3_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/s3_object_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/secrets_manager_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/ses_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/shared_functions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/sns_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/sqs_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/transfer_family_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_masking/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_masking/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_masking/constants.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_masking/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_masking/provider/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_masking/provider/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_masking/provider/kms/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/data_masking/provider/kms/aws_encryption_sdk.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/feature_flags/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/feature_flags/appconfig.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/feature_flags/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/feature_flags/comparators.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/feature_flags/constants.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/feature_flags/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/feature_flags/feature_flags.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/feature_flags/schema.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/feature_flags/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/config.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/hook.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/idempotency.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/persistence/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/persistence/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/persistence/cache.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/persistence/datarecord.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/persistence/redis.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/serialization/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/serialization/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/serialization/custom_dict.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/serialization/dataclass.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/serialization/functions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/serialization/no_op.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/idempotency/serialization/pydantic.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/jmespath_utils/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/jmespath_utils/envelopes.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/consumer_records.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/deserializer/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/deserializer/avro.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/deserializer/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/deserializer/default.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/deserializer/deserializer.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/deserializer/json.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/deserializer/protobuf.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/kafka_consumer.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/schema_config.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/serialization/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/serialization/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/serialization/custom_dict.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/serialization/dataclass.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/serialization/pydantic.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/serialization/serialization.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/kafka/serialization/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/metadata/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/metadata/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/metadata/lambda_metadata.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parameters/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parameters/appconfig.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parameters/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parameters/constants.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parameters/dynamodb.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parameters/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parameters/secrets.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parameters/ssm.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parameters/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/apigw.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/apigw_websocket.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/apigwv2.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/bedrock_agent.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/cloudwatch.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/dynamodb.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/event_bridge.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/kafka.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/kinesis.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/kinesis_firehose.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/lambda_function_url.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/sns.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/sqs.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/vpc_lattice.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/envelopes/vpc_latticev2.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/functions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/alb.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/apigw.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/apigw_websocket.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/apigwv2.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/appsync.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/appsync_events.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/bedrock_agent.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/cloudwatch.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/cognito.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/event_bridge.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/iot_registry_events.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/kafka.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/kinesis.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/kinesis_firehose.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/kinesis_firehose_sqs.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/lambda_function_url.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/s3.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/s3_batch_operation.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/s3_event_notification.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/s3_object_event.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/ses.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/sns.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/sqs.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/transfer_family.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/vpc_lattice.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/models/vpc_latticev2.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/parser.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/parser/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/serialization.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/_s3_seekable_io.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/compat.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/constants.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/s3_object.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/transformations/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/transformations/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/transformations/csv.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/transformations/gzip.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/transformations/zip.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/streaming/types.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/typing/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/typing/lambda_client_context.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/typing/lambda_client_context_mobile_client.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/typing/lambda_cognito_identity.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/typing/lambda_context.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/validation/__init__.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/validation/base.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/validation/envelopes.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/validation/exceptions.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/utilities/validation/validator.py +0 -0
- {aws_lambda_powertools-3.31.0 → aws_lambda_powertools-3.31.1}/aws_lambda_powertools/warnings/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: aws_lambda_powertools
|
|
3
|
-
Version: 3.31.
|
|
3
|
+
Version: 3.31.1
|
|
4
4
|
Summary: Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless best practices and increase developer velocity.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: aws_lambda_powertools,aws,tracing,logging,lambda,powertools,feature_flags,idempotency,middleware
|
|
@@ -29,7 +29,7 @@ Provides-Extra: tracer
|
|
|
29
29
|
Provides-Extra: validation
|
|
30
30
|
Provides-Extra: valkey
|
|
31
31
|
Requires-Dist: avro (>=1.12.0,<2.0.0) ; extra == "kafka-consumer-avro"
|
|
32
|
-
Requires-Dist: aws-encryption-sdk (>=
|
|
32
|
+
Requires-Dist: aws-encryption-sdk (>=4.0.5,<5.0.0) ; extra == "all" or extra == "datamasking"
|
|
33
33
|
Requires-Dist: aws-xray-sdk (>=2.8.0,<3.0.0) ; extra == "tracer" or extra == "all"
|
|
34
34
|
Requires-Dist: boto3 (>=1.34.32,<2.0.0) ; extra == "aws-sdk"
|
|
35
35
|
Requires-Dist: datadog-lambda (>=8.114.0,<9.0.0) ; extra == "datadog"
|
|
@@ -321,7 +321,7 @@ class AppSyncEventsResolver(Router):
|
|
|
321
321
|
if isinstance(ret, Exception)
|
|
322
322
|
else {"id": e.get("id"), "payload": ret}
|
|
323
323
|
)
|
|
324
|
-
for e, ret in zip(self.current_event.events, results)
|
|
324
|
+
for e, ret in zip(self.current_event.events, results, strict=True)
|
|
325
325
|
],
|
|
326
326
|
)
|
|
327
327
|
|
|
@@ -156,6 +156,7 @@ def get_dependant(
|
|
|
156
156
|
call: Callable[..., Any],
|
|
157
157
|
name: str | None = None,
|
|
158
158
|
responses: dict[int, OpenAPIResponse] | None = None,
|
|
159
|
+
is_dependency: bool = False,
|
|
159
160
|
) -> Dependant:
|
|
160
161
|
"""
|
|
161
162
|
Returns a dependant model for a handler function. A dependant model is a model that contains
|
|
@@ -200,6 +201,7 @@ def get_dependant(
|
|
|
200
201
|
sub_dependant = get_dependant(
|
|
201
202
|
path=path,
|
|
202
203
|
call=depends_instance.dependency,
|
|
204
|
+
is_dependency=True,
|
|
203
205
|
)
|
|
204
206
|
dependant.dependencies.append(
|
|
205
207
|
DependencyParam(
|
|
@@ -229,7 +231,12 @@ def get_dependant(
|
|
|
229
231
|
else:
|
|
230
232
|
add_param_to_fields(field=param_field, dependant=dependant)
|
|
231
233
|
|
|
232
|
-
|
|
234
|
+
# A dependency's return value is injected directly into the handler, never serialized as a
|
|
235
|
+
# response body nor validated as an OpenAPI parameter — so building a Pydantic schema for it is
|
|
236
|
+
# both unused (no reader consumes a sub-dependency's return_param) and actively harmful: it crashes
|
|
237
|
+
# for arbitrary return types (e.g. boto3/botocore clients). Only the top-level handler needs it.
|
|
238
|
+
if not is_dependency:
|
|
239
|
+
_add_return_annotation(dependant, endpoint_signature)
|
|
233
240
|
_add_extra_responses(dependant, responses)
|
|
234
241
|
|
|
235
242
|
return dependant
|
|
@@ -10,6 +10,8 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import datetime
|
|
12
12
|
import logging
|
|
13
|
+
import os
|
|
14
|
+
import threading
|
|
13
15
|
import uuid
|
|
14
16
|
from typing import TYPE_CHECKING, Any
|
|
15
17
|
|
|
@@ -37,8 +39,31 @@ _LOCAL_SUCCESSES: dict[str, int] = {}
|
|
|
37
39
|
# recovered), so stale local failure streaks can be invalidated.
|
|
38
40
|
_LAST_OBSERVED_STATE: dict[str, CircuitState] = {}
|
|
39
41
|
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
+
# Guards the three dicts above. Increments are read-modify-write and a threshold
|
|
43
|
+
# crossing must be observed by exactly one thread, so every access goes through this
|
|
44
|
+
# lock. Held only while mutating the dicts, never across persistence writes or user
|
|
45
|
+
# callbacks.
|
|
46
|
+
_COUNTERS_LOCK = threading.Lock()
|
|
47
|
+
|
|
48
|
+
# Identifier used to claim the half-open probe lock, unique per thread so the store's
|
|
49
|
+
# conditional election picks a single prober across threads as well as processes.
|
|
50
|
+
_PROBE_OWNER = threading.local()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _probe_owner_id() -> str:
|
|
54
|
+
"""
|
|
55
|
+
Return this thread's stable probe-owner identifier, minting it on first use.
|
|
56
|
+
|
|
57
|
+
A uuid in thread-local storage rather than ``threading.get_ident()``: the OS reuses
|
|
58
|
+
thread ids, and a recycled id would let an unrelated thread pass the owner check and
|
|
59
|
+
probe alongside the real owner. The pid check re-mints the id in forked children,
|
|
60
|
+
which inherit the forking thread's local storage.
|
|
61
|
+
"""
|
|
62
|
+
pid = os.getpid()
|
|
63
|
+
if getattr(_PROBE_OWNER, "pid", None) != pid:
|
|
64
|
+
_PROBE_OWNER.id = f"{pid}#{uuid.uuid4().hex}"
|
|
65
|
+
_PROBE_OWNER.pid = pid
|
|
66
|
+
return _PROBE_OWNER.id
|
|
42
67
|
|
|
43
68
|
|
|
44
69
|
class CircuitBreakerHandler:
|
|
@@ -111,32 +136,35 @@ class CircuitBreakerHandler:
|
|
|
111
136
|
# If we previously observed a non-CLOSED state and the circuit is now back to
|
|
112
137
|
# CLOSED, another environment completed the recovery cycle. Reset local counters
|
|
113
138
|
# so a stale partial failure streak doesn't immediately re-trip the circuit.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
139
|
+
with _COUNTERS_LOCK:
|
|
140
|
+
prev = _LAST_OBSERVED_STATE.get(self.name)
|
|
141
|
+
if prev is not None and prev != CircuitState.CLOSED:
|
|
142
|
+
_LOCAL_FAILURES[self.name] = 0
|
|
143
|
+
_LAST_OBSERVED_STATE[self.name] = CircuitState.CLOSED
|
|
118
144
|
return self._call_closed()
|
|
119
145
|
|
|
120
146
|
if record.state == CircuitState.OPEN:
|
|
121
|
-
|
|
147
|
+
with _COUNTERS_LOCK:
|
|
148
|
+
_LAST_OBSERVED_STATE[self.name] = CircuitState.OPEN
|
|
122
149
|
# ``opened_at`` may legitimately be 0 (epoch); treat only None as missing.
|
|
123
150
|
opened_at = record.opened_at if record.opened_at is not None else self._now()
|
|
124
151
|
if self._now() >= opened_at + self.config.recovery_timeout:
|
|
125
152
|
# Recovery window elapsed: try to become the single prober.
|
|
126
|
-
if self.persistence_store.try_acquire_half_open(self.name,
|
|
153
|
+
if self.persistence_store.try_acquire_half_open(self.name, _probe_owner_id(), opened_at):
|
|
127
154
|
self._notify(CircuitState.OPEN, CircuitState.HALF_OPEN, opened_at=opened_at)
|
|
128
155
|
return self._call_probe()
|
|
129
156
|
return self._open_response(record.to_circuit_info())
|
|
130
157
|
|
|
131
|
-
# HALF_OPEN: only the
|
|
132
|
-
|
|
133
|
-
|
|
158
|
+
# HALF_OPEN: only the thread that owns the probe lock runs.
|
|
159
|
+
with _COUNTERS_LOCK:
|
|
160
|
+
_LAST_OBSERVED_STATE[self.name] = CircuitState.HALF_OPEN
|
|
161
|
+
if record.half_open_owner == _probe_owner_id():
|
|
134
162
|
return self._call_probe()
|
|
135
163
|
|
|
136
164
|
# If the probe lease has expired (owner recycled mid-probe), take over.
|
|
137
165
|
if record.probe_lease_expiry is not None and self._now() >= record.probe_lease_expiry:
|
|
138
166
|
logger.debug("Circuit '%s' probe lease expired; attempting takeover.", self.name)
|
|
139
|
-
if self.persistence_store.try_acquire_half_open(self.name,
|
|
167
|
+
if self.persistence_store.try_acquire_half_open(self.name, _probe_owner_id(), record.opened_at or 0):
|
|
140
168
|
return self._call_probe()
|
|
141
169
|
|
|
142
170
|
return self._open_response(record.to_circuit_info())
|
|
@@ -148,9 +176,14 @@ class CircuitBreakerHandler:
|
|
|
148
176
|
except Exception as exc:
|
|
149
177
|
if not self.config.counts_as_failure(exc):
|
|
150
178
|
raise
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
179
|
+
# Increment and reset atomically so exactly one thread observes the threshold
|
|
180
|
+
# crossing; racing threads would otherwise lose increments (tripping late) or
|
|
181
|
+
# each persist the same transition.
|
|
182
|
+
with _COUNTERS_LOCK:
|
|
183
|
+
failures = _LOCAL_FAILURES.get(self.name, 0) + 1
|
|
184
|
+
tripped = failures >= self.config.failure_threshold
|
|
185
|
+
_LOCAL_FAILURES[self.name] = 0 if tripped else failures
|
|
186
|
+
if tripped:
|
|
154
187
|
logger.debug("Circuit '%s' tripping CLOSED to OPEN after %d failures.", self.name, failures)
|
|
155
188
|
opened_at = self._now()
|
|
156
189
|
self._safe_persist(
|
|
@@ -159,11 +192,11 @@ class CircuitBreakerHandler:
|
|
|
159
192
|
failure_count=failures,
|
|
160
193
|
opened_at=opened_at,
|
|
161
194
|
)
|
|
162
|
-
_LOCAL_FAILURES[self.name] = 0
|
|
163
195
|
self._notify(CircuitState.CLOSED, CircuitState.OPEN, opened_at=opened_at)
|
|
164
196
|
raise
|
|
165
197
|
else:
|
|
166
|
-
|
|
198
|
+
with _COUNTERS_LOCK:
|
|
199
|
+
_LOCAL_FAILURES[self.name] = 0
|
|
167
200
|
return result
|
|
168
201
|
|
|
169
202
|
def _call_probe(self) -> Any:
|
|
@@ -176,17 +209,20 @@ class CircuitBreakerHandler:
|
|
|
176
209
|
logger.debug("Circuit '%s' probe failed; reopening.", self.name)
|
|
177
210
|
opened_at = self._now()
|
|
178
211
|
self._safe_persist(self.persistence_store.save_reopen, self.name, opened_at=opened_at)
|
|
179
|
-
|
|
212
|
+
with _COUNTERS_LOCK:
|
|
213
|
+
_LOCAL_SUCCESSES[self.name] = 0
|
|
180
214
|
self._notify(CircuitState.HALF_OPEN, CircuitState.OPEN, opened_at=opened_at)
|
|
181
215
|
raise
|
|
182
216
|
else:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
217
|
+
with _COUNTERS_LOCK:
|
|
218
|
+
successes = _LOCAL_SUCCESSES.get(self.name, 0) + 1
|
|
219
|
+
closed = successes >= self.config.success_threshold
|
|
220
|
+
_LOCAL_SUCCESSES[self.name] = 0 if closed else successes
|
|
221
|
+
if closed:
|
|
222
|
+
_LOCAL_FAILURES[self.name] = 0
|
|
223
|
+
if closed:
|
|
186
224
|
logger.debug("Circuit '%s' closing after %d probe successes.", self.name, successes)
|
|
187
225
|
self._safe_persist(self.persistence_store.save_closed, self.name)
|
|
188
|
-
_LOCAL_SUCCESSES[self.name] = 0
|
|
189
|
-
_LOCAL_FAILURES[self.name] = 0
|
|
190
226
|
self._notify(CircuitState.HALF_OPEN, CircuitState.CLOSED)
|
|
191
227
|
return result
|
|
192
228
|
|
|
@@ -4,8 +4,13 @@ Configuration for the Circuit Breaker utility.
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
7
9
|
from aws_lambda_powertools.utilities.circuit_breaker_alpha.exceptions import CircuitBreakerConfigError
|
|
8
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Iterable
|
|
13
|
+
|
|
9
14
|
|
|
10
15
|
class CircuitBreakerConfig:
|
|
11
16
|
"""
|
|
@@ -24,12 +29,14 @@ class CircuitBreakerConfig:
|
|
|
24
29
|
success_threshold : int
|
|
25
30
|
Number of *consecutive* probe successes required to close a half-open circuit.
|
|
26
31
|
Defaults to 3.
|
|
27
|
-
handled_exceptions :
|
|
32
|
+
handled_exceptions : type[Exception] | Iterable[type[Exception]] | None
|
|
28
33
|
Allowlist: only these exception types count as failures; anything else
|
|
29
|
-
propagates without affecting the circuit.
|
|
34
|
+
propagates without affecting the circuit. Accepts a single exception type or
|
|
35
|
+
an iterable of them (normalized to a tuple). Mutually exclusive with
|
|
30
36
|
``ignored_exceptions``. Defaults to ``None`` (treated as ``(Exception,)``).
|
|
31
|
-
ignored_exceptions :
|
|
32
|
-
Denylist: every exception counts as a failure *except* these.
|
|
37
|
+
ignored_exceptions : type[Exception] | Iterable[type[Exception]] | None
|
|
38
|
+
Denylist: every exception counts as a failure *except* these. Accepts a single
|
|
39
|
+
exception type or an iterable of them (normalized to a tuple). Mutually
|
|
33
40
|
exclusive with ``handled_exceptions``. Defaults to ``None``.
|
|
34
41
|
local_cache_max_age : int
|
|
35
42
|
Seconds a circuit's state is cached in the execution environment before a
|
|
@@ -38,8 +45,9 @@ class CircuitBreakerConfig:
|
|
|
38
45
|
Raises
|
|
39
46
|
------
|
|
40
47
|
CircuitBreakerConfigError
|
|
41
|
-
If both ``handled_exceptions`` and ``ignored_exceptions`` are provided,
|
|
42
|
-
numeric tunable is not a positive integer
|
|
48
|
+
If both ``handled_exceptions`` and ``ignored_exceptions`` are provided, a
|
|
49
|
+
numeric tunable is not a positive integer, or an exception allowlist/denylist
|
|
50
|
+
is empty or contains a value that is not an exception type.
|
|
43
51
|
|
|
44
52
|
Example
|
|
45
53
|
-------
|
|
@@ -57,10 +65,16 @@ class CircuitBreakerConfig:
|
|
|
57
65
|
failure_threshold: int = 5,
|
|
58
66
|
recovery_timeout: int = 30,
|
|
59
67
|
success_threshold: int = 3,
|
|
60
|
-
handled_exceptions:
|
|
61
|
-
ignored_exceptions:
|
|
68
|
+
handled_exceptions: type[Exception] | Iterable[type[Exception]] | None = None,
|
|
69
|
+
ignored_exceptions: type[Exception] | Iterable[type[Exception]] | None = None,
|
|
62
70
|
local_cache_max_age: int = 5,
|
|
63
71
|
):
|
|
72
|
+
# Normalize first: a single exception type or any iterable becomes a tuple, and a
|
|
73
|
+
# bad value fails here (at construction) rather than as a cryptic isinstance
|
|
74
|
+
# TypeError later, the first time the circuit evaluates a failure.
|
|
75
|
+
handled_exceptions = self._normalize_exceptions(handled_exceptions, "handled_exceptions")
|
|
76
|
+
ignored_exceptions = self._normalize_exceptions(ignored_exceptions, "ignored_exceptions")
|
|
77
|
+
|
|
64
78
|
self._validate(
|
|
65
79
|
failure_threshold=failure_threshold,
|
|
66
80
|
recovery_timeout=recovery_timeout,
|
|
@@ -105,6 +119,48 @@ class CircuitBreakerConfig:
|
|
|
105
119
|
f"local_cache_max_age must be a non-negative integer, got {local_cache_max_age!r}.",
|
|
106
120
|
)
|
|
107
121
|
|
|
122
|
+
@classmethod
|
|
123
|
+
def _normalize_exceptions(
|
|
124
|
+
cls,
|
|
125
|
+
value: type[Exception] | Iterable[type[Exception]] | None,
|
|
126
|
+
field: str,
|
|
127
|
+
) -> tuple[type[Exception], ...] | None:
|
|
128
|
+
"""Coerce a single exception type or an iterable of them into a validated, non-empty tuple.
|
|
129
|
+
|
|
130
|
+
Runs at construction so a bad value fails immediately with a clear error, rather
|
|
131
|
+
than as a cryptic ``isinstance`` ``TypeError`` from ``counts_as_failure`` the
|
|
132
|
+
first time the circuit evaluates a failure (i.e. only once the dependency is
|
|
133
|
+
already unhealthy).
|
|
134
|
+
"""
|
|
135
|
+
if value is None:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
invalid = f"{field} must be an exception type or an iterable of exception types, got {value!r}."
|
|
139
|
+
# A str is iterable; reject it rather than iterate it as a sequence of characters.
|
|
140
|
+
if isinstance(value, str):
|
|
141
|
+
raise CircuitBreakerConfigError(invalid)
|
|
142
|
+
|
|
143
|
+
if isinstance(value, type):
|
|
144
|
+
# ty (unlike mypy) does not narrow the union here, so it needs the ignore.
|
|
145
|
+
exceptions: tuple[type[Exception], ...] = (value,) # ty: ignore[invalid-assignment]
|
|
146
|
+
else:
|
|
147
|
+
try:
|
|
148
|
+
exceptions = tuple(value)
|
|
149
|
+
except TypeError:
|
|
150
|
+
raise CircuitBreakerConfigError(invalid) from None
|
|
151
|
+
|
|
152
|
+
cls._validate_exception_types(exceptions, field)
|
|
153
|
+
return exceptions
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _validate_exception_types(exceptions: tuple[type[Exception], ...], field: str) -> None:
|
|
157
|
+
"""Require a non-empty tuple whose every element is an exception type."""
|
|
158
|
+
if not exceptions:
|
|
159
|
+
raise CircuitBreakerConfigError(f"{field} must contain at least one exception type.")
|
|
160
|
+
for exception in exceptions:
|
|
161
|
+
if not (isinstance(exception, type) and issubclass(exception, Exception)):
|
|
162
|
+
raise CircuitBreakerConfigError(f"{field} must contain only exception types, got {exception!r}.")
|
|
163
|
+
|
|
108
164
|
def counts_as_failure(self, exception: Exception) -> bool:
|
|
109
165
|
"""
|
|
110
166
|
Decide whether an exception raised by the protected call counts as a circuit failure.
|
|
@@ -10,8 +10,9 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import datetime
|
|
12
12
|
import logging
|
|
13
|
+
import threading
|
|
13
14
|
from abc import ABC, abstractmethod
|
|
14
|
-
from typing import TYPE_CHECKING
|
|
15
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
15
16
|
|
|
16
17
|
from aws_lambda_powertools.shared.cache_dict import LRUDict
|
|
17
18
|
from aws_lambda_powertools.utilities.circuit_breaker_alpha.persistence.record import CircuitStateRecord
|
|
@@ -32,6 +33,17 @@ LOCAL_CACHE_MAX_ITEMS = 1024
|
|
|
32
33
|
PERSISTED_STATE_TTL_BUFFER = 3600
|
|
33
34
|
|
|
34
35
|
|
|
36
|
+
class _CircuitSettings(NamedTuple):
|
|
37
|
+
"""Per-circuit tunables the layer captures from :meth:`configure`."""
|
|
38
|
+
|
|
39
|
+
local_cache_max_age: int
|
|
40
|
+
recovery_timeout: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Fallback for direct layer use before configure() has run; mirrors CircuitBreakerConfig defaults.
|
|
44
|
+
_DEFAULT_SETTINGS = _CircuitSettings(local_cache_max_age=5, recovery_timeout=30)
|
|
45
|
+
|
|
46
|
+
|
|
35
47
|
class CircuitBreakerExistingLockError(Exception):
|
|
36
48
|
"""Internal signal that a conditional half-open probe write lost the race."""
|
|
37
49
|
|
|
@@ -50,19 +62,26 @@ class CircuitBreakerPersistenceLayer(ABC):
|
|
|
50
62
|
|
|
51
63
|
def __init__(self) -> None:
|
|
52
64
|
"""Initialize defaults; real configuration happens in :meth:`configure`."""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
# Per-circuit tunables, keyed by circuit name. One persistence instance is shared
|
|
66
|
+
# by every circuit (and thread) using the same store, so these must never live in
|
|
67
|
+
# plain instance attributes: circuits with different configs would stamp each
|
|
68
|
+
# other's TTL and probe lease. A plain dict, not an LRUDict: evicting a live
|
|
69
|
+
# circuit's settings would silently swap in the defaults (wrong lease and TTL),
|
|
70
|
+
# whereas evicting a cache entry below only costs a store re-read.
|
|
71
|
+
self._settings: dict[str, _CircuitSettings] = {}
|
|
56
72
|
# Maps circuit name -> the unix timestamp the locally cached record goes stale.
|
|
57
73
|
# Kept separate from the record's durable ``expiry_timestamp`` (the store TTL) so
|
|
58
74
|
# the short in-memory freshness window is never mistaken for the long store TTL.
|
|
59
75
|
self._cache: LRUDict = LRUDict(max_items=LOCAL_CACHE_MAX_ITEMS)
|
|
76
|
+
# One lock for both maps: LRUDict reorders entries even on reads, so unguarded
|
|
77
|
+
# concurrent access can corrupt it or raise.
|
|
78
|
+
self._lock = threading.Lock()
|
|
60
79
|
|
|
61
80
|
def configure(self, config: CircuitBreakerConfig, circuit_name: str) -> None:
|
|
62
81
|
"""
|
|
63
|
-
Bind
|
|
82
|
+
Bind a circuit's configuration to the layer.
|
|
64
83
|
|
|
65
|
-
Called once per invocation by the handler; the
|
|
84
|
+
Called once per invocation by the handler; the assignment is cheap and the
|
|
66
85
|
same persistence instance is reused across invocations within an environment.
|
|
67
86
|
|
|
68
87
|
Parameters
|
|
@@ -70,43 +89,59 @@ class CircuitBreakerPersistenceLayer(ABC):
|
|
|
70
89
|
config : CircuitBreakerConfig
|
|
71
90
|
Configuration providing the local cache TTL and recovery timeout.
|
|
72
91
|
circuit_name : str
|
|
73
|
-
The circuit
|
|
92
|
+
The circuit these settings apply to.
|
|
74
93
|
"""
|
|
75
|
-
self.
|
|
76
|
-
|
|
77
|
-
|
|
94
|
+
with self._lock:
|
|
95
|
+
self._settings[circuit_name] = _CircuitSettings(
|
|
96
|
+
local_cache_max_age=config.local_cache_max_age,
|
|
97
|
+
recovery_timeout=config.recovery_timeout,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def _settings_for(self, name: str) -> _CircuitSettings:
|
|
101
|
+
"""Return a circuit's configured settings, or the defaults if never configured."""
|
|
102
|
+
with self._lock:
|
|
103
|
+
return self._settings.get(name, _DEFAULT_SETTINGS)
|
|
78
104
|
|
|
79
105
|
# ------------------------------------------------------------------ cache
|
|
80
106
|
|
|
81
107
|
def _cache_key(self, name: str) -> str:
|
|
82
108
|
return name
|
|
83
109
|
|
|
84
|
-
def _durable_ttl(self) -> int:
|
|
110
|
+
def _durable_ttl(self, name: str) -> int:
|
|
85
111
|
"""
|
|
86
112
|
Compute the store TTL stamped on a persisted record.
|
|
87
113
|
|
|
88
114
|
Sized to outlive a full recovery window so a live circuit is never reaped
|
|
89
115
|
mid-cycle, while an abandoned circuit (no further writes) self-cleans soon after.
|
|
90
116
|
"""
|
|
91
|
-
|
|
117
|
+
now = int(datetime.datetime.now().timestamp())
|
|
118
|
+
return now + self._settings_for(name).recovery_timeout + PERSISTED_STATE_TTL_BUFFER
|
|
92
119
|
|
|
93
120
|
def _save_to_cache(self, record: CircuitStateRecord) -> None:
|
|
94
121
|
"""Cache a record locally with a short in-memory freshness window."""
|
|
95
|
-
local_expiry = int(datetime.datetime.now().timestamp()) + self.local_cache_max_age
|
|
96
|
-
self.
|
|
122
|
+
local_expiry = int(datetime.datetime.now().timestamp()) + self._settings_for(record.name).local_cache_max_age
|
|
123
|
+
with self._lock:
|
|
124
|
+
self._cache[self._cache_key(record.name)] = (local_expiry, record)
|
|
97
125
|
|
|
98
126
|
def _retrieve_from_cache(self, name: str) -> CircuitStateRecord | None:
|
|
99
127
|
"""Return a cached record if present and still within its local freshness window."""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
128
|
+
with self._lock:
|
|
129
|
+
cached = self._cache.get(self._cache_key(name))
|
|
130
|
+
if cached is None:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
local_expiry, record = cached
|
|
134
|
+
if int(datetime.datetime.now().timestamp()) >= local_expiry:
|
|
135
|
+
# Guarded del, not pop: on Python 3.10 OrderedDict.pop re-enters the
|
|
136
|
+
# subclass __getitem__ after detaching the node, so LRUDict.pop raises
|
|
137
|
+
# KeyError for a *present* key and corrupts the dict (fixed in 3.11).
|
|
138
|
+
try:
|
|
139
|
+
del self._cache[self._cache_key(name)]
|
|
140
|
+
except KeyError:
|
|
141
|
+
pass
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
return record
|
|
110
145
|
|
|
111
146
|
# ------------------------------------------------------------- public API
|
|
112
147
|
|
|
@@ -175,19 +210,19 @@ class CircuitBreakerPersistenceLayer(ABC):
|
|
|
175
210
|
state=CircuitState.OPEN,
|
|
176
211
|
failure_count=failure_count,
|
|
177
212
|
opened_at=opened_at,
|
|
178
|
-
expiry_timestamp=self._durable_ttl(),
|
|
213
|
+
expiry_timestamp=self._durable_ttl(name),
|
|
179
214
|
)
|
|
180
215
|
self._put_record(record)
|
|
181
216
|
self._save_to_cache(record)
|
|
182
217
|
|
|
183
218
|
def try_acquire_half_open(self, name: str, owner: str, opened_at: int) -> bool:
|
|
184
219
|
"""
|
|
185
|
-
Atomically elect a single
|
|
220
|
+
Atomically elect a single worker to run the half-open probe.
|
|
186
221
|
|
|
187
222
|
The conditional write succeeds only when the circuit is OPEN with no existing
|
|
188
223
|
lock owner AND the ``opened_at`` matches what the caller observed (guards against
|
|
189
224
|
stale eventually-consistent reads). A lease expiry is stamped so that if the
|
|
190
|
-
winning
|
|
225
|
+
winning worker is recycled before completing the probe, others can take over
|
|
191
226
|
once the lease lapses.
|
|
192
227
|
|
|
193
228
|
Parameters
|
|
@@ -195,25 +230,26 @@ class CircuitBreakerPersistenceLayer(ABC):
|
|
|
195
230
|
name : str
|
|
196
231
|
Circuit name.
|
|
197
232
|
owner : str
|
|
198
|
-
Identifier of the
|
|
233
|
+
Identifier of the worker (one thread in one execution environment)
|
|
234
|
+
attempting the probe.
|
|
199
235
|
opened_at : int
|
|
200
236
|
The ``opened_at`` the caller observed, kept stable across the transition.
|
|
201
237
|
|
|
202
238
|
Returns
|
|
203
239
|
-------
|
|
204
240
|
bool
|
|
205
|
-
``True`` if this
|
|
206
|
-
|
|
241
|
+
``True`` if this worker won the probe lock, ``False`` if another
|
|
242
|
+
worker already holds it.
|
|
207
243
|
"""
|
|
208
244
|
# Lease = recovery_timeout gives the probe a full cycle to complete.
|
|
209
|
-
probe_lease_expiry = int(datetime.datetime.now().timestamp()) + self.recovery_timeout
|
|
245
|
+
probe_lease_expiry = int(datetime.datetime.now().timestamp()) + self._settings_for(name).recovery_timeout
|
|
210
246
|
record = CircuitStateRecord(
|
|
211
247
|
name=name,
|
|
212
248
|
state=CircuitState.HALF_OPEN,
|
|
213
249
|
opened_at=opened_at,
|
|
214
250
|
half_open_owner=owner,
|
|
215
251
|
probe_lease_expiry=probe_lease_expiry,
|
|
216
|
-
expiry_timestamp=self._durable_ttl(),
|
|
252
|
+
expiry_timestamp=self._durable_ttl(name),
|
|
217
253
|
)
|
|
218
254
|
try:
|
|
219
255
|
self._put_record(record, condition="half_open", expected_opened_at=opened_at)
|
|
@@ -228,7 +264,7 @@ class CircuitBreakerPersistenceLayer(ABC):
|
|
|
228
264
|
name=name,
|
|
229
265
|
state=CircuitState.CLOSED,
|
|
230
266
|
failure_count=0,
|
|
231
|
-
expiry_timestamp=self._durable_ttl(),
|
|
267
|
+
expiry_timestamp=self._durable_ttl(name),
|
|
232
268
|
)
|
|
233
269
|
self._update_record(record)
|
|
234
270
|
self._save_to_cache(record)
|
|
@@ -245,7 +281,7 @@ class CircuitBreakerPersistenceLayer(ABC):
|
|
|
245
281
|
name=name,
|
|
246
282
|
state=CircuitState.OPEN,
|
|
247
283
|
opened_at=opened_at,
|
|
248
|
-
expiry_timestamp=self._durable_ttl(),
|
|
284
|
+
expiry_timestamp=self._durable_ttl(name),
|
|
249
285
|
)
|
|
250
286
|
self._update_record(record)
|
|
251
287
|
self._save_to_cache(record)
|
|
@@ -6,19 +6,23 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import datetime
|
|
8
8
|
import logging
|
|
9
|
+
import os
|
|
10
|
+
import warnings
|
|
9
11
|
from typing import TYPE_CHECKING
|
|
10
12
|
|
|
11
13
|
import boto3
|
|
12
14
|
from boto3.dynamodb.types import TypeDeserializer
|
|
13
15
|
from botocore.exceptions import ClientError
|
|
14
16
|
|
|
15
|
-
from aws_lambda_powertools.shared import user_agent
|
|
17
|
+
from aws_lambda_powertools.shared import constants, user_agent
|
|
18
|
+
from aws_lambda_powertools.utilities.circuit_breaker_alpha.exceptions import CircuitBreakerConfigError
|
|
16
19
|
from aws_lambda_powertools.utilities.circuit_breaker_alpha.persistence.base import (
|
|
17
20
|
CircuitBreakerExistingLockError,
|
|
18
21
|
CircuitBreakerPersistenceLayer,
|
|
19
22
|
)
|
|
20
23
|
from aws_lambda_powertools.utilities.circuit_breaker_alpha.persistence.record import CircuitStateRecord
|
|
21
24
|
from aws_lambda_powertools.utilities.circuit_breaker_alpha.states import CircuitState
|
|
25
|
+
from aws_lambda_powertools.warnings import PowertoolsUserWarning
|
|
22
26
|
|
|
23
27
|
if TYPE_CHECKING:
|
|
24
28
|
from botocore.config import Config
|
|
@@ -41,6 +45,13 @@ class CircuitBreakerDynamoDBPersistence(CircuitBreakerPersistenceLayer):
|
|
|
41
45
|
Name of the DynamoDB table that stores circuit state.
|
|
42
46
|
key_attr : str
|
|
43
47
|
Partition key attribute holding the circuit name. Defaults to ``"id"``.
|
|
48
|
+
static_pk_value : str, optional
|
|
49
|
+
Partition key value used when ``sort_key_attr`` is set, so the circuit name
|
|
50
|
+
moves to the sort key. Defaults to ``"circuit_breaker#<function-name>"``.
|
|
51
|
+
sort_key_attr : str, optional
|
|
52
|
+
Sort key attribute holding the circuit name. When set, the table is treated as
|
|
53
|
+
composite: ``static_pk_value`` is written to the partition key and the circuit
|
|
54
|
+
name to the sort key. Omit it for the default partition-key-only behavior.
|
|
44
55
|
state_attr : str
|
|
45
56
|
Attribute holding the circuit state. Defaults to ``"state"``.
|
|
46
57
|
failure_count_attr : str
|
|
@@ -75,6 +86,8 @@ class CircuitBreakerDynamoDBPersistence(CircuitBreakerPersistenceLayer):
|
|
|
75
86
|
self,
|
|
76
87
|
table_name: str,
|
|
77
88
|
key_attr: str = "id",
|
|
89
|
+
static_pk_value: str | None = None,
|
|
90
|
+
sort_key_attr: str | None = None,
|
|
78
91
|
state_attr: str = "state",
|
|
79
92
|
failure_count_attr: str = "failure_count",
|
|
80
93
|
opened_at_attr: str = "opened_at",
|
|
@@ -92,8 +105,25 @@ class CircuitBreakerDynamoDBPersistence(CircuitBreakerPersistenceLayer):
|
|
|
92
105
|
|
|
93
106
|
user_agent.register_feature_to_client(client=self.client, feature="circuit_breaker")
|
|
94
107
|
|
|
108
|
+
if sort_key_attr == key_attr:
|
|
109
|
+
raise CircuitBreakerConfigError(
|
|
110
|
+
f"key_attr [{key_attr}] and sort_key_attr [{sort_key_attr}] cannot be the same!",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if static_pk_value is not None and sort_key_attr is None:
|
|
114
|
+
warnings.warn(
|
|
115
|
+
"static_pk_value is ignored unless sort_key_attr is also set.",
|
|
116
|
+
category=PowertoolsUserWarning,
|
|
117
|
+
stacklevel=2,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if static_pk_value is None:
|
|
121
|
+
static_pk_value = f"circuit_breaker#{os.getenv(constants.LAMBDA_FUNCTION_NAME_ENV, '')}"
|
|
122
|
+
|
|
95
123
|
self.table_name = table_name
|
|
96
124
|
self.key_attr = key_attr
|
|
125
|
+
self.static_pk_value = static_pk_value
|
|
126
|
+
self.sort_key_attr = sort_key_attr
|
|
97
127
|
self.state_attr = state_attr
|
|
98
128
|
self.failure_count_attr = failure_count_attr
|
|
99
129
|
self.opened_at_attr = opened_at_attr
|
|
@@ -111,7 +141,7 @@ class CircuitBreakerDynamoDBPersistence(CircuitBreakerPersistenceLayer):
|
|
|
111
141
|
opened_at = data.get(self.opened_at_attr)
|
|
112
142
|
probe_lease_expiry = data.get(self.probe_lease_expiry_attr)
|
|
113
143
|
return CircuitStateRecord(
|
|
114
|
-
name=data[self.key_attr],
|
|
144
|
+
name=data[self.sort_key_attr] if self.sort_key_attr else data[self.key_attr],
|
|
115
145
|
state=CircuitState(data[self.state_attr]),
|
|
116
146
|
failure_count=int(data.get(self.failure_count_attr, 0)),
|
|
117
147
|
opened_at=int(opened_at) if opened_at is not None else None,
|
|
@@ -130,10 +160,21 @@ class CircuitBreakerDynamoDBPersistence(CircuitBreakerPersistenceLayer):
|
|
|
130
160
|
"""Wrap a value as a DynamoDB string attribute."""
|
|
131
161
|
return {"S": value}
|
|
132
162
|
|
|
163
|
+
def _get_key(self, name: str) -> dict:
|
|
164
|
+
"""Build the simple or composite primary key for a circuit.
|
|
165
|
+
|
|
166
|
+
When ``sort_key_attr`` is set the key is composite: ``static_pk_value`` in the
|
|
167
|
+
partition and the circuit name in the sort key. Otherwise the name is the
|
|
168
|
+
partition key.
|
|
169
|
+
"""
|
|
170
|
+
if self.sort_key_attr:
|
|
171
|
+
return {self.key_attr: self._s(self.static_pk_value), self.sort_key_attr: self._s(name)}
|
|
172
|
+
return {self.key_attr: self._s(name)}
|
|
173
|
+
|
|
133
174
|
def _record_to_item(self, record: CircuitStateRecord) -> dict:
|
|
134
175
|
"""Translate a :class:`CircuitStateRecord` into a DynamoDB item."""
|
|
135
176
|
item: dict = {
|
|
136
|
-
self.
|
|
177
|
+
**self._get_key(record.name),
|
|
137
178
|
self.state_attr: self._s(str(record.state)),
|
|
138
179
|
self.failure_count_attr: self._n(record.failure_count),
|
|
139
180
|
}
|
|
@@ -152,7 +193,7 @@ class CircuitBreakerDynamoDBPersistence(CircuitBreakerPersistenceLayer):
|
|
|
152
193
|
# and halves the read cost on the hot path.
|
|
153
194
|
response = self.client.get_item(
|
|
154
195
|
TableName=self.table_name,
|
|
155
|
-
Key=
|
|
196
|
+
Key=self._get_key(name),
|
|
156
197
|
ConsistentRead=False,
|
|
157
198
|
)
|
|
158
199
|
item = response.get("Item")
|
|
@@ -242,7 +283,7 @@ class CircuitBreakerDynamoDBPersistence(CircuitBreakerPersistenceLayer):
|
|
|
242
283
|
|
|
243
284
|
return {
|
|
244
285
|
"TableName": self.table_name,
|
|
245
|
-
"Key":
|
|
286
|
+
"Key": self._get_key(record.name),
|
|
246
287
|
"UpdateExpression": update_expression,
|
|
247
288
|
"ExpressionAttributeNames": expression_attr_names,
|
|
248
289
|
"ExpressionAttributeValues": expression_attr_values,
|