diff --git a/CHANGELOG.md b/CHANGELOG.md
index f27dd0f88b9..853f53fa523 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,21 +4,55 @@
# Unreleased
+## Bug Fixes
+
+* **event_handler:** fix format for OpenAPI path templating ([#3399](https://github.com/aws-powertools/powertools-lambda-python/issues/3399))
+* **event_handler:** lazy load Pydantic to improve cold start ([#3397](https://github.com/aws-powertools/powertools-lambda-python/issues/3397))
+* **event_handler:** allow fine grained Response with data validation ([#3394](https://github.com/aws-powertools/powertools-lambda-python/issues/3394))
+* **event_handler:** apply serialization as the last operation for middlewares ([#3392](https://github.com/aws-powertools/powertools-lambda-python/issues/3392))
+
+## Features
+
+* **event_handler:** allow customers to catch request validation errors ([#3396](https://github.com/aws-powertools/powertools-lambda-python/issues/3396))
+
+## Maintenance
+
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3389](https://github.com/aws-powertools/powertools-lambda-python/issues/3389))
+* **deps-dev:** bump pytest-xdist from 3.4.0 to 3.5.0 ([#3387](https://github.com/aws-powertools/powertools-lambda-python/issues/3387))
+* **deps-dev:** bump sentry-sdk from 1.35.0 to 1.36.0 ([#3388](https://github.com/aws-powertools/powertools-lambda-python/issues/3388))
+
+
+
+## [v2.27.1] - 2023-11-21
+## Bug Fixes
+
+* **logger:** allow custom JMESPath functions to extract correlation ID ([#3382](https://github.com/aws-powertools/powertools-lambda-python/issues/3382))
+
## Documentation
+* **event_handlers:** note that CORS and */* binary mime type don't work in API Gateway ([#3383](https://github.com/aws-powertools/powertools-lambda-python/issues/3383))
* **logger:** improve ALC messaging in the PT context ([#3359](https://github.com/aws-powertools/powertools-lambda-python/issues/3359))
* **logger:** Fix ALC link ([#3352](https://github.com/aws-powertools/powertools-lambda-python/issues/3352))
+## Features
+
+* **logger:** implement addFilter/removeFilter to address static typing errors ([#3380](https://github.com/aws-powertools/powertools-lambda-python/issues/3380))
+
## Maintenance
+* version bump
* **ci:** lint and type checking removal in Pydantic v2 quality check ([#3360](https://github.com/aws-powertools/powertools-lambda-python/issues/3360))
-* **deps:** bump squidfunk/mkdocs-material from `f486dc9` to `2c57e4d` in /docs ([#3366](https://github.com/aws-powertools/powertools-lambda-python/issues/3366))
+* **deps:** bump actions/github-script from 7.0.0 to 7.0.1 ([#3377](https://github.com/aws-powertools/powertools-lambda-python/issues/3377))
+* **deps:** bump squidfunk/mkdocs-material from `2c57e4d` to `fc42bac` in /docs ([#3375](https://github.com/aws-powertools/powertools-lambda-python/issues/3375))
* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3353](https://github.com/aws-powertools/powertools-lambda-python/issues/3353))
-* **deps-dev:** bump aws-cdk from 2.109.0 to 2.110.0 ([#3361](https://github.com/aws-powertools/powertools-lambda-python/issues/3361))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3374](https://github.com/aws-powertools/powertools-lambda-python/issues/3374))
+* **deps:** bump squidfunk/mkdocs-material from `f486dc9` to `2c57e4d` in /docs ([#3366](https://github.com/aws-powertools/powertools-lambda-python/issues/3366))
+* **deps-dev:** bump cfn-lint from 0.83.2 to 0.83.3 ([#3363](https://github.com/aws-powertools/powertools-lambda-python/issues/3363))
* **deps-dev:** bump the boto-typing group with 11 updates ([#3362](https://github.com/aws-powertools/powertools-lambda-python/issues/3362))
* **deps-dev:** bump aws-cdk-lib from 2.108.1 to 2.110.0 ([#3365](https://github.com/aws-powertools/powertools-lambda-python/issues/3365))
* **deps-dev:** bump aws-cdk from 2.108.1 to 2.109.0 ([#3354](https://github.com/aws-powertools/powertools-lambda-python/issues/3354))
-* **deps-dev:** bump cfn-lint from 0.83.2 to 0.83.3 ([#3363](https://github.com/aws-powertools/powertools-lambda-python/issues/3363))
+* **deps-dev:** bump aws-cdk from 2.109.0 to 2.110.0 ([#3361](https://github.com/aws-powertools/powertools-lambda-python/issues/3361))
+* **deps-dev:** bump the boto-typing group with 2 updates ([#3376](https://github.com/aws-powertools/powertools-lambda-python/issues/3376))
* **deps-dev:** bump ruff from 0.1.5 to 0.1.6 ([#3364](https://github.com/aws-powertools/powertools-lambda-python/issues/3364))
@@ -4023,7 +4057,8 @@
* Merge pull request [#5](https://github.com/aws-powertools/powertools-lambda-python/issues/5) from jfuss/feat/python38
-[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.27.0...HEAD
+[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.27.1...HEAD
+[v2.27.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.27.0...v2.27.1
[v2.27.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.26.1...v2.27.0
[v2.26.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.26.0...v2.26.1
[v2.26.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.25.1...v2.26.0
diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py
index 535d79e5874..2231bc1b400 100644
--- a/aws_lambda_powertools/event_handler/api_gateway.py
+++ b/aws_lambda_powertools/event_handler/api_gateway.py
@@ -32,6 +32,7 @@
from aws_lambda_powertools.event_handler import content_types
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION
+from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
from aws_lambda_powertools.event_handler.openapi.swagger_ui.html import generate_swagger_html
from aws_lambda_powertools.event_handler.openapi.types import (
COMPONENT_REF_PREFIX,
@@ -66,6 +67,7 @@
_ROUTE_REGEX = "^{}$"
ResponseEventT = TypeVar("ResponseEventT", bound=BaseProxyEvent)
+ResponseT = TypeVar("ResponseT")
if TYPE_CHECKING:
from aws_lambda_powertools.event_handler.openapi.compat import (
@@ -207,14 +209,14 @@ def to_dict(self, origin: Optional[str]) -> Dict[str, str]:
return headers
-class Response:
+class Response(Generic[ResponseT]):
"""Response data class that provides greater control over what is returned from the proxy event"""
def __init__(
self,
status_code: int,
content_type: Optional[str] = None,
- body: Any = None,
+ body: Optional[ResponseT] = None,
headers: Optional[Dict[str, Union[str, List[str]]]] = None,
cookies: Optional[List[Cookie]] = None,
compress: Optional[bool] = None,
@@ -314,6 +316,11 @@ def __init__(
"""
self.method = method.upper()
self.path = "/" if path.strip() == "" else path
+
+ # OpenAPI spec only understands paths with { }. So we'll have to convert Powertools' < >.
+ # https://swagger.io/specification/#path-templating
+ self.openapi_path = re.sub(r"<(.*?)>", lambda m: f"{{{''.join(m.group(1))}}}", self.path)
+
self.rule = rule
self.func = func
self._middleware_stack = func
@@ -433,7 +440,7 @@ def dependant(self) -> "Dependant":
if self._dependant is None:
from aws_lambda_powertools.event_handler.openapi.dependant import get_dependant
- self._dependant = get_dependant(path=self.path, call=self.func)
+ self._dependant = get_dependant(path=self.openapi_path, call=self.func)
return self._dependant
@@ -540,7 +547,7 @@ def _openapi_operation_summary(self) -> str:
Returns the OpenAPI operation summary. If the user has not provided a summary, we
generate one based on the route path and method.
"""
- return self.summary or f"{self.method.upper()} {self.path}"
+ return self.summary or f"{self.method.upper()} {self.openapi_path}"
def _openapi_operation_metadata(self, operation_ids: Set[str]) -> Dict[str, Any]:
"""
@@ -690,7 +697,7 @@ def _openapi_operation_return(
return {"schema": return_schema}
def _generate_operation_id(self) -> str:
- operation_id = self.func.__name__ + self.path
+ operation_id = self.func.__name__ + self.openapi_path
operation_id = re.sub(r"\W", "_", operation_id)
operation_id = operation_id + "_" + self.method.lower()
return operation_id
@@ -699,8 +706,14 @@ def _generate_operation_id(self) -> str:
class ResponseBuilder(Generic[ResponseEventT]):
"""Internally used Response builder"""
- def __init__(self, response: Response, route: Optional[Route] = None):
+ def __init__(
+ self,
+ response: Response,
+ serializer: Callable[[Any], str] = json.dumps,
+ route: Optional[Route] = None,
+ ):
self.response = response
+ self.serializer = serializer
self.route = route
def _add_cors(self, event: ResponseEventT, cors: CORSConfig):
@@ -783,6 +796,11 @@ def build(self, event: ResponseEventT, cors: Optional[CORSConfig] = None) -> Dic
self.response.base64_encoded = True
self.response.body = base64.b64encode(self.response.body).decode()
+ # We only apply the serializer when the content type is JSON and the
+ # body is not a str, to avoid double encoding
+ elif self.response.is_json() and not isinstance(self.response.body, str):
+ self.response.body = self.serializer(self.response.body)
+
return {
"statusCode": self.response.status_code,
"body": self.response.body,
@@ -1332,14 +1350,6 @@ def __init__(
self.use([OpenAPIValidationMiddleware()])
- # When using validation, we need to skip the serializer, as the middleware is doing it automatically.
- # However, if the user is using a custom serializer, we need to abort.
- if serializer:
- raise ValueError("Cannot use a custom serializer when using validation")
-
- # Install a dummy serializer
- self._serializer = lambda args: args # type: ignore
-
def get_openapi_schema(
self,
*,
@@ -1447,7 +1457,7 @@ def get_openapi_schema(
if result:
path, path_definitions = result
if path:
- paths.setdefault(route.path, {}).update(path)
+ paths.setdefault(route.openapi_path, {}).update(path)
if path_definitions:
definitions.update(path_definitions)
@@ -1717,7 +1727,7 @@ def resolve(self, event, context) -> Dict[str, Any]:
event = event.raw_event
if self._debug:
- print(self._json_dump(event))
+ print(self._serializer(event))
# Populate router(s) dependencies without keeping a reference to each registered router
BaseRouter.current_event = self._to_proxy_event(event)
@@ -1881,19 +1891,23 @@ def _not_found(self, method: str) -> ResponseBuilder:
if method == "OPTIONS":
logger.debug("Pre-flight request detected. Returning CORS with null response")
headers["Access-Control-Allow-Methods"] = ",".join(sorted(self._cors_methods))
- return ResponseBuilder(Response(status_code=204, content_type=None, headers=headers, body=""))
+ return ResponseBuilder(
+ response=Response(status_code=204, content_type=None, headers=headers, body=""),
+ serializer=self._serializer,
+ )
handler = self._lookup_exception_handler(NotFoundError)
if handler:
- return self._response_builder_class(handler(NotFoundError()))
+ return self._response_builder_class(response=handler(NotFoundError()), serializer=self._serializer)
return self._response_builder_class(
- Response(
+ response=Response(
status_code=HTTPStatus.NOT_FOUND.value,
content_type=content_types.APPLICATION_JSON,
headers=headers,
- body=self._json_dump({"statusCode": HTTPStatus.NOT_FOUND.value, "message": "Not found"}),
+ body={"statusCode": HTTPStatus.NOT_FOUND.value, "message": "Not found"},
),
+ serializer=self._serializer,
)
def _call_route(self, route: Route, route_arguments: Dict[str, str]) -> ResponseBuilder:
@@ -1903,10 +1917,11 @@ def _call_route(self, route: Route, route_arguments: Dict[str, str]) -> Response
self._reset_processed_stack()
return self._response_builder_class(
- self._to_response(
+ response=self._to_response(
route(router_middlewares=self._router_middlewares, app=self, route_arguments=route_arguments),
),
- route,
+ serializer=self._serializer,
+ route=route,
)
except Exception as exc:
# If exception is handled then return the response builder to reduce noise
@@ -1920,12 +1935,13 @@ def _call_route(self, route: Route, route_arguments: Dict[str, str]) -> Response
# we'll let the original exception propagate, so
# they get more information about what went wrong.
return self._response_builder_class(
- Response(
+ response=Response(
status_code=500,
content_type=content_types.TEXT_PLAIN,
body="".join(traceback.format_exc()),
),
- route,
+ serializer=self._serializer,
+ route=route,
)
raise
@@ -1958,18 +1974,33 @@ def _call_exception_handler(self, exp: Exception, route: Route) -> Optional[Resp
handler = self._lookup_exception_handler(type(exp))
if handler:
try:
- return self._response_builder_class(handler(exp), route)
+ return self._response_builder_class(response=handler(exp), serializer=self._serializer, route=route)
except ServiceError as service_error:
exp = service_error
+ if isinstance(exp, RequestValidationError):
+ # For security reasons, we hide msg details (don't leak Python, Pydantic or file names)
+ errors = [{"loc": e["loc"], "type": e["type"]} for e in exp.errors()]
+
+ return self._response_builder_class(
+ response=Response(
+ status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+ content_type=content_types.APPLICATION_JSON,
+ body={"statusCode": HTTPStatus.UNPROCESSABLE_ENTITY, "detail": errors},
+ ),
+ serializer=self._serializer,
+ route=route,
+ )
+
if isinstance(exp, ServiceError):
return self._response_builder_class(
- Response(
+ response=Response(
status_code=exp.status_code,
content_type=content_types.APPLICATION_JSON,
- body=self._json_dump({"statusCode": exp.status_code, "message": exp.msg}),
+ body={"statusCode": exp.status_code, "message": exp.msg},
),
- route,
+ serializer=self._serializer,
+ route=route,
)
return None
@@ -1995,12 +2026,9 @@ def _to_response(self, result: Union[Dict, Tuple, Response]) -> Response:
return Response(
status_code=status_code,
content_type=content_types.APPLICATION_JSON,
- body=self._json_dump(result),
+ body=result,
)
- def _json_dump(self, obj: Any) -> str:
- return self._serializer(obj)
-
def include_router(self, router: "Router", prefix: Optional[str] = None) -> None:
"""Adds all routes and context defined in a router
diff --git a/aws_lambda_powertools/event_handler/bedrock_agent.py b/aws_lambda_powertools/event_handler/bedrock_agent.py
index f3a21958d60..f292a11519a 100644
--- a/aws_lambda_powertools/event_handler/bedrock_agent.py
+++ b/aws_lambda_powertools/event_handler/bedrock_agent.py
@@ -23,6 +23,10 @@ def build(self, event: BedrockAgentEvent, *args) -> Dict[str, Any]:
"""Build the full response dict to be returned by the lambda"""
self._route(event, None)
+ body = self.response.body
+ if self.response.is_json() and not isinstance(self.response.body, str):
+ body = self.serializer(self.response.body)
+
return {
"messageVersion": "1.0",
"response": {
@@ -32,7 +36,7 @@ def build(self, event: BedrockAgentEvent, *args) -> Dict[str, Any]:
"httpStatusCode": self.response.status_code,
"responseBody": {
self.response.content_type: {
- "body": self.response.body,
+ "body": body,
},
},
},
diff --git a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
index c162eeb4ce1..34011b64384 100644
--- a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
+++ b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
@@ -62,59 +62,52 @@ def handler(self, app: EventHandlerInstance, next_middleware: NextMiddleware) ->
values: Dict[str, Any] = {}
errors: List[Any] = []
- try:
- # Process path values, which can be found on the route_args
- path_values, path_errors = _request_params_to_args(
- route.dependant.path_params,
- app.context["_route_args"],
+ # Process path values, which can be found on the route_args
+ path_values, path_errors = _request_params_to_args(
+ route.dependant.path_params,
+ app.context["_route_args"],
+ )
+
+ # Process query values
+ query_values, query_errors = _request_params_to_args(
+ route.dependant.query_params,
+ app.current_event.query_string_parameters or {},
+ )
+
+ values.update(path_values)
+ values.update(query_values)
+ errors += path_errors + query_errors
+
+ # Process the request body, if it exists
+ if route.dependant.body_params:
+ (body_values, body_errors) = _request_body_to_args(
+ required_params=route.dependant.body_params,
+ received_body=self._get_body(app),
)
+ values.update(body_values)
+ errors.extend(body_errors)
- # Process query values
- query_values, query_errors = _request_params_to_args(
- route.dependant.query_params,
- app.current_event.query_string_parameters or {},
- )
-
- values.update(path_values)
- values.update(query_values)
- errors += path_errors + query_errors
+ if errors:
+ # Raise the validation errors
+ raise RequestValidationError(_normalize_errors(errors))
+ else:
+ # Re-write the route_args with the validated values, and call the next middleware
+ app.context["_route_args"] = values
- # Process the request body, if it exists
- if route.dependant.body_params:
- (body_values, body_errors) = _request_body_to_args(
- required_params=route.dependant.body_params,
- received_body=self._get_body(app),
- )
- values.update(body_values)
- errors.extend(body_errors)
+ # Call the handler by calling the next middleware
+ response = next_middleware(app)
- if errors:
- # Raise the validation errors
- raise RequestValidationError(_normalize_errors(errors))
- else:
- # Re-write the route_args with the validated values, and call the next middleware
- app.context["_route_args"] = values
-
- # Call the handler by calling the next middleware
- response = next_middleware(app)
-
- # Process the response
- return self._handle_response(route=route, response=response)
- except RequestValidationError as e:
- return Response(
- status_code=422,
- content_type="application/json",
- body=json.dumps({"detail": e.errors()}),
- )
+ # Process the response
+ return self._handle_response(route=route, response=response)
def _handle_response(self, *, route: Route, response: Response):
# Process the response body if it exists
if response.body:
# Validate and serialize the response, if it's JSON
if response.is_json():
- response.body = json.dumps(
- self._serialize_response(field=route.dependant.return_param, response_content=response.body),
- sort_keys=True,
+ response.body = self._serialize_response(
+ field=route.dependant.return_param,
+ response_content=response.body,
)
return response
diff --git a/aws_lambda_powertools/event_handler/openapi/compat.py b/aws_lambda_powertools/event_handler/openapi/compat.py
index 54b78f7e5f6..bd102aa7b93 100644
--- a/aws_lambda_powertools/event_handler/openapi/compat.py
+++ b/aws_lambda_powertools/event_handler/openapi/compat.py
@@ -15,9 +15,9 @@
from pydantic import BaseModel, create_model
from pydantic.fields import FieldInfo
+from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2
from aws_lambda_powertools.event_handler.openapi.types import (
COMPONENT_REF_PREFIX,
- PYDANTIC_V2,
ModelNameMap,
UnionType,
)
diff --git a/aws_lambda_powertools/event_handler/openapi/dependant.py b/aws_lambda_powertools/event_handler/openapi/dependant.py
index 87e0c7dfb3d..e22eb535a7e 100644
--- a/aws_lambda_powertools/event_handler/openapi/dependant.py
+++ b/aws_lambda_powertools/event_handler/openapi/dependant.py
@@ -124,7 +124,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
def get_path_param_names(path: str) -> Set[str]:
"""
- Returns the path parameter names from a path template. Those are the strings between < and >.
+ Returns the path parameter names from a path template. Those are the strings between { and }.
Parameters
----------
@@ -137,7 +137,7 @@ def get_path_param_names(path: str) -> Set[str]:
The path parameter names
"""
- return set(re.findall("<(.*?)>", path))
+ return set(re.findall("{(.*?)}", path))
def get_dependant(
diff --git a/aws_lambda_powertools/event_handler/openapi/models.py b/aws_lambda_powertools/event_handler/openapi/models.py
index bbbc160f1e6..ab97b6dc2e7 100644
--- a/aws_lambda_powertools/event_handler/openapi/models.py
+++ b/aws_lambda_powertools/event_handler/openapi/models.py
@@ -4,7 +4,7 @@
from pydantic import AnyUrl, BaseModel, Field
from aws_lambda_powertools.event_handler.openapi.compat import model_rebuild
-from aws_lambda_powertools.event_handler.openapi.types import PYDANTIC_V2
+from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2
from aws_lambda_powertools.shared.types import Annotated, Literal
"""
diff --git a/aws_lambda_powertools/event_handler/openapi/params.py b/aws_lambda_powertools/event_handler/openapi/params.py
index c8099d20404..bd542ba7932 100644
--- a/aws_lambda_powertools/event_handler/openapi/params.py
+++ b/aws_lambda_powertools/event_handler/openapi/params.py
@@ -5,6 +5,7 @@
from pydantic import BaseConfig
from pydantic.fields import FieldInfo
+from aws_lambda_powertools.event_handler import Response
from aws_lambda_powertools.event_handler.openapi.compat import (
ModelField,
Required,
@@ -14,7 +15,8 @@
field_annotation_is_scalar,
get_annotation_from_field_info,
)
-from aws_lambda_powertools.event_handler.openapi.types import PYDANTIC_V2, CacheKey
+from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2
+from aws_lambda_powertools.event_handler.openapi.types import CacheKey
from aws_lambda_powertools.shared.types import Annotated, Literal, get_args, get_origin
"""
@@ -115,6 +117,64 @@ def __init__(
json_schema_extra: Union[Dict[str, Any], None] = None,
**extra: Any,
):
+ """
+ Constructs a new Param.
+
+ Parameters
+ ----------
+ default: Any
+ The default value of the parameter
+ default_factory: Callable[[], Any], optional
+ Callable that will be called when a default value is needed for this field
+ annotation: Any, optional
+ The type annotation of the parameter
+ alias: str, optional
+ The public name of the field
+ alias_priority: int, optional
+ Priority of the alias. This affects whether an alias generator is used
+ validation_alias: str | AliasPath | AliasChoices | None, optional
+ Alias to be used for validation only
+ serialization_alias: str | AliasPath | AliasChoices | None, optional
+ Alias to be used for serialization only
+ title: str, optional
+ The title of the parameter
+ description: str, optional
+ The description of the parameter
+ gt: float, optional
+ Only applies to numbers, required the field to be "greater than"
+ ge: float, optional
+ Only applies to numbers, required the field to be "greater than or equal"
+ lt: float, optional
+ Only applies to numbers, required the field to be "less than"
+ le: float, optional
+ Only applies to numbers, required the field to be "less than or equal"
+ min_length: int, optional
+ Only applies to strings, required the field to have a minimum length
+ max_length: int, optional
+ Only applies to strings, required the field to have a maximum length
+ pattern: str, optional
+ Only applies to strings, requires the field match against a regular expression pattern string
+ discriminator: str, optional
+ Parameter field name for discriminating the type in a tagged union
+ strict: bool, optional
+ Enables Pydantic's strict mode for the field
+ multiple_of: float, optional
+ Only applies to numbers, requires the field to be a multiple of the given value
+ allow_inf_nan: bool, optional
+ Only applies to numbers, requires the field to allow infinity and NaN values
+ max_digits: int, optional
+ Only applies to Decimals, requires the field to have a maxmium number of digits within the decimal.
+ decimal_places: int, optional
+ Only applies to Decimals, requires the field to have at most a number of decimal places
+ examples: List[Any], optional
+ A list of examples for the parameter
+ deprecated: bool, optional
+ If `True`, the parameter will be marked as deprecated
+ include_in_schema: bool, optional
+ If `False`, the parameter will be excluded from the generated OpenAPI schema
+ json_schema_extra: Dict[str, Any], optional
+ Extra values to include in the generated OpenAPI schema
+ """
self.deprecated = deprecated
self.include_in_schema = include_in_schema
@@ -205,6 +265,64 @@ def __init__(
json_schema_extra: Union[Dict[str, Any], None] = None,
**extra: Any,
):
+ """
+ Constructs a new Path param.
+
+ Parameters
+ ----------
+ default: Any
+ The default value of the parameter
+ default_factory: Callable[[], Any], optional
+ Callable that will be called when a default value is needed for this field
+ annotation: Any, optional
+ The type annotation of the parameter
+ alias: str, optional
+ The public name of the field
+ alias_priority: int, optional
+ Priority of the alias. This affects whether an alias generator is used
+ validation_alias: str | AliasPath | AliasChoices | None, optional
+ Alias to be used for validation only
+ serialization_alias: str | AliasPath | AliasChoices | None, optional
+ Alias to be used for serialization only
+ title: str, optional
+ The title of the parameter
+ description: str, optional
+ The description of the parameter
+ gt: float, optional
+ Only applies to numbers, required the field to be "greater than"
+ ge: float, optional
+ Only applies to numbers, required the field to be "greater than or equal"
+ lt: float, optional
+ Only applies to numbers, required the field to be "less than"
+ le: float, optional
+ Only applies to numbers, required the field to be "less than or equal"
+ min_length: int, optional
+ Only applies to strings, required the field to have a minimum length
+ max_length: int, optional
+ Only applies to strings, required the field to have a maximum length
+ pattern: str, optional
+ Only applies to strings, requires the field match against a regular expression pattern string
+ discriminator: str, optional
+ Parameter field name for discriminating the type in a tagged union
+ strict: bool, optional
+ Enables Pydantic's strict mode for the field
+ multiple_of: float, optional
+ Only applies to numbers, requires the field to be a multiple of the given value
+ allow_inf_nan: bool, optional
+ Only applies to numbers, requires the field to allow infinity and NaN values
+ max_digits: int, optional
+ Only applies to Decimals, requires the field to have a maxmium number of digits within the decimal.
+ decimal_places: int, optional
+ Only applies to Decimals, requires the field to have at most a number of decimal places
+ examples: List[Any], optional
+ A list of examples for the parameter
+ deprecated: bool, optional
+ If `True`, the parameter will be marked as deprecated
+ include_in_schema: bool, optional
+ If `False`, the parameter will be excluded from the generated OpenAPI schema
+ json_schema_extra: Dict[str, Any], optional
+ Extra values to include in the generated OpenAPI schema
+ """
if default is not ...:
raise AssertionError("Path parameters cannot have a default value")
@@ -277,6 +395,64 @@ def __init__(
json_schema_extra: Union[Dict[str, Any], None] = None,
**extra: Any,
):
+ """
+ Constructs a new Query param.
+
+ Parameters
+ ----------
+ default: Any
+ The default value of the parameter
+ default_factory: Callable[[], Any], optional
+ Callable that will be called when a default value is needed for this field
+ annotation: Any, optional
+ The type annotation of the parameter
+ alias: str, optional
+ The public name of the field
+ alias_priority: int, optional
+ Priority of the alias. This affects whether an alias generator is used
+ validation_alias: str | AliasPath | AliasChoices | None, optional
+ Alias to be used for validation only
+ serialization_alias: str | AliasPath | AliasChoices | None, optional
+ Alias to be used for serialization only
+ title: str, optional
+ The title of the parameter
+ description: str, optional
+ The description of the parameter
+ gt: float, optional
+ Only applies to numbers, required the field to be "greater than"
+ ge: float, optional
+ Only applies to numbers, required the field to be "greater than or equal"
+ lt: float, optional
+ Only applies to numbers, required the field to be "less than"
+ le: float, optional
+ Only applies to numbers, required the field to be "less than or equal"
+ min_length: int, optional
+ Only applies to strings, required the field to have a minimum length
+ max_length: int, optional
+ Only applies to strings, required the field to have a maximum length
+ pattern: str, optional
+ Only applies to strings, requires the field match against a regular expression pattern string
+ discriminator: str, optional
+ Parameter field name for discriminating the type in a tagged union
+ strict: bool, optional
+ Enables Pydantic's strict mode for the field
+ multiple_of: float, optional
+ Only applies to numbers, requires the field to be a multiple of the given value
+ allow_inf_nan: bool, optional
+ Only applies to numbers, requires the field to allow infinity and NaN values
+ max_digits: int, optional
+ Only applies to Decimals, requires the field to have a maxmium number of digits within the decimal.
+ decimal_places: int, optional
+ Only applies to Decimals, requires the field to have at most a number of decimal places
+ examples: List[Any], optional
+ A list of examples for the parameter
+ deprecated: bool, optional
+ If `True`, the parameter will be marked as deprecated
+ include_in_schema: bool, optional
+ If `False`, the parameter will be excluded from the generated OpenAPI schema
+ json_schema_extra: Dict[str, Any], optional
+ Extra values to include in the generated OpenAPI schema
+ """
super().__init__(
default=default,
default_factory=default_factory,
@@ -724,6 +900,9 @@ def get_field_info_and_type_annotation(annotation, value, is_path_param: bool) -
# If the annotation is an Annotated type, we need to extract the type annotation and the FieldInfo
if get_origin(annotation) is Annotated:
field_info, type_annotation = get_field_info_annotated_type(annotation, value, is_path_param)
+ # If the annotation is a Response type, we recursively call this function with the inner type
+ elif get_origin(annotation) is Response:
+ field_info, type_annotation = get_field_info_response_type(annotation, value)
# If the annotation is not an Annotated type, we use it as the type annotation
else:
type_annotation = annotation
@@ -731,6 +910,14 @@ def get_field_info_and_type_annotation(annotation, value, is_path_param: bool) -
return field_info, type_annotation
+def get_field_info_response_type(annotation, value) -> Tuple[Optional[FieldInfo], Any]:
+ # Example: get_args(Response[inner_type]) == (inner_type,) # noqa: ERA001
+ (inner_type,) = get_args(annotation)
+
+ # Recursively resolve the inner type
+ return get_field_info_and_type_annotation(inner_type, value, False)
+
+
def get_field_info_annotated_type(annotation, value, is_path_param: bool) -> Tuple[Optional[FieldInfo], Any]:
"""
Get the FieldInfo and type annotation from an Annotated type.
diff --git a/aws_lambda_powertools/event_handler/openapi/pydantic_loader.py b/aws_lambda_powertools/event_handler/openapi/pydantic_loader.py
new file mode 100644
index 00000000000..12f06dad899
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/openapi/pydantic_loader.py
@@ -0,0 +1,6 @@
+try:
+ from pydantic.version import VERSION as PYDANTIC_VERSION
+
+ PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
+except ImportError:
+ PYDANTIC_V2 = False
diff --git a/aws_lambda_powertools/event_handler/openapi/types.py b/aws_lambda_powertools/event_handler/openapi/types.py
index 9161d8dc170..0d166de1131 100644
--- a/aws_lambda_powertools/event_handler/openapi/types.py
+++ b/aws_lambda_powertools/event_handler/openapi/types.py
@@ -16,13 +16,6 @@
COMPONENT_REF_TEMPLATE = "#/components/schemas/{model}"
METHODS_WITH_BODY = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"}
-try:
- from pydantic.version import VERSION as PYDANTIC_VERSION
-
- PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
-except ImportError:
- PYDANTIC_V2 = False
-
validation_error_definition = {
"title": "ValidationError",
diff --git a/aws_lambda_powertools/shared/version.py b/aws_lambda_powertools/shared/version.py
index 0566b37f769..ef589304f98 100644
--- a/aws_lambda_powertools/shared/version.py
+++ b/aws_lambda_powertools/shared/version.py
@@ -1,3 +1,3 @@
"""Exposes version constant to avoid circular dependencies."""
-VERSION = "2.27.0"
+VERSION = "2.28.0"
diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md
index 6868ce25d46..005ac3a4b7b 100644
--- a/docs/core/event_handler/api_gateway.md
+++ b/docs/core/event_handler/api_gateway.md
@@ -11,12 +11,21 @@ Event handler for Amazon API Gateway REST and HTTP APIs, Application Loader Bala
* Support for CORS, binary and Gzip compression, Decimals JSON encoding and bring your own JSON serializer
* Built-in integration with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"} for self-documented event schema
* Works with micro function (one or a few routes) and monolithic functions (all routes)
+* Support for OpenAPI and data validation for requests/responses
## Getting started
???+ tip
All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}.
+### Install
+
+!!! info "This is not necessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}."
+
+**When using the data validation feature**, you need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_.
+
+As of now, both Pydantic V1 and V2 are supported. For a future major version, we will only support Pydantic V2.
+
### Required resources
@@ -221,6 +230,190 @@ If you need to accept multiple HTTP methods in a single function, you can use th
???+ note
It is generally better to have separate functions for each HTTP method, as the functionality tends to differ depending on which method is used.
+### Data validation
+
+!!! note "This changes the authoring experience by relying on Python's type annotations"
+ It's inspired by [FastAPI framework](https://fastapi.tiangolo.com/){target="_blank" rel="nofollow"} for ergonomics and to ease migrations in either direction. We support both Pydantic models and Python's dataclass.
+
+ For brevity, we'll focus on Pydantic only.
+
+All resolvers can optionally coerce and validate incoming requests by setting `enable_validation=True`.
+
+With this feature, we can now express how we expect our incoming data and response to look like. This moves data validation responsibilities to Event Handler resolvers, reducing a ton of boilerplate code.
+
+Let's rewrite the previous examples to signal our resolver what shape we expect our data to be.
+
+
+
+=== "data_validation.py"
+
+ ```python hl_lines="13 16 25 29"
+ --8<-- "examples/event_handler_rest/src/data_validation.py"
+ ```
+
+ 1. This enforces data validation at runtime. Any validation error will return `HTTP 422: Unprocessable Entity error`.
+ 2. We create a Pydantic model to define how our data looks like.
+ 3. Defining a route remains exactly as before.
+ 4. By default, URL Paths will be `str`. Here, we are telling our resolver it should be `int`, so it converts it for us.
Lastly, we're also saying the return should be our `Todo`. This will help us later when we touch OpenAPI auto-documentation.
+ 5. `todo.json()` returns a dictionary. However, Event Handler knows the response should be `Todo` so it converts and validates accordingly.
+
+=== "data_validation.json"
+
+ ```json hl_lines="4"
+ --8<-- "examples/event_handler_rest/src/data_validation.json"
+ ```
+
+=== "data_validation_output.json"
+
+ ```json hl_lines="2-3"
+ --8<-- "examples/event_handler_rest/src/data_validation_output.json"
+ ```
+
+
+
+#### Handling validation errors
+
+!!! info "By default, we hide extended error details for security reasons _(e.g., pydantic url, Pydantic code)_."
+
+Any incoming request that fails validation will lead to a `HTTP 422: Unprocessable Entity error` response that will look similar to this:
+
+```json hl_lines="2 3" title="data_validation_error_unsanitized_output.json"
+--8<-- "examples/event_handler_rest/src/data_validation_error_unsanitized_output.json"
+```
+
+You can customize the error message by catching the `RequestValidationError` exception. This is useful when you might have a security policy to return opaque validation errors, or have a company standard for API validation errors.
+
+Here's an example where we catch validation errors, log all details for further investigation, and return the same `HTTP 422` with an opaque error.
+
+=== "data_validation_sanitized_error.py"
+
+ Note that Pydantic versions [1](https://docs.pydantic.dev/1.10/usage/models/#error-handling){target="_blank" rel="nofollow"} and [2](https://docs.pydantic.dev/latest/errors/errors/){target="_blank" rel="nofollow"} report validation detailed errors differently.
+
+ ```python hl_lines="8 24-25 31"
+ --8<-- "examples/event_handler_rest/src/data_validation_sanitized_error.py"
+ ```
+
+ 1. We use [exception handler](#exception-handling) decorator to catch **any** request validation errors.
Then, we log the detailed reason as to why it failed while returning a custom `Response` object to hide that from them.
+
+=== "data_validation_sanitized_error_output.json"
+
+ ```json hl_lines="2 3"
+ --8<-- "examples/event_handler_rest/src/data_validation_sanitized_error_output.json"
+ ```
+
+#### Validating payloads
+
+!!! info "We will automatically validate, inject, and convert incoming request payloads based on models via type annotation."
+
+Let's improve our previous example by handling the creation of todo items via `HTTP POST`.
+
+What we want is for Event Handler to convert the incoming payload as an instance of our `Todo` model. We handle the creation of that `todo`, and then return the `ID` of the newly created `todo`.
+
+Even better, we can also let Event Handler validate and convert our response according to type annotations, further reducing boilerplate.
+
+=== "validating_payloads.py"
+
+ ```python hl_lines="13 16 24 33"
+ --8<-- "examples/event_handler_rest/src/validating_payloads.py"
+ ```
+
+ 1. This enforces data validation at runtime. Any validation error will return `HTTP 422: Unprocessable Entity error`.
+ 2. We create a Pydantic model to define how our data looks like.
+ 3. We define `Todo` as our type annotation. Event Handler then uses this model to validate and inject the incoming request as `Todo`.
+ 4. Lastly, we return the ID of our newly created `todo` item.
Because we specify the return type (`str`), Event Handler will take care of serializing this as a JSON string.
+ 5. Note that the return type is `List[Todo]`.
Event Handler will take the return (`todo.json`), and validate each list item against `Todo` model before returning the response accordingly.
+
+=== "validating_payloads.json"
+
+ ```json hl_lines="3 5-6"
+ --8<-- "examples/event_handler_rest/src/validating_payloads.json"
+ ```
+
+=== "validating_payloads_output.json"
+
+ ```json hl_lines="3"
+ --8<-- "examples/event_handler_rest/src/validating_payloads_output.json"
+ ```
+
+##### Validating payload subset
+
+With the addition of the [`Annotated` type starting in Python 3.9](https://docs.python.org/3/library/typing.html#typing.Annotated){target="_blank" rel="nofollow"}, types can contain additional metadata, allowing us to represent anything we want.
+
+We use the `Annotated` and OpenAPI `Body` type to instruct Event Handler that our payload is located in a particular JSON key.
+
+!!! note "Event Handler will match the parameter name with the JSON key to validate and inject what you want."
+
+=== "validating_payload_subset.py"
+
+ ```python hl_lines="7 8 22"
+ --8<-- "examples/event_handler_rest/src/validating_payload_subset.py"
+ ```
+
+ 1. `Body` is a special OpenAPI type that can add additional constraints to a request payload.
+ 2. `Body(embed=True)` instructs Event Handler to look up inside the payload for a key.
This means Event Handler will look up for a key named `todo`, validate the value against `Todo`, and inject it.
+
+=== "validating_payload_subset.json"
+
+ ```json hl_lines="3-4 6"
+ --8<-- "examples/event_handler_rest/src/validating_payload_subset.json"
+ ```
+
+=== "validating_payload_subset_output.json"
+
+ ```json hl_lines="3"
+ --8<-- "examples/event_handler_rest/src/validating_payload_subset_output.json"
+ ```
+
+#### Validating query strings
+
+!!! info "We will automatically validate and inject incoming query strings via type annotation."
+
+We use the `Annotated` type to tell Event Handler that a particular parameter is not only an optional string, but also a query string with constraints.
+
+In the following example, we use a new `Query` OpenAPI type to add [one out of many possible constraints](#customizing-openapi-parameters), which should read as:
+
+* `completed` is a query string with a `None` as its default value
+* `completed`, when set, should have at minimum 4 characters
+* Doesn't match? Event Handler will return a validation error response
+
+
+
+=== "validating_query_strings.py"
+
+ ```python hl_lines="8 10 27"
+ --8<-- "examples/event_handler_rest/src/validating_query_strings.py"
+ ```
+
+ 1. If you're not using Python 3.9 or higher, you can install and use [`typing_extensions`](https://pypi.org/project/typing-extensions/){target="_blank" rel="nofollow"} to the same effect
+ 2. `Query` is a special OpenAPI type that can add constraints to a query string as well as document them
+ 3. **First time seeing the `Annotated`?**
This special type uses the first argument as the actual type, and subsequent arguments are metadata.
At runtime, static checkers will also see the first argument, but anyone receiving them could inspect them to fetch their metadata.
+
+=== "skip_validating_query_strings.py"
+
+ If you don't want to validate query strings but simply let Event Handler inject them as parameters, you can omit `Query` type annotation.
+
+ This is merely for your convenience.
+
+ ```python hl_lines="25"
+ --8<-- "examples/event_handler_rest/src/skip_validating_query_strings.py"
+ ```
+
+ 1. `completed` is still the same query string as before, except we simply state it's an string. No `Query` or `Annotated` to validate it.
+
+
+
+#### Validating path parameters
+
+Just like we learned in [query string validation](#validating-query-strings), we can use a new `Path` OpenAPI type to [add constraints](#customizing-openapi-parameters).
+
+For example, we could validate that `` dynamic path should be no greater than three digits.
+
+```python hl_lines="8 10 27" title="validating_path.py"
+--8<-- "examples/event_handler_rest/src/validating_path.py"
+```
+
+1. `Path` is a special OpenAPI type that allows us to constrain todo_id to be less than 999.
+
### Accessing request details
Event Handler integrates with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"}, and it exposes their respective resolver request details and convenient methods under `app.current_event`.
@@ -279,6 +472,30 @@ We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 4
--8<-- "examples/event_handler_rest/src/raising_http_errors.py"
```
+### Enabling SwaggerUI
+
+!!! note "This feature requires [data validation](#data-validation) feature to be enabled."
+
+Behind the scenes, the [data validation](#data-validation) feature auto-generates an OpenAPI specification from your routes and type annotations. You can use [Swagger UI](https://swagger.io/tools/swagger-ui/){target="_blank" rel="nofollow"} to visualize and interact with your newly auto-documented API.
+
+There are some important **caveats** that you should know before enabling it:
+
+| Caveat | Description |
+| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Swagger UI is **publicly accessible by default** | When using `enable_swagger` method, you can [protect sensitive API endpoints by implementing a custom middleware](#customizing-swagger-ui) using your preferred authorization mechanism. |
+| **No micro-functions support** yet | Swagger UI is enabled on a per resolver instance which will limit its accuracy here. |
+| You need to expose **new routes** | You'll need to expose the following paths to Lambda: `/swagger`, `/swagger.css`, `/swagger.js`; ignore if you're routing all paths already. |
+
+```python hl_lines="12-13" title="enabling_swagger.py"
+--8<-- "examples/event_handler_rest/src/enabling_swagger.py"
+```
+
+1. `enable_swagger` creates a route to serve Swagger UI and allows quick customizations.
You can also include middlewares to protect or enhance the overall experience.
+
+Here's an example of what it looks like by default:
+
+
+
### Custom Domain API Mappings
When using [Custom Domain API Mappings feature](https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-mappings.html){target="_blank"}, you must use **`strip_prefixes`** param in the `APIGatewayRestResolver` constructor.
@@ -573,8 +790,8 @@ As a practical example, let's refactor our correlation ID middleware so it accep
These are native middlewares that may become native features depending on customer demand.
-| Middleware | Purpose |
-| ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
+| Middleware | Purpose |
+| ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [SchemaValidationMiddleware](/lambda/python/latest/api/event_handler/middlewares/schema_validation.html){target="_blank"} | Validates API request body and response against JSON Schema, using [Validation utility](../../utilities/validation.md){target="_blank"} |
#### Being a good citizen
@@ -696,6 +913,112 @@ This will enable full tracebacks errors in the response, print request and respo
--8<-- "examples/event_handler_rest/src/debug_mode.py"
```
+### OpenAPI
+
+When you enable [Data Validation](#data-validation), we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your API's parameters.
+
+In OpenAPI documentation tools like [SwaggerUI](#enabling-swaggerui), these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation.
+
+???+ note
+ We don't have support for files, form data, and header parameters at the moment. If you're interested in this, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=feature-request%2Ctriage&projects=&template=feature_request.yml&title=Feature+request%3A+TITLE).
+
+#### Customizing OpenAPI parameters
+
+Whenever you use OpenAPI parameters to validate [query strings](#validating-query-strings) or [path parameters](#validating-path-parameters), you can enhance validation and OpenAPI documentation by using any of these parameters:
+
+| Field name | Type | Description |
+| --------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `alias` | `str` | Alternative name for a field, used when serializing and deserializing data |
+| `validation_alias` | `str` | Alternative name for a field during validation (but not serialization) |
+| `serialization_alias` | `str` | Alternative name for a field during serialization (but not during validation) |
+| `description` | `str` | Human-readable description |
+| `gt` | `float` | Greater than. If set, value must be greater than this. Only applicable to numbers |
+| `ge` | `float` | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers |
+| `lt` | `float` | Less than. If set, value must be less than this. Only applicable to numbers |
+| `le` | `float` | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers |
+| `min_length` | `int` | Minimum length for strings |
+| `max_length` | `int` | Maximum length for strings |
+| `pattern` | `string` | A regular expression that the string must match. |
+| `strict` | `bool` | If `True`, strict validation is applied to the field. See [Strict Mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target"_blank" rel="nofollow"} for details |
+| `multiple_of` | `float` | Value must be a multiple of this. Only applicable to numbers |
+| `allow_inf_nan` | `bool` | Allow `inf`, `-inf`, `nan`. Only applicable to numbers |
+| `max_digits` | `int` | Maximum number of allow digits for strings |
+| `decimal_places` | `int` | Maximum number of decimal places allowed for numbers |
+| `examples` | `List\[Any\]` | List of examples of the field |
+| `deprecated` | `bool` | Marks the field as deprecated |
+| `include_in_schema` | `bool` | If `False` the field will not be part of the exported OpenAPI schema |
+| `json_schema_extra` | `JsonDict` | Any additional JSON schema data for the schema property |
+
+#### Customizing API operations
+
+Customize your API endpoints by adding metadata to endpoint definitions. This provides descriptive documentation for API consumers and gives extra instructions to the framework.
+
+Here's a breakdown of various customizable fields:
+
+| Field Name | Type | Description |
+| ---------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `summary` | `str` | A concise overview of the main functionality of the endpoint. This brief introduction is usually displayed in autogenerated API documentation and helps consumers quickly understand what the endpoint does. |
+| `description` | `str` | A more detailed explanation of the endpoint, which can include information about the operation's behavior, including side effects, error states, and other operational guidelines. |
+| `responses` | `Dict[int, Dict[str, Any]]` | A dictionary that maps each HTTP status code to a Response Object as defined by the [OpenAPI Specification](https://swagger.io/specification/#response-object). This allows you to describe expected responses, including default or error messages, and their corresponding schemas for different status codes. |
+| `response_description` | `str` | Provides the default textual description of the response sent by the endpoint when the operation is successful. It is intended to give a human-readable understanding of the result. |
+| `tags` | `List[str]` | Tags are a way to categorize and group endpoints within the API documentation. They can help organize the operations by resources or other heuristic. |
+| `operation_id` | `str` | A unique identifier for the operation, which can be used for referencing this operation in documentation or code. This ID must be unique across all operations described in the API. |
+| `include_in_schema` | `bool` | A boolean value that determines whether or not this operation should be included in the OpenAPI schema. Setting it to `False` can hide the endpoint from generated documentation and schema exports, which might be useful for private or experimental endpoints. |
+
+To implement these customizations, include extra parameters when defining your routes:
+
+```python hl_lines="11-20" title="customizing_api_operations.py"
+--8<-- "examples/event_handler_rest/src/customizing_api_operations.py"
+```
+
+#### Customizing Swagger UI
+
+???+note "Customizing the Swagger metadata"
+ The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](#customizing-openapi-metadata).
+
+The Swagger UI appears by default at the `/swagger` path, but you can customize this to serve the documentation from another path and specify the source for Swagger UI assets.
+
+Below is an example configuration for serving Swagger UI from a custom path or CDN, with assets like CSS and JavaScript loading from a chosen CDN base URL.
+
+=== "customizing_swagger.py"
+
+ ```python hl_lines="10"
+ --8<-- "examples/event_handler_rest/src/customizing_swagger.py"
+ ```
+
+=== "customizing_swagger_middlewares.py"
+
+ A Middleware can handle tasks such as adding security headers, user authentication, or other request processing for serving the Swagger UI.
+
+ ```python hl_lines="7 13-18 21"
+ --8<-- "examples/event_handler_rest/src/customizing_swagger_middlewares.py"
+ ```
+
+#### Customizing OpenAPI metadata
+
+Defining and customizing OpenAPI metadata gives detailed, top-level information about your API. Here's the method to set and tailor this metadata:
+
+| Field Name | Type | Description |
+| ------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `title` | `str` | The title for your API. It should be a concise, specific name that can be used to identify the API in documentation or listings. |
+| `version` | `str` | The version of the API you are documenting. This could reflect the release iteration of the API and helps clients understand the evolution of the API. |
+| `openapi_version` | `str` | Specifies the version of the OpenAPI Specification on which your API is based. For most contemporary APIs, the default value would be `3.0.0` or higher. |
+| `summary` | `str` | A short and informative summary that can provide an overview of what the API does. This can be the same as or different from the title but should add context or information. |
+| `description` | `str` | A verbose description that can include Markdown formatting, providing a full explanation of the API's purpose, functionalities, and general usage instructions. |
+| `tags` | `List[str]` | A collection of tags that categorize endpoints for better organization and navigation within the documentation. This can group endpoints by their functionality or other criteria. |
+| `servers` | `List[Server]` | An array of Server objects, which specify the URL to the server and a description for its environment (production, staging, development, etc.), providing connectivity information. |
+| `terms_of_service` | `str` | A URL that points to the terms of service for your API. This could provide legal information and user responsibilities related to the usage of the API. |
+| `contact` | `Contact` | A Contact object containing contact details of the organization or individuals maintaining the API. This may include fields such as name, URL, and email. |
+| `license_info` | `License` | A License object providing the license details for the API, typically including the name of the license and the URL to the full license text. |
+
+Include extra parameters when exporting your OpenAPI specification to apply these customizations:
+
+=== "customizing_api_metadata.py"
+
+ ```python hl_lines="25-31"
+ --8<-- "examples/event_handler_rest/src/customizing_api_metadata.py"
+ ```
+
### Custom serializer
You can instruct event handler to use a custom serializer to best suit your needs, for example take into account Enums when serializing.
diff --git a/docs/index.md b/docs/index.md
index 7c8959612c9..5f5cd8e6e5a 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -26,8 +26,8 @@ Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverles
You can install Powertools for AWS Lambda (Python) using one of the following options:
-* **Lambda Layer (x86_64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:48**](# "Replace {region} with your AWS region, e.g., eu-west-1"){: .copyMe}:clipboard:
-* **Lambda Layer (arm64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48**](# "Replace {region} with your AWS region, e.g., eu-west-1"){: .copyMe}:clipboard:
+* **Lambda Layer (x86_64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:49**](# "Replace {region} with your AWS region, e.g., eu-west-1"){: .copyMe}:clipboard:
+* **Lambda Layer (arm64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49**](# "Replace {region} with your AWS region, e.g., eu-west-1"){: .copyMe}:clipboard:
* **Pip**: **[`pip install "aws-lambda-powertools"`](#){: .copyMe}:clipboard:**
!!! question "Looking for Pip signed releases? [Learn more about verifying signed builds](./security.md#verifying-signed-builds)"
@@ -80,66 +80,66 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
| Region | Layer ARN |
| ---------------- | ---------------------------------------------------------------------------------------------------------- |
- | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `ap-south-2` | [arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
- | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `ap-southeast-4` | [arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
- | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `eu-central-2` | [arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
- | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `eu-south-2` | [arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
- | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `il-central-1` | [arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
- | `me-central-1` | [arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
- | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
- | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:48](#){: .copyMe}:clipboard: |
+ | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `ap-south-2` | [arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:50](#){: .copyMe}:clipboard: |
+ | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `ap-southeast-4` | [arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:50](#){: .copyMe}:clipboard: |
+ | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `eu-central-2` | [arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:50](#){: .copyMe}:clipboard: |
+ | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `eu-south-2` | [arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:50](#){: .copyMe}:clipboard: |
+ | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `il-central-1` | [arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:50](#){: .copyMe}:clipboard: |
+ | `me-central-1` | [arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:50](#){: .copyMe}:clipboard: |
+ | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
+ | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:49](#){: .copyMe}:clipboard: |
=== "arm64"
| Region | Layer ARN |
| ---------------- | ---------------------------------------------------------------------------------------------------------------- |
- | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `ap-south-2` | [arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
- | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `eu-central-2` | [arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
- | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `eu-south-2` | [arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
- | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `il-central-1` | [arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
- | `me-central-1` | [arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
- | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
- | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48](#){: .copyMe}:clipboard: |
+ | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `ap-south-2` | [arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:50](#){: .copyMe}:clipboard: |
+ | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `eu-central-2` | [arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:50](#){: .copyMe}:clipboard: |
+ | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `eu-south-2` | [arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:50](#){: .copyMe}:clipboard: |
+ | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `il-central-1` | [arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:50](#){: .copyMe}:clipboard: |
+ | `me-central-1` | [arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:50](#){: .copyMe}:clipboard: |
+ | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
+ | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49](#){: .copyMe}:clipboard: |
??? note "Note: Click to expand and copy code snippets for popular frameworks"
@@ -152,7 +152,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
Type: AWS::Serverless::Function
Properties:
Layers:
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:48
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:49
```
=== "Serverless framework"
@@ -162,7 +162,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
hello:
handler: lambda_function.lambda_handler
layers:
- - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:48
+ - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:49
```
=== "CDK"
@@ -178,7 +178,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn(
self,
id="lambda-powertools",
- layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:48"
+ layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:49"
)
aws_lambda.Function(self,
'sample-app-lambda',
@@ -227,7 +227,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"
runtime = "python3.9"
- layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:48"]
+ layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:49"]
source_code_hash = filebase64sha256("lambda_function_payload.zip")
}
@@ -280,7 +280,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
? Do you want to configure advanced settings? Yes
...
? Do you want to enable Lambda layers for this function? Yes
- ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49
❯ amplify push -y
@@ -291,7 +291,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
- Name:
? Which setting do you want to update? Lambda layers configuration
? Do you want to enable Lambda layers for this function? Yes
- ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:48
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:49
? Do you want to edit the local lambda function now? No
```
@@ -305,7 +305,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
Properties:
Architectures: [arm64]
Layers:
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49
```
=== "Serverless framework"
@@ -316,7 +316,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
handler: lambda_function.lambda_handler
architecture: arm64
layers:
- - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48
+ - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49
```
=== "CDK"
@@ -332,7 +332,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn(
self,
id="lambda-powertools",
- layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48"
+ layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49"
)
aws_lambda.Function(self,
'sample-app-lambda',
@@ -382,7 +382,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"
runtime = "python3.9"
- layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48"]
+ layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49"]
architectures = ["arm64"]
source_code_hash = filebase64sha256("lambda_function_payload.zip")
@@ -438,7 +438,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
? Do you want to configure advanced settings? Yes
...
? Do you want to enable Lambda layers for this function? Yes
- ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49
❯ amplify push -y
@@ -449,7 +449,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
- Name:
? Which setting do you want to update? Lambda layers configuration
? Do you want to enable Lambda layers for this function? Yes
- ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:48
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:49
? Do you want to edit the local lambda function now? No
```
@@ -457,7 +457,7 @@ You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambd
Change {region} to your AWS region, e.g. `eu-west-1`
```bash title="AWS CLI"
- aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:48 --region {region}
+ aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:49 --region {region}
```
The pre-signed URL to download this Lambda Layer will be within `Location` key.
diff --git a/docs/media/swagger.png b/docs/media/swagger.png
new file mode 100644
index 00000000000..3db7786886b
Binary files /dev/null and b/docs/media/swagger.png differ
diff --git a/examples/event_handler_lambda_function_url/sam/template.yaml b/examples/event_handler_lambda_function_url/sam/template.yaml
index 457f68c3816..0c58c9e87c1 100644
--- a/examples/event_handler_lambda_function_url/sam/template.yaml
+++ b/examples/event_handler_lambda_function_url/sam/template.yaml
@@ -5,7 +5,7 @@ Description: Hello world event handler Lambda Function URL
Globals:
Function:
Timeout: 5
- Runtime: python3.9
+ Runtime: python3.11
Tracing: Active
Environment:
Variables:
@@ -28,4 +28,4 @@ Resources:
CodeUri: ../src
Description: API handler function
FunctionUrlConfig:
- AuthType: NONE # AWS_IAM for added security beyond sample documentation
+ AuthType: NONE # AWS_IAM for added security beyond sample documentation
diff --git a/examples/event_handler_rest/sam/template.yaml b/examples/event_handler_rest/sam/template.yaml
index 67000a39122..aa0ad4b0452 100644
--- a/examples/event_handler_rest/sam/template.yaml
+++ b/examples/event_handler_rest/sam/template.yaml
@@ -59,3 +59,21 @@ Resources:
# Properties:
# Path: /todos
# Method: POST
+
+ ## Swagger UI specific routes
+
+ # SwaggerUI:
+ # Type: Api
+ # Properties:
+ # Path: /swagger
+ # Method: GET
+ # SwaggerUICSS:
+ # Type: Api
+ # Properties:
+ # Path: /swagger.css
+ # Method: GET
+ # SwaggerUIJS:
+ # Type: Api
+ # Properties:
+ # Path: /swagger.js
+ # Method: GET
diff --git a/examples/event_handler_rest/src/customizing_api_metadata.py b/examples/event_handler_rest/src/customizing_api_metadata.py
new file mode 100644
index 00000000000..cd9ced455d2
--- /dev/null
+++ b/examples/event_handler_rest/src/customizing_api_metadata.py
@@ -0,0 +1,33 @@
+import requests
+
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler.openapi.models import Contact, Server
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+@app.get("/todos/")
+def get_todo_title(todo_id: int) -> str:
+ todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}")
+ todo.raise_for_status()
+
+ return todo.json()["title"]
+
+
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
+
+
+if __name__ == "__main__":
+ print(
+ app.get_openapi_json_schema(
+ title="TODO's API",
+ version="1.21.3",
+ summary="API to manage TODOs",
+ description="This API implements all the CRUD operations for the TODO app",
+ tags=["todos"],
+ servers=[Server(url="https://stg.example.org/orders", description="Staging server")],
+ contact=Contact(name="John Smith", email="john@smith.com"),
+ ),
+ )
diff --git a/examples/event_handler_rest/src/customizing_api_operations.py b/examples/event_handler_rest/src/customizing_api_operations.py
new file mode 100644
index 00000000000..e455fc7dadd
--- /dev/null
+++ b/examples/event_handler_rest/src/customizing_api_operations.py
@@ -0,0 +1,30 @@
+import requests
+
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+@app.get(
+ "/todos/",
+ summary="Retrieves a todo item",
+ description="Loads a todo item identified by the `todo_id`",
+ response_description="The todo object",
+ responses={
+ 200: {"description": "Todo item found"},
+ 404: {
+ "description": "Item not found",
+ },
+ },
+ tags=["Todos"],
+)
+def get_todo_title(todo_id: int) -> str:
+ todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}")
+ todo.raise_for_status()
+
+ return todo.json()["title"]
+
+
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/customizing_swagger.py b/examples/event_handler_rest/src/customizing_swagger.py
new file mode 100644
index 00000000000..4903ff25443
--- /dev/null
+++ b/examples/event_handler_rest/src/customizing_swagger.py
@@ -0,0 +1,29 @@
+from typing import List
+
+import requests
+from pydantic import BaseModel, EmailStr, Field
+
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = APIGatewayRestResolver(enable_validation=True)
+app.enable_swagger(path="/_swagger", swagger_base_url="https://cdn.example.com/path/to/assets/")
+
+
+class Todo(BaseModel):
+ userId: int
+ id_: int = Field(alias="id")
+ title: str
+ completed: bool
+
+
+@app.get("/todos")
+def get_todos_by_email(email: EmailStr) -> List[Todo]:
+ todos = requests.get(f"https://jsonplaceholder.typicode.com/todos?email={email}")
+ todos.raise_for_status()
+
+ return todos.json()
+
+
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/customizing_swagger_middlewares.py b/examples/event_handler_rest/src/customizing_swagger_middlewares.py
new file mode 100644
index 00000000000..49822fecefe
--- /dev/null
+++ b/examples/event_handler_rest/src/customizing_swagger_middlewares.py
@@ -0,0 +1,40 @@
+from typing import List
+
+import requests
+from pydantic import BaseModel, EmailStr, Field
+
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response
+from aws_lambda_powertools.event_handler.middlewares import NextMiddleware
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+def swagger_middleware(app: APIGatewayRestResolver, next_middleware: NextMiddleware) -> Response:
+ is_authenticated = ...
+ if not is_authenticated:
+ return Response(status_code=400, body="Unauthorized")
+
+ return next_middleware(app)
+
+
+app.enable_swagger(middlewares=[swagger_middleware])
+
+
+class Todo(BaseModel):
+ userId: int
+ id_: int = Field(alias="id")
+ title: str
+ completed: bool
+
+
+@app.get("/todos")
+def get_todos_by_email(email: EmailStr) -> List[Todo]:
+ todos = requests.get(f"https://jsonplaceholder.typicode.com/todos?email={email}")
+ todos.raise_for_status()
+
+ return todos.json()
+
+
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/data_validation.json b/examples/event_handler_rest/src/data_validation.json
new file mode 100644
index 00000000000..f5814ccaa26
--- /dev/null
+++ b/examples/event_handler_rest/src/data_validation.json
@@ -0,0 +1,36 @@
+{
+ "version": "1.0",
+ "resource": "/todos/1",
+ "path": "/todos/1",
+ "httpMethod": "GET",
+ "headers": {
+ "Origin": "https://aws.amazon.com"
+ },
+ "multiValueHeaders": {},
+ "queryStringParameters": {},
+ "multiValueQueryStringParameters": {},
+ "requestContext": {
+ "accountId": "123456789012",
+ "apiId": "id",
+ "authorizer": {
+ "claims": null,
+ "scopes": null
+ },
+ "domainName": "id.execute-api.us-east-1.amazonaws.com",
+ "domainPrefix": "id",
+ "extendedRequestId": "request-id",
+ "httpMethod": "GET",
+ "path": "/todos/1",
+ "protocol": "HTTP/1.1",
+ "requestId": "id=",
+ "requestTime": "04/Mar/2020:19:15:17 +0000",
+ "requestTimeEpoch": 1583349317135,
+ "resourceId": null,
+ "resourcePath": "/todos/1",
+ "stage": "$default"
+ },
+ "pathParameters": null,
+ "stageVariables": null,
+ "body": "",
+ "isBase64Encoded": false
+}
\ No newline at end of file
diff --git a/examples/event_handler_rest/src/data_validation.py b/examples/event_handler_rest/src/data_validation.py
new file mode 100644
index 00000000000..1daa9fb2174
--- /dev/null
+++ b/examples/event_handler_rest/src/data_validation.py
@@ -0,0 +1,35 @@
+from typing import Optional
+
+import requests
+from pydantic import BaseModel, Field
+
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.logging import correlation_paths
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+tracer = Tracer()
+logger = Logger()
+app = APIGatewayRestResolver(enable_validation=True) # (1)!
+
+
+class Todo(BaseModel): # (2)!
+ userId: int
+ id_: Optional[int] = Field(alias="id", default=None)
+ title: str
+ completed: bool
+
+
+@app.get("/todos/") # (3)!
+@tracer.capture_method
+def get_todo_by_id(todo_id: int) -> Todo: # (4)!
+ todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}")
+ todo.raise_for_status()
+
+ return todo.json() # (5)!
+
+
+@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
+@tracer.capture_lambda_handler
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/data_validation_error.json b/examples/event_handler_rest/src/data_validation_error.json
new file mode 100644
index 00000000000..6fc2636ad9c
--- /dev/null
+++ b/examples/event_handler_rest/src/data_validation_error.json
@@ -0,0 +1,42 @@
+{
+ "version": "2.0",
+ "routeKey": "$default",
+ "rawPath": "/todos/apples",
+ "rawQueryString": "",
+ "cookies": [
+ "cookie1",
+ "cookie2"
+ ],
+ "headers": {
+ "header1": "value1",
+ "header2": "value1,value2"
+ },
+ "queryStringParameters": {
+ "parameter1": "value1,value2",
+ "parameter2": "value"
+ },
+ "requestContext": {
+ "accountId": "123456789012",
+ "apiId": "api-id",
+ "domainName": "id.execute-api.us-east-1.amazonaws.com",
+ "domainPrefix": "id",
+ "http": {
+ "method": "GET",
+ "path": "/todos/apples",
+ "protocol": "HTTP/1.1",
+ "sourceIp": "192.0.2.1",
+ "userAgent": "agent"
+ },
+ "requestId": "id",
+ "routeKey": "$default",
+ "stage": "$default",
+ "time": "12/Mar/2020:19:03:58 +0000",
+ "timeEpoch": 1583348638390
+ },
+ "pathParameters": {},
+ "isBase64Encoded": false,
+ "stageVariables": {
+ "stageVariable1": "value1",
+ "stageVariable2": "value2"
+ }
+}
diff --git a/examples/event_handler_rest/src/data_validation_error_unsanitized_output.json b/examples/event_handler_rest/src/data_validation_error_unsanitized_output.json
new file mode 100644
index 00000000000..46d22c00eef
--- /dev/null
+++ b/examples/event_handler_rest/src/data_validation_error_unsanitized_output.json
@@ -0,0 +1,9 @@
+{
+ "statusCode": 422,
+ "body": "{\"statusCode\": 422, \"detail\": [{\"type\": \"int_parsing\", \"loc\": [\"path\", \"todo_id\"]}]}",
+ "isBase64Encoded": false,
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "cookies": []
+}
\ No newline at end of file
diff --git a/examples/event_handler_rest/src/data_validation_output.json b/examples/event_handler_rest/src/data_validation_output.json
new file mode 100644
index 00000000000..ec078c87078
--- /dev/null
+++ b/examples/event_handler_rest/src/data_validation_output.json
@@ -0,0 +1,10 @@
+{
+ "statusCode": 200,
+ "body": "Hello world",
+ "isBase64Encoded": false,
+ "multiValueHeaders": {
+ "Content-Type": [
+ "application/json"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/examples/event_handler_rest/src/data_validation_sanitized_error.py b/examples/event_handler_rest/src/data_validation_sanitized_error.py
new file mode 100644
index 00000000000..71849938f48
--- /dev/null
+++ b/examples/event_handler_rest/src/data_validation_sanitized_error.py
@@ -0,0 +1,46 @@
+from typing import Optional
+
+import requests
+from pydantic import BaseModel, Field
+
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response, content_types
+from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
+from aws_lambda_powertools.logging import correlation_paths
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+tracer = Tracer()
+logger = Logger()
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+class Todo(BaseModel):
+ userId: int
+ id_: Optional[int] = Field(alias="id", default=None)
+ title: str
+ completed: bool
+
+
+@app.exception_handler(RequestValidationError) # (1)!
+def handle_validation_error(ex: RequestValidationError):
+ logger.error("Request failed validation", path=app.current_event.path, errors=ex.errors())
+
+ return Response(
+ status_code=422,
+ content_type=content_types.APPLICATION_JSON,
+ body="Invalid data",
+ )
+
+
+@app.post("/todos")
+def create_todo(todo: Todo) -> int:
+ response = requests.post("https://jsonplaceholder.typicode.com/todos", json=todo.dict(by_alias=True))
+ response.raise_for_status()
+
+ return response.json()["id"]
+
+
+@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
+@tracer.capture_lambda_handler
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/data_validation_sanitized_error_output.json b/examples/event_handler_rest/src/data_validation_sanitized_error_output.json
new file mode 100644
index 00000000000..aa6ab7e0d57
--- /dev/null
+++ b/examples/event_handler_rest/src/data_validation_sanitized_error_output.json
@@ -0,0 +1,9 @@
+{
+ "statusCode": 422,
+ "body": "Invalid data",
+ "isBase64Encoded": false,
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "cookies": []
+}
\ No newline at end of file
diff --git a/examples/event_handler_rest/src/enabling_swagger.py b/examples/event_handler_rest/src/enabling_swagger.py
new file mode 100644
index 00000000000..b624af77d32
--- /dev/null
+++ b/examples/event_handler_rest/src/enabling_swagger.py
@@ -0,0 +1,40 @@
+from typing import List
+
+import requests
+from pydantic import BaseModel, Field
+
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+tracer = Tracer()
+logger = Logger()
+app = APIGatewayRestResolver(enable_validation=True)
+app.enable_swagger(path="/swagger") # (1)!
+
+
+class Todo(BaseModel):
+ userId: int
+ id_: int = Field(alias="id")
+ title: str
+ completed: bool
+
+
+@app.post("/todos")
+def create_todo(todo: Todo) -> str:
+ response = requests.post("https://jsonplaceholder.typicode.com/todos", json=todo.dict(by_alias=True))
+ response.raise_for_status()
+
+ return response.json()["id"]
+
+
+@app.get("/todos")
+def get_todos() -> List[Todo]:
+ todo = requests.get("https://jsonplaceholder.typicode.com/todos")
+ todo.raise_for_status()
+
+ return todo.json()
+
+
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/skip_validating_query_strings.py b/examples/event_handler_rest/src/skip_validating_query_strings.py
new file mode 100644
index 00000000000..882769239a1
--- /dev/null
+++ b/examples/event_handler_rest/src/skip_validating_query_strings.py
@@ -0,0 +1,40 @@
+from typing import List, Optional
+
+import requests
+from pydantic import BaseModel, Field
+
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.logging import correlation_paths
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+tracer = Tracer()
+logger = Logger()
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+class Todo(BaseModel):
+ userId: int
+ id_: Optional[int] = Field(alias="id", default=None)
+ title: str
+ completed: bool
+
+
+@app.get("/todos")
+@tracer.capture_method
+def get_todos(completed: Optional[str] = None) -> List[Todo]: # (1)!
+ url = "https://jsonplaceholder.typicode.com/todos"
+
+ if completed is not None:
+ url = f"{url}/?completed={completed}"
+
+ todo = requests.get(url)
+ todo.raise_for_status()
+
+ return todo.json()
+
+
+@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
+@tracer.capture_lambda_handler
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/validating_path.py b/examples/event_handler_rest/src/validating_path.py
new file mode 100644
index 00000000000..e892e1c8597
--- /dev/null
+++ b/examples/event_handler_rest/src/validating_path.py
@@ -0,0 +1,37 @@
+from typing import Optional
+
+import requests
+from pydantic import BaseModel, Field
+
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler.openapi.params import Path
+from aws_lambda_powertools.logging import correlation_paths
+from aws_lambda_powertools.shared.types import Annotated
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+tracer = Tracer()
+logger = Logger()
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+class Todo(BaseModel):
+ userId: int
+ id_: Optional[int] = Field(alias="id", default=None)
+ title: str
+ completed: bool
+
+
+@app.get("/todos/")
+@tracer.capture_method
+def get_todo_by_id(todo_id: Annotated[int, Path(lt=999)]) -> Todo: # (1)!
+ todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}")
+ todo.raise_for_status()
+
+ return todo.json()
+
+
+@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
+@tracer.capture_lambda_handler
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/validating_payload_subset.json b/examples/event_handler_rest/src/validating_payload_subset.json
new file mode 100644
index 00000000000..b786e9287b8
--- /dev/null
+++ b/examples/event_handler_rest/src/validating_payload_subset.json
@@ -0,0 +1,36 @@
+{
+ "version": "1.0",
+ "body": "{ \"todo\": {\"title\": \"foo\", \"userId\": \"1\", \"completed\": false } }",
+ "resource": "/todos",
+ "path": "/todos",
+ "httpMethod": "POST",
+ "headers": {
+ "Origin": "https://aws.amazon.com"
+ },
+ "multiValueHeaders": {},
+ "queryStringParameters": {},
+ "multiValueQueryStringParameters": {},
+ "requestContext": {
+ "accountId": "123456789012",
+ "apiId": "id",
+ "authorizer": {
+ "claims": null,
+ "scopes": null
+ },
+ "domainName": "id.execute-api.us-east-1.amazonaws.com",
+ "domainPrefix": "id",
+ "extendedRequestId": "request-id",
+ "httpMethod": "POST",
+ "path": "/todos",
+ "protocol": "HTTP/1.1",
+ "requestId": "id=",
+ "requestTime": "04/Mar/2020:19:15:17 +0000",
+ "requestTimeEpoch": 1583349317135,
+ "resourceId": null,
+ "resourcePath": "/todos",
+ "stage": "$default"
+ },
+ "pathParameters": null,
+ "stageVariables": null,
+ "isBase64Encoded": false
+}
\ No newline at end of file
diff --git a/examples/event_handler_rest/src/validating_payload_subset.py b/examples/event_handler_rest/src/validating_payload_subset.py
new file mode 100644
index 00000000000..ac4ee603853
--- /dev/null
+++ b/examples/event_handler_rest/src/validating_payload_subset.py
@@ -0,0 +1,30 @@
+from typing import Optional
+
+import requests
+from pydantic import BaseModel, Field
+
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler.openapi.params import Body # (1)!
+from aws_lambda_powertools.shared.types import Annotated
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+class Todo(BaseModel):
+ userId: int
+ id_: Optional[int] = Field(alias="id", default=None)
+ title: str
+ completed: bool
+
+
+@app.post("/todos")
+def create_todo(todo: Annotated[Todo, Body(embed=True)]) -> int: # (2)!
+ response = requests.post("https://jsonplaceholder.typicode.com/todos", json=todo.dict(by_alias=True))
+ response.raise_for_status()
+
+ return response.json()["id"]
+
+
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/validating_payload_subset_output.json b/examples/event_handler_rest/src/validating_payload_subset_output.json
new file mode 100644
index 00000000000..754e3a6c128
--- /dev/null
+++ b/examples/event_handler_rest/src/validating_payload_subset_output.json
@@ -0,0 +1,10 @@
+{
+ "statusCode": 200,
+ "body": "2008822",
+ "isBase64Encoded": false,
+ "multiValueHeaders": {
+ "Content-Type": [
+ "application/json"
+ ]
+ }
+}
diff --git a/examples/event_handler_rest/src/validating_payloads.json b/examples/event_handler_rest/src/validating_payloads.json
new file mode 100644
index 00000000000..125405e0cf2
--- /dev/null
+++ b/examples/event_handler_rest/src/validating_payloads.json
@@ -0,0 +1,36 @@
+{
+ "version": "1.0",
+ "body": "{\"title\": \"foo\", \"userId\": \"1\", \"completed\": false}",
+ "resource": "/todos",
+ "path": "/todos",
+ "httpMethod": "POST",
+ "headers": {
+ "Origin": "https://aws.amazon.com"
+ },
+ "multiValueHeaders": {},
+ "queryStringParameters": {},
+ "multiValueQueryStringParameters": {},
+ "requestContext": {
+ "accountId": "123456789012",
+ "apiId": "id",
+ "authorizer": {
+ "claims": null,
+ "scopes": null
+ },
+ "domainName": "id.execute-api.us-east-1.amazonaws.com",
+ "domainPrefix": "id",
+ "extendedRequestId": "request-id",
+ "httpMethod": "POST",
+ "path": "/todos",
+ "protocol": "HTTP/1.1",
+ "requestId": "id=",
+ "requestTime": "04/Mar/2020:19:15:17 +0000",
+ "requestTimeEpoch": 1583349317135,
+ "resourceId": null,
+ "resourcePath": "/todos",
+ "stage": "$default"
+ },
+ "pathParameters": null,
+ "stageVariables": null,
+ "isBase64Encoded": false
+}
\ No newline at end of file
diff --git a/examples/event_handler_rest/src/validating_payloads.py b/examples/event_handler_rest/src/validating_payloads.py
new file mode 100644
index 00000000000..945cefd8089
--- /dev/null
+++ b/examples/event_handler_rest/src/validating_payloads.py
@@ -0,0 +1,43 @@
+from typing import List, Optional
+
+import requests
+from pydantic import BaseModel, Field
+
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.logging import correlation_paths
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+tracer = Tracer()
+logger = Logger()
+app = APIGatewayRestResolver(enable_validation=True) # (1)!
+
+
+class Todo(BaseModel): # (2)!
+ userId: int
+ id_: Optional[int] = Field(alias="id", default=None)
+ title: str
+ completed: bool
+
+
+@app.post("/todos")
+def create_todo(todo: Todo) -> str: # (3)!
+ response = requests.post("https://jsonplaceholder.typicode.com/todos", json=todo.dict(by_alias=True))
+ response.raise_for_status()
+
+ return response.json()["id"] # (4)!
+
+
+@app.get("/todos")
+@tracer.capture_method
+def get_todos() -> List[Todo]:
+ todo = requests.get("https://jsonplaceholder.typicode.com/todos")
+ todo.raise_for_status()
+
+ return todo.json() # (5)!
+
+
+@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
+@tracer.capture_lambda_handler
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/validating_payloads_output.json b/examples/event_handler_rest/src/validating_payloads_output.json
new file mode 100644
index 00000000000..9d72764c3c8
--- /dev/null
+++ b/examples/event_handler_rest/src/validating_payloads_output.json
@@ -0,0 +1,10 @@
+{
+ "statusCode": 200,
+ "body": "2008821",
+ "isBase64Encoded": false,
+ "multiValueHeaders": {
+ "Content-Type": [
+ "application/json"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/examples/event_handler_rest/src/validating_query_strings.py b/examples/event_handler_rest/src/validating_query_strings.py
new file mode 100644
index 00000000000..21d34dbd25a
--- /dev/null
+++ b/examples/event_handler_rest/src/validating_query_strings.py
@@ -0,0 +1,42 @@
+from typing import List, Optional
+
+import requests
+from pydantic import BaseModel, Field
+
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler.openapi.params import Query # (2)!
+from aws_lambda_powertools.logging import correlation_paths
+from aws_lambda_powertools.shared.types import Annotated # (1)!
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+tracer = Tracer()
+logger = Logger()
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+class Todo(BaseModel):
+ userId: int
+ id_: Optional[int] = Field(alias="id", default=None)
+ title: str
+ completed: bool
+
+
+@app.get("/todos")
+@tracer.capture_method
+def get_todos(completed: Annotated[Optional[str], Query(min_length=4)] = None) -> List[Todo]: # (3)!
+ url = "https://jsonplaceholder.typicode.com/todos"
+
+ if completed is not None:
+ url = f"{url}/?completed={completed}"
+
+ todo = requests.get(url)
+ todo.raise_for_status()
+
+ return todo.json()
+
+
+@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
+@tracer.capture_lambda_handler
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/logger/sam/template.yaml b/examples/logger/sam/template.yaml
index d5410ab9164..10358eec0e8 100644
--- a/examples/logger/sam/template.yaml
+++ b/examples/logger/sam/template.yaml
@@ -14,7 +14,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:48
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:49
Resources:
LoggerLambdaHandlerExample:
diff --git a/examples/metrics/sam/template.yaml b/examples/metrics/sam/template.yaml
index bcf2f805476..65a31816f73 100644
--- a/examples/metrics/sam/template.yaml
+++ b/examples/metrics/sam/template.yaml
@@ -15,7 +15,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:48
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:49
Resources:
CaptureLambdaHandlerExample:
diff --git a/examples/tracer/sam/template.yaml b/examples/tracer/sam/template.yaml
index c61b08dcb94..a3ab1418b63 100644
--- a/examples/tracer/sam/template.yaml
+++ b/examples/tracer/sam/template.yaml
@@ -13,7 +13,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:48
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:49
Resources:
CaptureLambdaHandlerExample:
diff --git a/layer/scripts/layer-balancer/go.mod b/layer/scripts/layer-balancer/go.mod
index d05fe79258c..93058aa1ac7 100644
--- a/layer/scripts/layer-balancer/go.mod
+++ b/layer/scripts/layer-balancer/go.mod
@@ -3,25 +3,25 @@ module layerbalancer
go 1.18
require (
- github.com/aws/aws-sdk-go-v2 v1.23.0
- github.com/aws/aws-sdk-go-v2/config v1.25.3
- github.com/aws/aws-sdk-go-v2/service/lambda v1.48.0
+ github.com/aws/aws-sdk-go-v2 v1.23.1
+ github.com/aws/aws-sdk-go-v2/config v1.25.5
+ github.com/aws/aws-sdk-go-v2/service/lambda v1.48.1
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/sync v0.5.0
)
require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1 // indirect
- github.com/aws/aws-sdk-go-v2/credentials v1.16.2 // indirect
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.3 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.3 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.16.4 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.3 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.17.2 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.0 // indirect
- github.com/aws/aws-sdk-go-v2/service/sts v1.25.3 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 // indirect
github.com/aws/smithy-go v1.17.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
)
diff --git a/layer/scripts/layer-balancer/go.sum b/layer/scripts/layer-balancer/go.sum
index 896fc480654..222e4acf1e5 100644
--- a/layer/scripts/layer-balancer/go.sum
+++ b/layer/scripts/layer-balancer/go.sum
@@ -1,31 +1,31 @@
-github.com/aws/aws-sdk-go-v2 v1.23.0 h1:PiHAzmiQQr6JULBUdvR8fKlA+UPKLT/8KbiqpFBWiAo=
-github.com/aws/aws-sdk-go-v2 v1.23.0/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA=
+github.com/aws/aws-sdk-go-v2 v1.23.1 h1:qXaFsOOMA+HsZtX8WoCa+gJnbyW7qyFFBlPqvTSzbaI=
+github.com/aws/aws-sdk-go-v2 v1.23.1/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1 h1:ZY3108YtBNq96jNZTICHxN1gSBSbnvIdYwwqnvCV4Mc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1/go.mod h1:t8PYl/6LzdAqsU4/9tz28V/kU+asFePvpOMkdul0gEQ=
-github.com/aws/aws-sdk-go-v2/config v1.25.3 h1:E4m9LbwJOoncDNt3e9MPLbz/saxWcGUlZVBydydD6+8=
-github.com/aws/aws-sdk-go-v2/config v1.25.3/go.mod h1:tAByZy03nH5jcq0vZmkcVoo6tRzRHEwSFx3QW4NmDw8=
-github.com/aws/aws-sdk-go-v2/credentials v1.16.2 h1:0sdZ5cwfOAipTzZ7eOL0gw4LAhk/RZnTa16cDqIt8tg=
-github.com/aws/aws-sdk-go-v2/credentials v1.16.2/go.mod h1:sDdvGhXrSVT5yzBDR7qXz+rhbpiMpUYfF3vJ01QSdrc=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4 h1:9wKDWEjwSnXZre0/O3+ZwbBl1SmlgWYBbrTV10X/H1s=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4/go.mod h1:t4i+yGHMCcUNIX1x7YVYa6bH/Do7civ5I6cG/6PMfyA=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.3 h1:DUwbD79T8gyQ23qVXFUthjzVMTviSHi3y4z58KvghhM=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.3/go.mod h1:7sGSz1JCKHWWBHq98m6sMtWQikmYPpxjqOydDemiVoM=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.3 h1:AplLJCtIaUZDCbr6+gLYdsYNxne4iuaboJhVt9d+WXI=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.3/go.mod h1:ify42Rb7nKeDDPkFjKn7q1bPscVPu/+gmHH8d2c+anU=
+github.com/aws/aws-sdk-go-v2/config v1.25.5 h1:UGKm9hpQS2hoK8CEJ1BzAW8NbUpvwDJJ4lyqXSzu8bk=
+github.com/aws/aws-sdk-go-v2/config v1.25.5/go.mod h1:Bf4gDvy4ZcFIK0rqDu1wp9wrubNba2DojiPB2rt6nvI=
+github.com/aws/aws-sdk-go-v2/credentials v1.16.4 h1:i7UQYYDSJrtc30RSwJwfBKwLFNnBTiICqAJ0pPdum8E=
+github.com/aws/aws-sdk-go-v2/credentials v1.16.4/go.mod h1:Kdh/okh+//vQ/AjEt81CjvkTo64+/zIE4OewP7RpfXk=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 h1:KehRNiVzIfAcj6gw98zotVbb/K67taJE0fkfgM6vzqU=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5/go.mod h1:VhnExhw6uXy9QzetvpXDolo1/hjhx4u9qukBGkuUwjs=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 h1:LAm3Ycm9HJfbSCd5I+wqC2S9Ej7FPrgr5CQoOljJZcE=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4/go.mod h1:xEhvbJcyUf/31yfGSQBe01fukXwXJ0gxDp7rLfymWE0=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 h1:4GV0kKZzUxiWxSVpn/9gwR0g21NF1Jsyduzo9rHgC/Q=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4/go.mod h1:dYvTNAggxDZy6y1AF7YDwXsPuHFy/VNEpEI/2dWK9IU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 h1:rpkF4n0CyFcrJUG/rNNohoTmhtWlFTRI4BsZOh9PvLs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1/go.mod h1:l9ymW25HOqymeU2m1gbUQ3rUIsTwKs8gYHXkqDQUhiI=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.3 h1:kJOolE8xBAD13xTCgOakByZkyP4D/owNmvEiioeUNAg=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.3/go.mod h1:Owv1I59vaghv1Ax8zz8ELY8DN7/Y0rGS+WWAmjgi950=
-github.com/aws/aws-sdk-go-v2/service/lambda v1.48.0 h1:Q1ajPX+B64b/OyxuaSDBjqOMmVrpNLhPfTFghpU783k=
-github.com/aws/aws-sdk-go-v2/service/lambda v1.48.0/go.mod h1:80TuTBIg7+OWOOA85SdMfvV393HGXPwqoepFTQn6/qA=
-github.com/aws/aws-sdk-go-v2/service/sso v1.17.2 h1:V47N5eKgVZoRSvx2+RQ0EpAEit/pqOhqeSQFiS4OFEQ=
-github.com/aws/aws-sdk-go-v2/service/sso v1.17.2/go.mod h1:/pE21vno3q1h4bbhUOEi+6Zu/aT26UK2WKkDXd+TssQ=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.0 h1:/XiEU7VIFcVWRDQLabyrSjBoKIm8UkYgsvWDuFW8Img=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.0/go.mod h1:dWqm5G767qwKPuayKfzm4rjzFmVjiBFbOJrpSPnAMDs=
-github.com/aws/aws-sdk-go-v2/service/sts v1.25.3 h1:M2w4kiMGJCCM6Ljmmx/l6mmpfa3gPJVpBencfnsgvqs=
-github.com/aws/aws-sdk-go-v2/service/sts v1.25.3/go.mod h1:4EqRHDCKP78hq3zOnmFXu5k0j4bXbRFfCh/zQ6KnEfQ=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 h1:rdovz3rEu0vZKbzoMYPTehp0E8veoE9AyfzqCr5Eeao=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4/go.mod h1:aYCGNjyUCUelhofxlZyj63srdxWUSsBSGg5l6MCuXuE=
+github.com/aws/aws-sdk-go-v2/service/lambda v1.48.1 h1:xVOzP4rFi0kMXUQozqInP+Yy6zldr8WTpHeVEqxMtOY=
+github.com/aws/aws-sdk-go-v2/service/lambda v1.48.1/go.mod h1:7dj5Kak6A6QOeZxUgIDUWVG5+7upeEBY1ivtFDRLxSQ=
+github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 h1:CdsSOGlFF3Pn+koXOIpTtvX7st0IuGsZ8kJqcWMlX54=
+github.com/aws/aws-sdk-go-v2/service/sso v1.17.3/go.mod h1:oA6VjNsLll2eVuUoF2D+CMyORgNzPEW/3PyUdq6WQjI=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 h1:cbRqFTVnJV+KRpwFl76GJdIZJKKCdTPnjUZ7uWh3pIU=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1/go.mod h1:hHL974p5auvXlZPIjJTblXJpbkfK4klBczlsEaMCGVY=
+github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 h1:yEvZ4neOQ/KpUqyR+X0ycUTW/kVRNR4nDZ38wStHGAA=
+github.com/aws/aws-sdk-go-v2/service/sts v1.25.4/go.mod h1:feTnm2Tk/pJxdX+eooEsxvlvTWBvDm6CasRZ+JOs2IY=
github.com/aws/smithy-go v1.17.0 h1:wWJD7LX6PBV6etBUwO0zElG0nWN9rUhp0WdYeHSHAaI=
github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
diff --git a/package-lock.json b/package-lock.json
index db1e807ca42..ff88df2208e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,13 +11,13 @@
"package-lock.json": "^1.0.0"
},
"devDependencies": {
- "aws-cdk": "^2.110.0"
+ "aws-cdk": "^2.110.1"
}
},
"node_modules/aws-cdk": {
- "version": "2.110.0",
- "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.110.0.tgz",
- "integrity": "sha512-ods6/Lh5hWv9qOMmifgg6ur/M6020Yi5mFXUolVSy/0gjzo9wFRcPAxKmQ3++Yz+rf5dadUZmmpc53evvUgR4A==",
+ "version": "2.110.1",
+ "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.110.1.tgz",
+ "integrity": "sha512-/V0FOgsvD/FkFANrYnSmyb+XK56tm2oE86pUCoEggQ2tka6Zm0z9blKZQV4euMErNSkWz4ReSAKenaqk86Fr5Q==",
"dev": true,
"bin": {
"cdk": "bin/cdk"
@@ -51,9 +51,9 @@
},
"dependencies": {
"aws-cdk": {
- "version": "2.110.0",
- "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.110.0.tgz",
- "integrity": "sha512-ods6/Lh5hWv9qOMmifgg6ur/M6020Yi5mFXUolVSy/0gjzo9wFRcPAxKmQ3++Yz+rf5dadUZmmpc53evvUgR4A==",
+ "version": "2.110.1",
+ "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.110.1.tgz",
+ "integrity": "sha512-/V0FOgsvD/FkFANrYnSmyb+XK56tm2oE86pUCoEggQ2tka6Zm0z9blKZQV4euMErNSkWz4ReSAKenaqk86Fr5Q==",
"dev": true,
"requires": {
"fsevents": "2.3.2"
diff --git a/package.json b/package.json
index 13537339cce..c50593c4870 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "aws-lambda-powertools-python-e2e",
"version": "1.0.0",
"devDependencies": {
- "aws-cdk": "^2.110.0"
+ "aws-cdk": "^2.110.1"
},
"dependencies": {
"package-lock.json": "^1.0.0"
diff --git a/poetry.lock b/poetry.lock
index eaa0fb7914b..7720087d2e6 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -149,13 +149,13 @@ typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "aws-cdk-lib"
-version = "2.110.0"
+version = "2.110.1"
description = "Version 2 of the AWS Cloud Development Kit library"
optional = false
python-versions = "~=3.7"
files = [
- {file = "aws-cdk-lib-2.110.0.tar.gz", hash = "sha256:2f6650e8d365fb2b143e65cf22d91de45c090636d8a2f1ac68efc302187780f7"},
- {file = "aws_cdk_lib-2.110.0-py3-none-any.whl", hash = "sha256:80a8eac6dcc2dd38496d9296efb1d90b45051dd1748743555bf69cfe83b1aa0a"},
+ {file = "aws-cdk-lib-2.110.1.tar.gz", hash = "sha256:f9780664b70e11aa886ef42fdb4e45dab180721e42eb8a4575617573a8e46ed0"},
+ {file = "aws_cdk_lib-2.110.1-py3-none-any.whl", hash = "sha256:63f234360832f08ae7a767fa1e3f6775ceeef0b8f9a75aa9ec7b79642c1fee21"},
]
[package.dependencies]
@@ -812,18 +812,18 @@ requests = ">=2.6.0"
[[package]]
name = "datadog-lambda"
-version = "4.82.0"
+version = "5.83.0"
description = "The Datadog AWS Lambda Library"
optional = false
python-versions = ">=3.7.0,<4"
files = [
- {file = "datadog_lambda-4.82.0-py3-none-any.whl", hash = "sha256:498fd675a3131edf268f006fabe8b9e3eb2e3d2f94a95ad72a26d5c52f5a6624"},
- {file = "datadog_lambda-4.82.0.tar.gz", hash = "sha256:59918dfe20645539558ce4b7b8afcee2895ca8bf2568e594d73d360e6ea871a6"},
+ {file = "datadog_lambda-5.83.0-py3-none-any.whl", hash = "sha256:87c9185c1f27b78eba9c053f434b4672199a5613d62ad37bd6c583a54c817513"},
+ {file = "datadog_lambda-5.83.0.tar.gz", hash = "sha256:59209f6cdbef4a9b167d8a832368ae2384134214d5d62f6be6b0078ce0d8fffa"},
]
[package.dependencies]
datadog = ">=0.41.0,<1.0.0"
-ddtrace = "<2.0.0"
+ddtrace = ">=2.3.0"
importlib_metadata = {version = "*", markers = "python_version < \"3.8\""}
typing_extensions = {version = ">=4.0,<5.0", markers = "python_version < \"3.8\""}
urllib3 = [
@@ -852,94 +852,86 @@ six = "*"
[[package]]
name = "ddtrace"
-version = "1.20.5"
+version = "2.3.1"
description = "Datadog APM client library"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-files = [
- {file = "ddtrace-1.20.5-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:8e848f4d4efd02f887633aa6eca284a820e42b316ebbbd9ed25599f777e8090f"},
- {file = "ddtrace-1.20.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:3aff8d055aa57de51a814dc86aaee5602b5ea665e7502d60b8467b07fb5018d2"},
- {file = "ddtrace-1.20.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ec89b97c50b2ccc27ecef9d9d084a412f174002d665140a5710928909a2d592e"},
- {file = "ddtrace-1.20.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:8216d010c7206544de46053638ec5a9ed1ac3a56908621a958aaa390d8cffb27"},
- {file = "ddtrace-1.20.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:3c16686ed141dc847ed8cd09528649cccd833d896b2d5878f8ee370f15de6de6"},
- {file = "ddtrace-1.20.5-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:834e49122cf1e75c299c0ac54886de7b776f4f292c2679d88c1ea31e866d8514"},
- {file = "ddtrace-1.20.5-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:4f28220ce9cbdd58d02a9851d41b7c2424270ffe3a08fd042299ffc3d92c96f5"},
- {file = "ddtrace-1.20.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7228037265efbd74656e65f8d3f17ea28854c280aa86db1a7adaf642d9ffaa63"},
- {file = "ddtrace-1.20.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f01a2a06974716fb516063369ab973fad38ffb33ca5ca15b3b7b128c06a2490"},
- {file = "ddtrace-1.20.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be2570b7348eb9b561fe148997af3c11a50904870503a3fe7c2b6348e87dac7f"},
- {file = "ddtrace-1.20.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6ca5b6ef30a300518e535a8291c9008d68fc1cde8fc4ed476af1b3c9377ab675"},
- {file = "ddtrace-1.20.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecd7e3a15e73c1f86cdb6e4c7874e99023f412ba9f63e1b342ecd593313fad2f"},
- {file = "ddtrace-1.20.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86a052d8fe5cb380702bc4ee49857c2f12b019c7a3f4c78f8b2dfd43c72668ca"},
- {file = "ddtrace-1.20.5-cp310-cp310-win32.whl", hash = "sha256:3fd4d252224a74ff72b27b8371a2ac9500dc0ba08d3e009c3052ea63ad514824"},
- {file = "ddtrace-1.20.5-cp310-cp310-win_amd64.whl", hash = "sha256:90a641673b29521ec73ce572896011653cd222152b92ec744b6f162b4ccdb27f"},
- {file = "ddtrace-1.20.5-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:345c7a2910309547fa9b53d4386912a4cbd9c24c0a9f0b771404c7c2b82f8764"},
- {file = "ddtrace-1.20.5-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:07f97e1d94e3fe3f79d9656baa7283441c5d155adb2a0ca5d2defbdfbb12a28c"},
- {file = "ddtrace-1.20.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3718c0804cdd2def4b80f9bd51c55b2f44dca914086f647a21356980022668"},
- {file = "ddtrace-1.20.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0acd401c52bf617474c4cac8bca0aa8cf6253d82b31c4e892b30294b397f10d"},
- {file = "ddtrace-1.20.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b36954f49c82e9e2e42615a5b70ba04dfd12d71011f4c9931effb6957c63fbd"},
- {file = "ddtrace-1.20.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:51523c55618a6b26ecdaab1c4540e3679e96723d0f883e1df7ffaa5010231cb2"},
- {file = "ddtrace-1.20.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:61d538c2c6e7a460cbdc19735093c993a62abc3381def43d5940a8facc4a280f"},
- {file = "ddtrace-1.20.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:59635b9fef2e02fefd46876f93e63eaa857503eb64aced6bcffaa81a199cfa4e"},
- {file = "ddtrace-1.20.5-cp311-cp311-win32.whl", hash = "sha256:7c4b6b121ec72736c9004c83d40d4d64155b935fb1c033390370de78af001fdb"},
- {file = "ddtrace-1.20.5-cp311-cp311-win_amd64.whl", hash = "sha256:cdaff774e88feb6d0229609ecdbaecbf50a220428bb29f863734b167d910a658"},
- {file = "ddtrace-1.20.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:ec41eff870ceba145306daab76f003a6977986dd65074b9a74f50cd0a985e431"},
- {file = "ddtrace-1.20.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:89dfaf4133ad8dcb6c63db843624e10175e9813a1f867e24d1c2a8e724088119"},
- {file = "ddtrace-1.20.5-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:7b625af8eb772bc242b850a011ba9294721f85d3f965cecdcc430fb73da1a11a"},
- {file = "ddtrace-1.20.5-cp35-cp35m-win32.whl", hash = "sha256:8cd282d38a394417e65b360ff9fb1d982bb3270ab18239f3ad3c70a1ec9cb9a3"},
- {file = "ddtrace-1.20.5-cp35-cp35m-win_amd64.whl", hash = "sha256:2d1086c5178be1763af7f30a18dd5bf6b77de855bbaa1c6c1d10e98fa7ad0931"},
- {file = "ddtrace-1.20.5-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:af4eac36813863e08cb71f72f86e9c5a060841ce66acdf2a090d831bf6886895"},
- {file = "ddtrace-1.20.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a2a15a3672331ffbdf32f8b8a387979b070510fbc2bc90e2c33c4669cd846bc"},
- {file = "ddtrace-1.20.5-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9f8071a9b4ea0be1fe62e39a68e95592742c56e336fbd0e4e99d18c39eca27"},
- {file = "ddtrace-1.20.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa49ab583816163b4cc133da234c5c2bc75d37cf459004c8319dc4026ac96c9b"},
- {file = "ddtrace-1.20.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a37cc5871b0dc6a18f19228280927d6b22d04fb30522eebcc48371481e447709"},
- {file = "ddtrace-1.20.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e1f4ad4eab735f092d9a23b80d9ffd7d33f7bb8fecba001f222155a7dc03d6ed"},
- {file = "ddtrace-1.20.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:253c75018b412a6f90c26ddfd2f265fc950751dbf76cd8529f8537b4fa4c031d"},
- {file = "ddtrace-1.20.5-cp36-cp36m-win32.whl", hash = "sha256:a61ec2a2874ab9f7228b3cf430a7048803a1ce2db22fde021c1f61ed38a1e5b4"},
- {file = "ddtrace-1.20.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2c6ff4e1118d4530b80fbf9432414e0fad38ef33bc64e39a00648b396c4c44a7"},
- {file = "ddtrace-1.20.5-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:f0eaf682acaed72427c9f05831e44050e6145ca0b227fc25a123c22d1c7d4c89"},
- {file = "ddtrace-1.20.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d952483633e637c9bb6364baf0dd3b805872b121746a7243d9de647c6f2c9417"},
- {file = "ddtrace-1.20.5-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac292a54a8b3b00d424e8c6451d7c21d247ff7e223ab6280ee541319428cd41a"},
- {file = "ddtrace-1.20.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07eae27732d63ac4e7370b575e59ff4f8c1396f8e40992feeca45425b4fce4cc"},
- {file = "ddtrace-1.20.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5678feec55fc77771eae9ff80a34037af14a140b4f6bf68a454cf86204db6401"},
- {file = "ddtrace-1.20.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1b23c154a3ca0f60b9324be0886655e05db40ea98cd33cb8111091ba9adea8f2"},
- {file = "ddtrace-1.20.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d73cb8a34112d80caadd6b088d02f9c6070264abc71733ecb537b2f9a97d6f10"},
- {file = "ddtrace-1.20.5-cp37-cp37m-win32.whl", hash = "sha256:ca8eabcd59c965086de4067475ba8e70895f81f601ce9660c5080cefd66d655c"},
- {file = "ddtrace-1.20.5-cp37-cp37m-win_amd64.whl", hash = "sha256:7ad29dcea031b6ba03f77de4ad6f85d749294fd9e8ba8af871a73811c47d1e5a"},
- {file = "ddtrace-1.20.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:67fea1d08dbca04eff521cc013adba1dd0d80c6e5c75a02cb4a7c42c0e973993"},
- {file = "ddtrace-1.20.5-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:79536cfa68338db9c95e728585e1f5f059e321cfcf145fce6b14e8cd1d12892c"},
- {file = "ddtrace-1.20.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c746d3546f69b7718fb61508826552a197fc981bb26c66408e1a20f7ac0952a3"},
- {file = "ddtrace-1.20.5-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fb3aaf24760d8e4d23d01e497d15ac1b52be202e82a63140afd0c2b799e8571"},
- {file = "ddtrace-1.20.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6602c52359dbf009ef762c4110ee1ca7dfb9a163aa7bc43515c920a3ddf545f"},
- {file = "ddtrace-1.20.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d1cc510f3a4be3df3282415b045cd85dc83a3a2051d219a24546dfb9fdcb39d"},
- {file = "ddtrace-1.20.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:774d48d7e050fc4fec35317348f04326d8f781348e11ff937e739fd7ad555c6b"},
- {file = "ddtrace-1.20.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7094dd13bf2e17dac2f555a2a0ec85f89e330e6d9327122a6e16fac2b6765f34"},
- {file = "ddtrace-1.20.5-cp38-cp38-win32.whl", hash = "sha256:264cadc7caf89f45d225b1323a09c73a2706f38b754266660fc93a19b2ccb1d7"},
- {file = "ddtrace-1.20.5-cp38-cp38-win_amd64.whl", hash = "sha256:815a14133c25ed9a6deb0df043c7095107a2a900faaf458af153352bb329ccd5"},
- {file = "ddtrace-1.20.5-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:3faff82c1c0b809faa6b79ecb37a7402ca2e452241607db3ca4f8d79e30d8695"},
- {file = "ddtrace-1.20.5-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:6a94fc30efcf753cb40f37a1c8d440139ac63db6cc346ecaf3a409f12410297b"},
- {file = "ddtrace-1.20.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a43168df3b30fa228720d7c900a51a4fdaae145efaba6e6f37b678e2714f846"},
- {file = "ddtrace-1.20.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dad47a4058ed2b703843f0615411e5bd8edcaad54b0d8bc3de811252d8dd242e"},
- {file = "ddtrace-1.20.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a415b779b34d4af9880665b25ffcab8463c2e75dc82eed0228756a9fa93ed16"},
- {file = "ddtrace-1.20.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6750dcf7f84895e136452a5c03ff66d4a9e022994b961b1cf7a983fcfc65afba"},
- {file = "ddtrace-1.20.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:76e8f5f07784c8ec26a7bfd5150c359429a3423179776e075735a2e685148095"},
- {file = "ddtrace-1.20.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c4e56bd317578d34663b399b213d8b22801267b8d4faf7a1b645eb1918feaf1b"},
- {file = "ddtrace-1.20.5-cp39-cp39-win32.whl", hash = "sha256:247d8206686ef6c0b1263ce63e37a99eee5fee20ad1456bcb78edcb77519c6f5"},
- {file = "ddtrace-1.20.5-cp39-cp39-win_amd64.whl", hash = "sha256:bafc1c668b8f32a8e980f75c121c33464188270c01ef87430baa489e55fb1590"},
- {file = "ddtrace-1.20.5.tar.gz", hash = "sha256:3a15940d03a4c35d253d93b6c3acf82e0cd70d72f4ec9d8c3d0ca0d0c398dda3"},
-]
-
-[package.dependencies]
-attrs = {version = ">=20", markers = "python_version > \"2.7\""}
+python-versions = ">=3.7"
+files = [
+ {file = "ddtrace-2.3.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:556a046413024cf53ebb0256bbf957692a5e417599e04dac5793e659d08c398c"},
+ {file = "ddtrace-2.3.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:6066f1deddb454b8e098e5a0eb53ab36d81344209fdf6bec94767358da190294"},
+ {file = "ddtrace-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb2b950901845b966a7805ff49a9ad58dcd5e9c27b5b804079977a1309c5b4fb"},
+ {file = "ddtrace-2.3.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05b0da47bc98a9802faa2557e83c096868c4ef249c3d9a43f8e5daf91d1c8e4f"},
+ {file = "ddtrace-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0183c5178112604eb012653fd17d0947e6e2f17325f93b1e32cc6af05ceffd0"},
+ {file = "ddtrace-2.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:462eb671cd78780af6e42b43f2bc451537a0d283db054c175348e9b3a1fcaff4"},
+ {file = "ddtrace-2.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b5284786a0912a9739665a33760f561423524e2d250c0b0bb2dedf6edba2da5"},
+ {file = "ddtrace-2.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dbfb1ade5725a63f21945ab8234e64e46645e98a7deb4342eddf6e86d0f9145c"},
+ {file = "ddtrace-2.3.1-cp310-cp310-win32.whl", hash = "sha256:1f51732c4181e5b671a5ae3c6c786ce3b9fd2abacad2d4249d53a55564906902"},
+ {file = "ddtrace-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f0ae5814fbb51b4aba4d4f4b5c1fd2110790b04d4141cf4a03291566d1d5b0f"},
+ {file = "ddtrace-2.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:24f4df55fd952182efe6815748db4675540f6fb674d9838dfa680dec1fdd176f"},
+ {file = "ddtrace-2.3.1-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:04b4e476f78389021b50b3ae5c4d494bbbd033a300e93253fe1f873a67611436"},
+ {file = "ddtrace-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:711978dd14c0aca7eaf90587b8608c891b82e1767fc6f2be7d82b67d56c8d580"},
+ {file = "ddtrace-2.3.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfa6b1b2698029b7b1f8cc351869397c33bff996159660a00ca254d9fcc5b78d"},
+ {file = "ddtrace-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd7295921009ccc61f5325cc3d30fc6182396fc8e598975b372bdf94fd16077"},
+ {file = "ddtrace-2.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:94aa6a2e16d05cbb2d7a9a7553ca9b638e5b200e0d80fd027179e6af0faf59a2"},
+ {file = "ddtrace-2.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:62f67040ef16149a46c8506d92a2824d7ded39427a51947a3651d572bb7a379f"},
+ {file = "ddtrace-2.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02622c4b8d5497f6367d9ccad38ac8c59d46fc3373034be114474fb01b1a28e6"},
+ {file = "ddtrace-2.3.1-cp311-cp311-win32.whl", hash = "sha256:1d13ec5393802a619f922fb37a9f534911f44554bd0434dfd2d8db4e8897649e"},
+ {file = "ddtrace-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:36b3427136f61d499f3fd307f97ae168a4d2728887e1922204e509a5aa72a4a3"},
+ {file = "ddtrace-2.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac47d141e03c8bea3953fc5f51ac284de9ff4e6325faf2554b003ac906bc4da8"},
+ {file = "ddtrace-2.3.1-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:dd23e10b4cac1cf26e64d4d1ec1d6e173e609a207f5520469326f5cff6c7e462"},
+ {file = "ddtrace-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a31cddf750d7a28c886c194624c6be5a4475de064489002df898731f27f3d16"},
+ {file = "ddtrace-2.3.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dedd8097e58519f47f8908fe684f37c8f9722ce4b0614de78d9f39b83621dc7"},
+ {file = "ddtrace-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:367aed800b78fb4d2af332c44d07d7126b1dbf758af422299f9a177811ec723d"},
+ {file = "ddtrace-2.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca4dea67facdeba44040d9af8eeff96fb9a35a2b1cff93255e33a4d7250881b9"},
+ {file = "ddtrace-2.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a661e133d451416741c6c2ad96baa417a1267204975bfb0d247cab748ecc3ed1"},
+ {file = "ddtrace-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:556f60d6c9bbfc2da6d7f6751625fa3ae597c26bb8bbe74953db0d2d74f93b04"},
+ {file = "ddtrace-2.3.1-cp312-cp312-win32.whl", hash = "sha256:261e20b9e9a363ec2dc728f8a009a2b1d3c9de4fbe07438b5600902a285bb179"},
+ {file = "ddtrace-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:119be400024efff2f0eb66216b2aa3d2a700cd9b4a07605f7f9c94eb5e4b4cb5"},
+ {file = "ddtrace-2.3.1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:a66d0e0cccfa2fb207fc9a4d9ca6ab235a768f130129d6bb1dd256b7b3d34305"},
+ {file = "ddtrace-2.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c465e43b96380f09e1e8f2d0f9cb3b78b4ef2bb211f25b57c925bb79f53cb00c"},
+ {file = "ddtrace-2.3.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3eaaf8c5b63e07533822425b3402552c75adf091a1f0a6bf949725fa610c779"},
+ {file = "ddtrace-2.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:600551ecd232df060203714dc1acba4809e9194fc91a7c638b68c548e92af171"},
+ {file = "ddtrace-2.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:837232d708956a5d595a3618641c188a5844d663e0f77b1461f20c83f74a21c0"},
+ {file = "ddtrace-2.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ddf3043581e2424fc3d4271ee00a038651a4ec9d2610eeaa2d6645095c9f4960"},
+ {file = "ddtrace-2.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:63c6b28096e273431da923a8dfc0f54f7d472c3c78f0a5c4c99ed7e210b9c855"},
+ {file = "ddtrace-2.3.1-cp37-cp37m-win32.whl", hash = "sha256:8b09a42cc975f798bfda9b8d8bf5c8c813022bfcf48b9e0e5e90caf4cf33ee8f"},
+ {file = "ddtrace-2.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:66b49153c85423d5e99b1f364cc3b4a3ffedf35be0f3eb840f3bacd7c58100e8"},
+ {file = "ddtrace-2.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:81f0bd1d50c8fc7d8a96e38f746ca4421fa3b52991f0df44e5e9faeb5a934c2b"},
+ {file = "ddtrace-2.3.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37d600d582a5046f82cf77ae9247cf15cf62cf23c15739c5f23c30db2aa092c9"},
+ {file = "ddtrace-2.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60a62cfa22695cb1392c617910fb389c7240fa9dae0b5792bd87ff3ae82d2c45"},
+ {file = "ddtrace-2.3.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bdf55fa4a842f9786ca30434b31bf6f877e95af86b6fb7a5a540ce592f566b7"},
+ {file = "ddtrace-2.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63032c6a76173cab03c021e65c1997a12c0c571263caf00ec18b82c2293c49be"},
+ {file = "ddtrace-2.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:516b830e52bc8ac2988f11a06a6c6a5296f73b119e99e8ee55a34e531389acea"},
+ {file = "ddtrace-2.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:86e7764759043439c3f672f998f60bb9118fc4a6d7f603c762b125471b17f549"},
+ {file = "ddtrace-2.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:87ae203dd8fa3e04f8855786ae4b2f103bc66c9f2368ee2b4e620bccdde9b34d"},
+ {file = "ddtrace-2.3.1-cp38-cp38-win32.whl", hash = "sha256:f42fa2fa6f2cd9e3673a3bd7469439f5bea0ee86456706db1b50dc20b10682a6"},
+ {file = "ddtrace-2.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:2a3ad8e53c45c3329f939fe921714dfe76f5737e48f5b37a5422b1573a20ce44"},
+ {file = "ddtrace-2.3.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:5adff6a5d60239e64062ad5efb72631c47c7fb8310ebea6d817f0208a7585074"},
+ {file = "ddtrace-2.3.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:84012bc7d27dd3c4cd591bbaf0a0cc0413ebc6c838637ca5a76bacb354e2518f"},
+ {file = "ddtrace-2.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc2596b26701c9e3a362195f79ddcf54b491a8ea13277ed16697da9ad943646"},
+ {file = "ddtrace-2.3.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:986113f7eb4d8a8e87216b55e6cc40b578f84a5730241822af3f34cc61e42710"},
+ {file = "ddtrace-2.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd0cdbc6d81e556b6af0875b0bb2ac77d3cf0a0c5da8faa014da1936e1e0adc2"},
+ {file = "ddtrace-2.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e8eb17ef8ca2fc9464216290969cff3bbf8df00860ebb219328804125b43bd1"},
+ {file = "ddtrace-2.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fc21e46c5e9d077022b7634ae247d15d2318cbb347f6756607dfd64ff5941797"},
+ {file = "ddtrace-2.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:51bf7e3e5c80ef0daadd22c26e7c24c90fc4b4a7662dec1a3d9d8e0db68f3c09"},
+ {file = "ddtrace-2.3.1-cp39-cp39-win32.whl", hash = "sha256:2a5f040c0eb101f82a9cd8b8b0279e8583bb0a62fd39b879197d53b71a5d6dbe"},
+ {file = "ddtrace-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:ff708683becb18771cb31ae5fb5d1430ac5031a082106e0dabac46a1fd6f832e"},
+ {file = "ddtrace-2.3.1.tar.gz", hash = "sha256:273a0e98f93e7231708b30067768d80df9bc93a505de93500f30c6da24b70a7b"},
+]
+
+[package.dependencies]
+attrs = ">=20"
bytecode = [
{version = ">=0.13.0,<0.14.0", markers = "python_version == \"3.7\""},
{version = "*", markers = "python_version >= \"3.8\""},
]
-cattrs = {version = "*", markers = "python_version >= \"3.7\""}
+cattrs = "*"
ddsketch = ">=2.0.1"
envier = "*"
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
-opentelemetry-api = {version = ">=1", markers = "python_version >= \"3.7\""}
-protobuf = {version = ">=3", markers = "python_version >= \"3.7\""}
+opentelemetry-api = ">=1"
+protobuf = ">=3"
+setuptools = {version = "*", markers = "python_version >= \"3.12\""}
six = ">=1.12.0"
typing-extensions = "*"
xmltodict = ">=0.12"
@@ -1926,13 +1918,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-s3"
-version = "1.29.3"
-description = "Type annotations for boto3.S3 1.29.3 service generated with mypy-boto3-builder 7.20.3"
+version = "1.29.5"
+description = "Type annotations for boto3.S3 1.29.5 service generated with mypy-boto3-builder 7.20.3"
optional = false
python-versions = ">=3.7"
files = [
- {file = "mypy-boto3-s3-1.29.3.tar.gz", hash = "sha256:683c427ed722cee649443afb1dc65fc00871dac0812caa9d1947d0e3d8a03a37"},
- {file = "mypy_boto3_s3-1.29.3-py3-none-any.whl", hash = "sha256:d951dd748d2717de0a9b642231fe5069e503093ad6cfb7fa5976037e8c0e95de"},
+ {file = "mypy-boto3-s3-1.29.5.tar.gz", hash = "sha256:82c9df70b6cfa5e1c3e208a63aaa6edda4fc80696c8718fda4e6ed5bb6501ad3"},
+ {file = "mypy_boto3_s3-1.29.5-py3-none-any.whl", hash = "sha256:ce727a57dc1619bad0d4527ec6d74fecf3d2d05f6fef99e884e4a9e7485a1a18"},
]
[package.dependencies]
@@ -2428,13 +2420,13 @@ pytest = ">=3.6.3"
[[package]]
name = "pytest-xdist"
-version = "3.4.0"
+version = "3.5.0"
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pytest-xdist-3.4.0.tar.gz", hash = "sha256:3a94a931dd9e268e0b871a877d09fe2efb6175c2c23d60d56a6001359002b832"},
- {file = "pytest_xdist-3.4.0-py3-none-any.whl", hash = "sha256:e513118bf787677a427e025606f55e95937565e06dfaac8d87f55301e57ae607"},
+ {file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"},
+ {file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"},
]
[package.dependencies]
@@ -2772,13 +2764,13 @@ pbr = "*"
[[package]]
name = "sentry-sdk"
-version = "1.35.0"
+version = "1.36.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = "*"
files = [
- {file = "sentry-sdk-1.35.0.tar.gz", hash = "sha256:04e392db9a0d59bd49a51b9e3a92410ac5867556820465057c2ef89a38e953e9"},
- {file = "sentry_sdk-1.35.0-py2.py3-none-any.whl", hash = "sha256:a7865952701e46d38b41315c16c075367675c48d049b90a4cc2e41991ebc7efa"},
+ {file = "sentry-sdk-1.36.0.tar.gz", hash = "sha256:f32dd16547f2f45e1c71a96fd4a48925e629541f7ddfe3d5d25ef7d5e94eb3c8"},
+ {file = "sentry_sdk-1.36.0-py2.py3-none-any.whl", hash = "sha256:25d574f94fdf72199e331c2401fdac60d01b5be8f32822174c51c3ff0fc2f8cb"},
]
[package.dependencies]
@@ -2815,6 +2807,22 @@ starlette = ["starlette (>=0.19.1)"]
starlite = ["starlite (>=1.48)"]
tornado = ["tornado (>=5)"]
+[[package]]
+name = "setuptools"
+version = "69.0.2"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"},
+ {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+
[[package]]
name = "six"
version = "1.16.0"
@@ -3224,4 +3232,4 @@ validation = ["fastjsonschema"]
[metadata]
lock-version = "2.0"
python-versions = "^3.7.4"
-content-hash = "21c7697a42537357d74b97fcec11754de4defa04a296362dabc226078869f454"
+content-hash = "dbb0e0d3c1ea94802effb1b24095eef86e300a0c28d139818985b38799fd70f9"
diff --git a/pyproject.toml b/pyproject.toml
index 9f9162acb43..eafb91f6830 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "aws_lambda_powertools"
-version = "2.27.0"
+version = "2.28.0"
description = "Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless best practices and increase developer velocity."
authors = ["Amazon Web Services"]
include = ["aws_lambda_powertools/py.typed", "THIRD-PARTY-LICENSES"]
@@ -44,7 +44,7 @@ fastjsonschema = { version = "^2.14.5", optional = true }
pydantic = { version = "^1.8.2", optional = true }
boto3 = { version = "^1.20.32", optional = true }
typing-extensions = "^4.6.2"
-datadog-lambda = { version = "^4.77.0", optional = true }
+datadog-lambda = { version = ">=4.77,<6.0", optional = true }
aws-encryption-sdk = { version = "^3.1.1", optional = true }
[tool.poetry.dev-dependencies]
@@ -62,8 +62,8 @@ radon = "^6.0.1"
xenon = "^0.9.1"
mkdocs-git-revision-date-plugin = "^0.3.2"
mike = "^1.1.2"
-pytest-xdist = "^3.4.0"
-aws-cdk-lib = "^2.110.0"
+pytest-xdist = "^3.5.0"
+aws-cdk-lib = "^2.110.1"
"aws-cdk.aws-apigatewayv2-alpha" = "^2.38.1-alpha.0"
"aws-cdk.aws-apigatewayv2-integrations-alpha" = "^2.38.1-alpha.0"
"aws-cdk.aws-apigatewayv2-authorizers-alpha" = "^2.38.1-alpha.0"
@@ -76,7 +76,7 @@ mypy-boto3-lambda = "^1.29.2"
mypy-boto3-logs = "^1.29.0"
mypy-boto3-secretsmanager = "^1.29.0"
mypy-boto3-ssm = "^1.29.2"
-mypy-boto3-s3 = "^1.29.3"
+mypy-boto3-s3 = "^1.29.5"
mypy-boto3-xray = "^1.29.0"
types-requests = "^2.31.0"
typing-extensions = "^4.6.2"
@@ -88,7 +88,7 @@ ijson = "^3.2.2"
typed-ast = { version = "^1.5.5", python = "< 3.8"}
hvac = "^1.2.1"
aws-requests-auth = "^0.4.3"
-datadog-lambda = "^4.82.0"
+datadog-lambda = "^5.83.0"
[tool.poetry.extras]
parser = ["pydantic"]
diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py
index 8ad5ac35b18..d4c88b541aa 100644
--- a/tests/functional/event_handler/test_api_gateway.py
+++ b/tests/functional/event_handler/test_api_gateway.py
@@ -30,6 +30,7 @@
ServiceError,
UnauthorizedError,
)
+from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
from aws_lambda_powertools.shared import constants
from aws_lambda_powertools.shared.cookies import Cookie
from aws_lambda_powertools.shared.json_encoder import Encoder
@@ -379,7 +380,7 @@ def handler(event, context):
# WHEN calling the event handler
result = handler(mock_event, None)
- # THEN then the response is not compressed
+ # THEN the response is not compressed
assert result["isBase64Encoded"] is False
assert result["body"] == expected_value
assert result["multiValueHeaders"].get("Content-Encoding") is None
@@ -1458,6 +1459,51 @@ def get_lambda() -> Response:
assert result["body"] == "Foo!"
+def test_exception_handler_with_data_validation():
+ # GIVEN a resolver with an exception handler defined for RequestValidationError
+ app = ApiGatewayResolver(enable_validation=True)
+
+ @app.exception_handler(RequestValidationError)
+ def handle_validation_error(ex: RequestValidationError):
+ print(f"request path is '{app.current_event.path}'")
+ return Response(
+ status_code=422,
+ content_type=content_types.TEXT_PLAIN,
+ body=f"Invalid data. Number of errors: {len(ex.errors())}",
+ )
+
+ @app.get("/my/path")
+ def get_lambda(param: int):
+ ...
+
+ # WHEN calling the event handler
+ # AND a RequestValidationError is raised
+ result = app(LOAD_GW_EVENT, {})
+
+ # THEN call the exception_handler
+ assert result["statusCode"] == 422
+ assert result["multiValueHeaders"]["Content-Type"] == [content_types.TEXT_PLAIN]
+ assert result["body"] == "Invalid data. Number of errors: 1"
+
+
+def test_data_validation_error():
+ # GIVEN a resolver without an exception handler
+ app = ApiGatewayResolver(enable_validation=True)
+
+ @app.get("/my/path")
+ def get_lambda(param: int):
+ ...
+
+ # WHEN calling the event handler
+ # AND a RequestValidationError is raised
+ result = app(LOAD_GW_EVENT, {})
+
+ # THEN call the exception_handler
+ assert result["statusCode"] == 422
+ assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
+ assert "missing" in result["body"]
+
+
def test_exception_handler_service_error():
# GIVEN
app = ApiGatewayResolver()
diff --git a/tests/functional/event_handler/test_bedrock_agent.py b/tests/functional/event_handler/test_bedrock_agent.py
index f112acf0463..266edd10de0 100644
--- a/tests/functional/event_handler/test_bedrock_agent.py
+++ b/tests/functional/event_handler/test_bedrock_agent.py
@@ -2,7 +2,7 @@
from typing import Any, Dict
from aws_lambda_powertools.event_handler import BedrockAgentResolver, Response, content_types
-from aws_lambda_powertools.event_handler.openapi.types import PYDANTIC_V2
+from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2
from aws_lambda_powertools.utilities.data_classes import BedrockAgentEvent
from tests.functional.utils import load_event
@@ -31,7 +31,7 @@ def claims() -> Dict[str, Any]:
assert result["response"]["httpStatusCode"] == 200
body = result["response"]["responseBody"]["application/json"]["body"]
- assert body == json.dumps({"output": claims_response})
+ assert json.loads(body) == {"output": claims_response}
def test_bedrock_agent_with_path_params():
@@ -79,7 +79,7 @@ def claims():
assert result["response"]["httpStatusCode"] == 200
body = result["response"]["responseBody"]["application/json"]["body"]
- assert body == json.dumps(output)
+ assert json.loads(body) == output
def test_bedrock_agent_event_with_no_matches():
@@ -121,11 +121,11 @@ def claims() -> Dict[str, Any]:
assert result["response"]["httpMethod"] == "GET"
assert result["response"]["httpStatusCode"] == 422
- body = result["response"]["responseBody"]["application/json"]["body"]
+ body = json.loads(result["response"]["responseBody"]["application/json"]["body"])
if PYDANTIC_V2:
- assert "should be a valid dictionary" in body
+ assert body["detail"][0]["type"] == "dict_type"
else:
- assert "value is not a valid dict" in body
+ assert body["detail"][0]["type"] == "type_error.dict"
def test_bedrock_agent_event_with_exception():
diff --git a/tests/functional/event_handler/test_openapi_params.py b/tests/functional/event_handler/test_openapi_params.py
index ec31bb14236..9209cb9decd 100644
--- a/tests/functional/event_handler/test_openapi_params.py
+++ b/tests/functional/event_handler/test_openapi_params.py
@@ -4,7 +4,7 @@
from pydantic import BaseModel
-from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver, Response
from aws_lambda_powertools.event_handler.openapi.models import (
Example,
Parameter,
@@ -70,13 +70,13 @@ def handler(user_id: str, include_extra: bool = False):
assert schema.info.version == "0.2.2"
assert len(schema.paths.keys()) == 1
- assert "/users/" in schema.paths
+ assert "/users/{user_id}" in schema.paths
- path = schema.paths["/users/"]
+ path = schema.paths["/users/{user_id}"]
assert path.get
get = path.get
- assert get.summary == "GET /users/"
+ assert get.summary == "GET /users/{user_id}"
assert get.operationId == "handler_users__user_id__get"
assert len(get.parameters) == 2
@@ -153,6 +153,24 @@ def handler() -> str:
assert response.schema_.type == "string"
+def test_openapi_with_response_returns():
+ app = APIGatewayRestResolver()
+
+ @app.get("/")
+ def handler() -> Response[Annotated[str, Body(title="Response title")]]:
+ return Response(body="Hello, world", status_code=200)
+
+ schema = app.get_openapi_schema()
+ assert len(schema.paths.keys()) == 1
+
+ get = schema.paths["/"].get
+ assert get.parameters is None
+
+ response = get.responses[200].content[JSON_CONTENT_TYPE]
+ assert response.schema_.title == "Response title"
+ assert response.schema_.type == "string"
+
+
def test_openapi_with_omitted_param():
app = APIGatewayRestResolver()
diff --git a/tests/functional/event_handler/test_openapi_validation_middleware.py b/tests/functional/event_handler/test_openapi_validation_middleware.py
index 2c86ec0baa6..f558bd23ced 100644
--- a/tests/functional/event_handler/test_openapi_validation_middleware.py
+++ b/tests/functional/event_handler/test_openapi_validation_middleware.py
@@ -4,10 +4,9 @@
from pathlib import PurePath
from typing import List, Tuple
-import pytest
from pydantic import BaseModel
-from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response
from aws_lambda_powertools.event_handler.openapi.params import Body
from aws_lambda_powertools.shared.types import Annotated
from tests.functional.utils import load_event
@@ -15,11 +14,6 @@
LOAD_GW_EVENT = load_event("apiGatewayProxyEvent.json")
-def test_validate_with_customn_serializer():
- with pytest.raises(ValueError):
- APIGatewayRestResolver(enable_validation=True, serializer=json.dumps)
-
-
def test_validate_scalars():
# GIVEN an APIGatewayRestResolver with validation enabled
app = APIGatewayRestResolver(enable_validation=True)
@@ -128,7 +122,7 @@ def handler() -> List[int]:
# THEN the body must be [123, 234]
result = app(LOAD_GW_EVENT, {})
assert result["statusCode"] == 200
- assert result["body"] == "[123, 234]"
+ assert json.loads(result["body"]) == [123, 234]
def test_validate_return_tuple():
@@ -148,7 +142,7 @@ def handler() -> Tuple:
# THEN the body must be a tuple
result = app(LOAD_GW_EVENT, {})
assert result["statusCode"] == 200
- assert result["body"] == "[1, 2, 3]"
+ assert json.loads(result["body"]) == [1, 2, 3]
def test_validate_return_purepath():
@@ -169,7 +163,7 @@ def handler() -> str:
# THEN the body must be a string
result = app(LOAD_GW_EVENT, {})
assert result["statusCode"] == 200
- assert result["body"] == json.dumps(sample_path.as_posix())
+ assert result["body"] == sample_path.as_posix()
def test_validate_return_enum():
@@ -190,7 +184,7 @@ def handler() -> Model:
# THEN the body must be a string
result = app(LOAD_GW_EVENT, {})
assert result["statusCode"] == 200
- assert result["body"] == '"powertools"'
+ assert result["body"] == "powertools"
def test_validate_return_dataclass():
@@ -336,3 +330,51 @@ def handler(user: Annotated[Model, Body(embed=True)]) -> Model:
LOAD_GW_EVENT["body"] = json.dumps({"user": {"name": "John", "age": 30}})
result = app(LOAD_GW_EVENT, {})
assert result["statusCode"] == 200
+
+
+def test_validate_response_return():
+ # GIVEN an APIGatewayRestResolver with validation enabled
+ app = APIGatewayRestResolver(enable_validation=True)
+
+ class Model(BaseModel):
+ name: str
+ age: int
+
+ # WHEN a handler is defined with a body parameter
+ @app.post("/")
+ def handler(user: Model) -> Response[Model]:
+ return Response(body=user, status_code=200, content_type="application/json")
+
+ LOAD_GW_EVENT["httpMethod"] = "POST"
+ LOAD_GW_EVENT["path"] = "/"
+ LOAD_GW_EVENT["body"] = json.dumps({"name": "John", "age": 30})
+
+ # THEN the handler should be invoked and return 200
+ # THEN the body must be a dict
+ result = app(LOAD_GW_EVENT, {})
+ assert result["statusCode"] == 200
+ assert json.loads(result["body"]) == {"name": "John", "age": 30}
+
+
+def test_validate_response_invalid_return():
+ # GIVEN an APIGatewayRestResolver with validation enabled
+ app = APIGatewayRestResolver(enable_validation=True)
+
+ class Model(BaseModel):
+ name: str
+ age: int
+
+ # WHEN a handler is defined with a body parameter
+ @app.post("/")
+ def handler(user: Model) -> Response[Model]:
+ return Response(body=user, status_code=200)
+
+ LOAD_GW_EVENT["httpMethod"] = "POST"
+ LOAD_GW_EVENT["path"] = "/"
+ LOAD_GW_EVENT["body"] = json.dumps({})
+
+ # THEN the handler should be invoked and return 422
+ # THEN the body should have the word missing
+ result = app(LOAD_GW_EVENT, {})
+ assert result["statusCode"] == 422
+ assert "missing" in result["body"]