diff --git a/aws_lambda_powertools/event_handler/openapi/encoders.py b/aws_lambda_powertools/event_handler/openapi/encoders.py index 520c0d71509..bfa6c56b3b9 100644 --- a/aws_lambda_powertools/event_handler/openapi/encoders.py +++ b/aws_lambda_powertools/event_handler/openapi/encoders.py @@ -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 @@ -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 @@ -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 @@ -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( @@ -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()) @@ -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 @@ -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: @@ -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 @@ -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 @@ -292,6 +307,7 @@ def _dump_other( exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, + custom_serializer=custom_serializer, ) diff --git a/noxfile.py b/noxfile.py index 68882470de5..7023f45a2b7 100644 --- a/noxfile.py +++ b/noxfile.py @@ -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 diff --git a/tests/functional/event_handler/_pydantic/test_openapi_encoders.py b/tests/functional/event_handler/_pydantic/test_openapi_encoders.py index dee9c21b84c..81f7299b04f 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_encoders.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_encoders.py @@ -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"}