Skip to content

Commit bfe3f64

Browse files
committed
feat(event_handler): use custom serializer during openapi serialization
1 parent 7da8a7b commit bfe3f64

File tree

4 files changed

+39
-4
lines changed

4 files changed

+39
-4
lines changed

aws_lambda_powertools/event_handler/api_gateway.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1401,7 +1401,9 @@ def __init__(
14011401
if self._enable_validation:
14021402
from aws_lambda_powertools.event_handler.middlewares.openapi_validation import OpenAPIValidationMiddleware
14031403

1404-
self.use([OpenAPIValidationMiddleware()])
1404+
# Note the serializer argument: only use custom serializer if provided by the caller
1405+
# Otherwise, fully rely on the internal Pydantic based mechanism to serialize responses.
1406+
self.use([OpenAPIValidationMiddleware(response_serializer=serializer)])
14051407

14061408
def get_openapi_schema(
14071409
self,

aws_lambda_powertools/event_handler/middlewares/openapi_validation.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
import logging
44
from copy import deepcopy
5-
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple
5+
from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple
66

77
from pydantic import BaseModel
88

@@ -55,6 +55,9 @@ def get_todos(): List[Todo]:
5555
```
5656
"""
5757

58+
def __init__(self, response_serializer: Optional[Callable[[Any], str]] = None):
59+
self._response_serializer = response_serializer
60+
5861
def handler(self, app: EventHandlerInstance, next_middleware: NextMiddleware) -> Response:
5962
logger.debug("OpenAPIValidationMiddleware handler")
6063

@@ -181,10 +184,11 @@ def _serialize_response(
181184
exclude_unset=exclude_unset,
182185
exclude_defaults=exclude_defaults,
183186
exclude_none=exclude_none,
187+
custom_serializer=self._response_serializer,
184188
)
185189
else:
186190
# Just serialize the response content returned from the handler
187-
return jsonable_encoder(response_content)
191+
return jsonable_encoder(response_content, custom_serializer=self._response_serializer)
188192

189193
def _prepare_response_content(
190194
self,

aws_lambda_powertools/event_handler/openapi/encoders.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def jsonable_encoder( # noqa: PLR0911
2929
exclude_unset: bool = False,
3030
exclude_defaults: bool = False,
3131
exclude_none: bool = False,
32+
custom_serializer: Optional[Callable[[Any], str]] = None,
3233
) -> Any:
3334
"""
3435
JSON encodes an arbitrary Python object into JSON serializable data types.
@@ -134,6 +135,10 @@ def jsonable_encoder( # noqa: PLR0911
134135
if isinstance(obj, classes_tuple):
135136
return encoder(obj)
136137

138+
# Use custom serializer if present
139+
if custom_serializer:
140+
return custom_serializer(obj)
141+
137142
# Default
138143
return _dump_other(
139144
obj=obj,
@@ -259,7 +264,7 @@ def _dump_other(
259264
exclude_defaults: bool = False,
260265
) -> Any:
261266
"""
262-
Dump an object to ah hashable object, using the same parameters as jsonable_encoder
267+
Dump an object to a hashable object, using the same parameters as jsonable_encoder
263268
"""
264269
try:
265270
data = dict(obj)

tests/functional/event_handler/test_openapi_serialization.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,27 @@ def handler():
3737

3838
# THEN we should get a dictionary
3939
assert isinstance(schema, Dict)
40+
41+
42+
def test_openapi_serialize_other(gw_event):
43+
# GIVEN a custom serializer
44+
def serializer(_):
45+
return "hello world"
46+
47+
# GIVEN APIGatewayRestResolver is initialized with enable_validation=True and the custom serializer
48+
app = APIGatewayRestResolver(enable_validation=True, serializer=serializer)
49+
50+
# GIVEN a custom class
51+
class CustomClass(object):
52+
__slots__ = []
53+
54+
# GIVEN a handler that returns an instance of that class
55+
@app.get("/my/path")
56+
def handler():
57+
return CustomClass()
58+
59+
# WHEN we invoke the handler
60+
response = app(gw_event, {})
61+
62+
# THEN we the custom serializer should be used
63+
assert response["body"] == "hello world"

0 commit comments

Comments
 (0)