Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(event_handler): custom serializer recursive values when using data validation #4664

Merged
merged 9 commits into from
Jul 3, 2024
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
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"}