From 0b576b09607d24e00b5d605041e232f4edb11662 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 | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 451ea0760..1f896cabe 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 ( @@ -14,7 +14,7 @@ from ipaddress import ( from pathlib import Path, PurePath from re import Pattern from types import GeneratorType -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Type, Union from uuid import UUID from fastapi.types import IncEx @@ -85,20 +85,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, @@ -314,11 +300,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) try: data = dict(obj)