Skip to content

Commit

Permalink
fix(event_handler): custom serializer recursive values when using dat…
Browse files Browse the repository at this point in the history
…a validation (#4664)

* fix(event_handler): custom serializer recursive values

* fix: use custom serializers for custom sequence values

* fix: use custom serializers for custom base model values

* fix: use custom serializers for custom data classes values

* fix: propagate custom serializer for any unmatched type for safety

* fix(test): target pydantic v2 only in arbitrary type

* Customer must handle serialization on Pydantic models

---------

Co-authored-by: Leandro Damascena <[email protected]>
  • Loading branch information
heitorlessa and leandrodamascena authored Jul 3, 2024
1 parent c7f9bb0 commit 1554019
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 3 deletions.
20 changes: 18 additions & 2 deletions aws_lambda_powertools/event_handler/openapi/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def jsonable_encoder( # noqa: PLR0911
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
custom_serializer=custom_serializer,
)

# Enums
Expand All @@ -115,8 +116,9 @@ def jsonable_encoder( # noqa: PLR0911
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_none=exclude_none,
exclude_unset=exclude_unset,
exclude_none=exclude_none,
custom_serializer=custom_serializer,
)

# Sequences
Expand All @@ -129,6 +131,7 @@ def jsonable_encoder( # noqa: PLR0911
exclude_none=exclude_none,
exclude_defaults=exclude_defaults,
exclude_unset=exclude_unset,
custom_serializer=custom_serializer,
)

# Other types
Expand All @@ -152,6 +155,7 @@ def jsonable_encoder( # noqa: PLR0911
exclude_none=exclude_none,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
custom_serializer=custom_serializer,
)
except ValueError as exc:
raise SerializationError(
Expand Down Expand Up @@ -201,9 +205,15 @@ def _dump_dict(
by_alias: bool = True,
exclude_unset: bool = False,
exclude_none: bool = False,
custom_serializer: Optional[Callable[[Any], str]] = None,
) -> Dict[str, Any]:
"""
Dump a dict to a dict, using the same parameters as jsonable_encoder
Parameters
----------
custom_serializer : Callable, optional
A custom serializer to use for encoding the object, when everything else fails.
"""
encoded_dict = {}
allowed_keys = set(obj.keys())
Expand All @@ -222,12 +232,14 @@ def _dump_dict(
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_none=exclude_none,
custom_serializer=custom_serializer,
)
encoded_value = jsonable_encoder(
value,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_none=exclude_none,
custom_serializer=custom_serializer,
)
encoded_dict[encoded_key] = encoded_value
return encoded_dict
Expand All @@ -242,9 +254,10 @@ def _dump_sequence(
exclude_unset: bool = False,
exclude_none: bool = False,
exclude_defaults: bool = False,
custom_serializer: Optional[Callable[[Any], str]] = None,
) -> List[Any]:
"""
Dump a sequence to a list, using the same parameters as jsonable_encoder
Dump a sequence to a list, using the same parameters as jsonable_encoder.
"""
encoded_list = []
for item in obj:
Expand All @@ -257,6 +270,7 @@ def _dump_sequence(
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
custom_serializer=custom_serializer,
),
)
return encoded_list
Expand All @@ -271,6 +285,7 @@ def _dump_other(
exclude_unset: bool = False,
exclude_none: bool = False,
exclude_defaults: bool = False,
custom_serializer: Optional[Callable[[Any], str]] = None,
) -> Any:
"""
Dump an object to a hashable object, using the same parameters as jsonable_encoder
Expand All @@ -292,6 +307,7 @@ def _dump_other(
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
custom_serializer=custom_serializer,
)


Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def test_with_aws_encryption_sdk_as_required_package(session: nox.Session):


@nox.session()
@nox.parametrize("pydantic", ["1.10", "2.0"])
@nox.parametrize("pydantic", ["1.10,<2.0", "2.0"])
def test_with_pydantic_required_package(session: nox.Session, pydantic: str):
"""Tests that only depends for Pydantic library v1 and v2"""
# Event Handler OpenAPI
Expand Down
57 changes: 57 additions & 0 deletions tests/functional/event_handler/_pydantic/test_openapi_encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,60 @@ class MyClass:

with pytest.raises(SerializationError, match="Unable to serialize the object*"):
jsonable_encoder(MyClass())


def test_openapi_encode_custom_serializer_nested_dict():
# GIVEN a nested dictionary with a custom class
class CustomClass: ...

nested_dict = {"a": {"b": CustomClass()}}

# AND a custom serializer
def serializer(value):
return "serialized"

# WHEN we call jsonable_encoder with the nested dictionary and unserializable value
result = jsonable_encoder(nested_dict, custom_serializer=serializer)

# THEN we should get the custom serializer output
assert result == {"a": {"b": "serialized"}}


def test_openapi_encode_custom_serializer_sequences():
# GIVEN a sequence with a custom class
class CustomClass:
__slots__ = []

seq = [CustomClass()]

# AND a custom serializer
def serializer(value):
return "serialized"

# WHEN we call jsonable_encoder with the nested dictionary and unserializable value
result = jsonable_encoder(seq, custom_serializer=serializer)

# THEN we should get the custom serializer output
assert result == ["serialized"]


def test_openapi_encode_custom_serializer_dataclasses():
# GIVEN a sequence with a custom class
class CustomClass:
__slots__ = []

@dataclass
class Order:
kind: CustomClass

order = Order(kind=CustomClass())

# AND a custom serializer
def serializer(value):
return "serialized"

# WHEN we call jsonable_encoder with the nested dictionary and unserializable value
result = jsonable_encoder(order, custom_serializer=serializer)

# THEN we should get the custom serializer output
assert result == {"kind": "serialized"}

0 comments on commit 1554019

Please sign in to comment.