From 869d375f9472ec79ae4dd3e775ae7c0d8f329937 Mon Sep 17 00:00:00 2001 From: ramnes Date: Wed, 30 Nov 2022 18:24:42 +0100 Subject: [PATCH] Compare objects MRO with encoders at runtime The previous implementation doesn't handle subclass instances when pydantic.json.ENCODERS_BY_TYPE is modified after fastapi.encoders import. This diff makes it easier for developers to add custom encoders that also work with subclass instances (and it simplifies the code, as well). --- fastapi/encoders.py | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/fastapi/encoders.py b/fastapi/encoders.py index e8610c983b..9f5c6897a0 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -1,6 +1,6 @@ import dataclasses import datetime -from collections import defaultdict, deque +from collections import deque from decimal import Decimal from enum import Enum from ipaddress import ( @@ -94,20 +94,6 @@ ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { } -def generate_encoders_by_class_tuples( - type_encoder_map: dict[Any, Callable[[Any], Any]], -) -> dict[Callable[[Any], Any], tuple[Any, ...]]: - encoders_by_class_tuples: dict[Callable[[Any], Any], tuple[Any, ...]] = defaultdict( - tuple - ) - for type_, encoder in type_encoder_map.items(): - encoders_by_class_tuples[encoder] += (type_,) - return encoders_by_class_tuples - - -encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE) - - def jsonable_encoder( obj: Annotated[ Any, @@ -313,11 +299,9 @@ def jsonable_encoder( ) return encoded_list - if type(obj) in ENCODERS_BY_TYPE: - return ENCODERS_BY_TYPE[type(obj)](obj) - for encoder, classes_tuple in encoders_by_class_tuples.items(): - if isinstance(obj, classes_tuple): - return encoder(obj) + for base in obj.__class__.__mro__[:-1]: + if base in ENCODERS_BY_TYPE: + return ENCODERS_BY_TYPE[base](obj) if is_pydantic_v1_model_instance(obj): raise PydanticV1NotSupportedError( "pydantic.v1 models are no longer supported by FastAPI."