Skip to content

Commit cff37cb

Browse files
Adding exception when working with unsupported objetcs
1 parent 418172d commit cff37cb

File tree

4 files changed

+97
-69
lines changed

4 files changed

+97
-69
lines changed

aws_lambda_powertools/event_handler/openapi/encoders.py

Lines changed: 76 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from pydantic.types import SecretBytes, SecretStr
1515

1616
from aws_lambda_powertools.event_handler.openapi.compat import _model_dump
17+
from aws_lambda_powertools.event_handler.openapi.exceptions import EncoderError
1718
from aws_lambda_powertools.event_handler.openapi.types import IncEx
1819

1920
"""
@@ -69,88 +70,94 @@ def jsonable_encoder( # noqa: PLR0911
6970
if exclude is not None and not isinstance(exclude, (set, dict)):
7071
exclude = set(exclude)
7172

72-
# Pydantic models
73-
if isinstance(obj, BaseModel):
74-
return _dump_base_model(
75-
obj=obj,
76-
include=include,
77-
exclude=exclude,
78-
by_alias=by_alias,
79-
exclude_unset=exclude_unset,
80-
exclude_none=exclude_none,
81-
exclude_defaults=exclude_defaults,
82-
)
73+
try:
74+
# Pydantic models
75+
if isinstance(obj, BaseModel):
76+
return _dump_base_model(
77+
obj=obj,
78+
include=include,
79+
exclude=exclude,
80+
by_alias=by_alias,
81+
exclude_unset=exclude_unset,
82+
exclude_none=exclude_none,
83+
exclude_defaults=exclude_defaults,
84+
)
8385

84-
# Dataclasses
85-
if dataclasses.is_dataclass(obj):
86-
obj_dict = dataclasses.asdict(obj)
87-
return jsonable_encoder(
88-
obj_dict,
89-
include=include,
90-
exclude=exclude,
91-
by_alias=by_alias,
92-
exclude_unset=exclude_unset,
93-
exclude_defaults=exclude_defaults,
94-
exclude_none=exclude_none,
95-
)
86+
# Dataclasses
87+
if dataclasses.is_dataclass(obj):
88+
obj_dict = dataclasses.asdict(obj)
89+
return jsonable_encoder(
90+
obj_dict,
91+
include=include,
92+
exclude=exclude,
93+
by_alias=by_alias,
94+
exclude_unset=exclude_unset,
95+
exclude_defaults=exclude_defaults,
96+
exclude_none=exclude_none,
97+
)
9698

97-
# Enums
98-
if isinstance(obj, Enum):
99-
return obj.value
99+
# Enums
100+
if isinstance(obj, Enum):
101+
return obj.value
100102

101-
# Paths
102-
if isinstance(obj, PurePath):
103-
return str(obj)
103+
# Paths
104+
if isinstance(obj, PurePath):
105+
return str(obj)
104106

105-
# Scalars
106-
if isinstance(obj, (str, int, float, type(None))):
107-
return obj
107+
# Scalars
108+
if isinstance(obj, (str, int, float, type(None))):
109+
return obj
108110

109-
# Dictionaries
110-
if isinstance(obj, dict):
111-
return _dump_dict(
112-
obj=obj,
113-
include=include,
114-
exclude=exclude,
115-
by_alias=by_alias,
116-
exclude_none=exclude_none,
117-
exclude_unset=exclude_unset,
118-
)
111+
# Dictionaries
112+
if isinstance(obj, dict):
113+
return _dump_dict(
114+
obj=obj,
115+
include=include,
116+
exclude=exclude,
117+
by_alias=by_alias,
118+
exclude_none=exclude_none,
119+
exclude_unset=exclude_unset,
120+
)
121+
122+
# Sequences
123+
if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):
124+
return _dump_sequence(
125+
obj=obj,
126+
include=include,
127+
exclude=exclude,
128+
by_alias=by_alias,
129+
exclude_none=exclude_none,
130+
exclude_defaults=exclude_defaults,
131+
exclude_unset=exclude_unset,
132+
)
119133

120-
# Sequences
121-
if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):
122-
return _dump_sequence(
134+
# Other types
135+
if type(obj) in ENCODERS_BY_TYPE:
136+
return ENCODERS_BY_TYPE[type(obj)](obj)
137+
138+
for encoder, classes_tuple in encoders_by_class_tuples.items():
139+
if isinstance(obj, classes_tuple):
140+
return encoder(obj)
141+
142+
# Use custom serializer if present
143+
if custom_serializer:
144+
return custom_serializer(obj)
145+
146+
# Default
147+
return _dump_other(
123148
obj=obj,
124149
include=include,
125150
exclude=exclude,
126151
by_alias=by_alias,
127152
exclude_none=exclude_none,
128-
exclude_defaults=exclude_defaults,
129153
exclude_unset=exclude_unset,
154+
exclude_defaults=exclude_defaults,
130155
)
131-
132-
# Other types
133-
if type(obj) in ENCODERS_BY_TYPE:
134-
return ENCODERS_BY_TYPE[type(obj)](obj)
135-
136-
for encoder, classes_tuple in encoders_by_class_tuples.items():
137-
if isinstance(obj, classes_tuple):
138-
return encoder(obj)
139-
140-
# Use custom serializer if present
141-
if custom_serializer:
142-
return custom_serializer(obj)
143-
144-
# Default
145-
return _dump_other(
146-
obj=obj,
147-
include=include,
148-
exclude=exclude,
149-
by_alias=by_alias,
150-
exclude_none=exclude_none,
151-
exclude_unset=exclude_unset,
152-
exclude_defaults=exclude_defaults,
153-
)
156+
except ValueError as exc:
157+
raise EncoderError(
158+
f"Unable to serializer the object {obj} as it is not a supported type. Error details: {str(exc)}",
159+
"See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#serializing-objects",
160+
) from exc
154161

155162

156163
def _dump_base_model(

aws_lambda_powertools/event_handler/openapi/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ class RequestValidationError(ValidationException):
2121
def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None:
2222
super().__init__(errors)
2323
self.body = body
24+
25+
26+
class EncoderError(Exception):
27+
"""
28+
Base exception for all encoding errors
29+
"""

docs/core/event_handler/api_gateway.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,12 @@ In the following example, we use a new `Header` OpenAPI type to add [one out of
458458

459459
1. `cloudfront_viewer_country` is a list that must contain values from the `CountriesAllowed` enumeration.
460460

461+
#### Serializing objects
462+
463+
We support the serialization of various Python objects, including Pydantic models, dataclasses, enumerations, file paths, scalar types (strings, integers, floats, and None), dictionaries, various sequence types (lists, sets, frozen sets, generators, tuples, and deques), and others defined [here](https://github.com/aws-powertools/powertools-lambda-python/blob/develop/aws_lambda_powertools/event_handler/openapi/encoders.py#L24).
464+
465+
For objects we do not support, such as SQLAlchemy models, we suggest providing your own [custom serializer](#custom-serializer).
466+
461467
### Accessing request details
462468

463469
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`.

tests/functional/event_handler/test_openapi_encoders.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pydantic.color import Color
88

99
from aws_lambda_powertools.event_handler.openapi.encoders import jsonable_encoder
10+
from aws_lambda_powertools.event_handler.openapi.exceptions import EncoderError
1011

1112

1213
def test_openapi_encode_include():
@@ -184,3 +185,11 @@ def __init__(self, name: str):
184185

185186
result = jsonable_encoder(User(name="John"))
186187
assert result == {"name": "John"}
188+
189+
190+
def test_openapi_encode_with_error():
191+
class MyClass:
192+
__slots__ = []
193+
194+
with pytest.raises(EncoderError, match="Unable to serializer the object*"):
195+
jsonable_encoder(MyClass())

0 commit comments

Comments
 (0)