fastapi/encoders.py PYTHON 365 lines View on github.com → Search inside
1import dataclasses2import datetime3from collections import defaultdict, deque4from collections.abc import Callable5from decimal import Decimal6from enum import Enum7from ipaddress import (8    IPv4Address,9    IPv4Interface,10    IPv4Network,11    IPv6Address,12    IPv6Interface,13    IPv6Network,14)15from pathlib import Path, PurePath16from re import Pattern17from types import GeneratorType18from typing import Annotated, Any19from uuid import UUID2021from annotated_doc import Doc22from fastapi.exceptions import PydanticV1NotSupportedError23from fastapi.types import IncEx24from pydantic import BaseModel25from pydantic.networks import AnyUrl, NameEmail26from pydantic.types import SecretBytes, SecretStr27from pydantic_core import PydanticUndefinedType2829from ._compat import (30    Url,31    is_pydantic_v1_model_instance,32)3334try:35    # pydantic.color.Color is deprecated since v2.0b3, but supporting for bwd-compat36    from pydantic.color import Color  # ty: ignore[deprecated]37except ImportError:  # pragma: no cover3839    class Color:  # type: ignore[no-redef]40        pass414243try:44    # Supporting the new Color format for newer versions of Pydantic45    from pydantic_extra_types.color import Color as PyExtraColor46except ImportError:  # pragma: no cover4748    class PyExtraColor:  # type: ignore[no-redef]49        pass505152# Taken from Pydantic v1 as is53def isoformat(o: datetime.date | datetime.time) -> str:54    return o.isoformat()555657# Adapted from Pydantic v158# TODO: pv2 should this return strings instead?59def decimal_encoder(dec_value: Decimal) -> int | float:60    """61    Encodes a Decimal as int if there's no exponent, otherwise float6263    This is useful when we use ConstrainedDecimal to represent Numeric(x,0)64    where an integer (but not int typed) is used. Encoding this as a float65    results in failed round-tripping between encode and parse.66    Our Id type is a prime example of this.6768    >>> decimal_encoder(Decimal("1.0"))69    1.07071    >>> decimal_encoder(Decimal("1"))72    17374    >>> decimal_encoder(Decimal("NaN"))75    nan76    """77    exponent = dec_value.as_tuple().exponent78    if isinstance(exponent, int) and exponent >= 0:79        return int(dec_value)80    else:81        return float(dec_value)828384ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {85    bytes: lambda o: o.decode(),86    Color: str,87    PyExtraColor: str,88    datetime.date: isoformat,89    datetime.datetime: isoformat,90    datetime.time: isoformat,91    datetime.timedelta: lambda td: td.total_seconds(),92    Decimal: decimal_encoder,93    Enum: lambda o: o.value,94    frozenset: list,95    deque: list,96    GeneratorType: list,97    IPv4Address: str,98    IPv4Interface: str,99    IPv4Network: str,100    IPv6Address: str,101    IPv6Interface: str,102    IPv6Network: str,103    NameEmail: str,104    Path: str,105    Pattern: lambda o: o.pattern,106    SecretBytes: str,107    SecretStr: str,108    set: list,109    UUID: str,110    Url: str,111    AnyUrl: str,112}113114115def generate_encoders_by_class_tuples(116    type_encoder_map: dict[Any, Callable[[Any], Any]],117) -> dict[Callable[[Any], Any], tuple[Any, ...]]:118    encoders_by_class_tuples: dict[Callable[[Any], Any], tuple[Any, ...]] = defaultdict(119        tuple120    )121    for type_, encoder in type_encoder_map.items():122        encoders_by_class_tuples[encoder] += (type_,)123    return encoders_by_class_tuples124125126encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE)127128129def jsonable_encoder(130    obj: Annotated[131        Any,132        Doc(133            """134            The input object to convert to JSON.135            """136        ),137    ],138    include: Annotated[139        IncEx | None,140        Doc(141            """142            Pydantic's `include` parameter, passed to Pydantic models to set the143            fields to include.144            """145        ),146    ] = None,147    exclude: Annotated[148        IncEx | None,149        Doc(150            """151            Pydantic's `exclude` parameter, passed to Pydantic models to set the152            fields to exclude.153            """154        ),155    ] = None,156    by_alias: Annotated[157        bool,158        Doc(159            """160            Pydantic's `by_alias` parameter, passed to Pydantic models to define if161            the output should use the alias names (when provided) or the Python162            attribute names. In an API, if you set an alias, it's probably because you163            want to use it in the result, so you probably want to leave this set to164            `True`.165            """166        ),167    ] = True,168    exclude_unset: Annotated[169        bool,170        Doc(171            """172            Pydantic's `exclude_unset` parameter, passed to Pydantic models to define173            if it should exclude from the output the fields that were not explicitly174            set (and that only had their default values).175            """176        ),177    ] = False,178    exclude_defaults: Annotated[179        bool,180        Doc(181            """182            Pydantic's `exclude_defaults` parameter, passed to Pydantic models to define183            if it should exclude from the output the fields that had the same default184            value, even when they were explicitly set.185            """186        ),187    ] = False,188    exclude_none: Annotated[189        bool,190        Doc(191            """192            Pydantic's `exclude_none` parameter, passed to Pydantic models to define193            if it should exclude from the output any fields that have a `None` value.194            """195        ),196    ] = False,197    custom_encoder: Annotated[198        dict[Any, Callable[[Any], Any]] | None,199        Doc(200            """201            Pydantic's `custom_encoder` parameter, passed to Pydantic models to define202            a custom encoder.203            """204        ),205    ] = None,206    sqlalchemy_safe: Annotated[207        bool,208        Doc(209            """210            Exclude from the output any fields that start with the name `_sa`.211212            This is mainly a hack for compatibility with SQLAlchemy objects, they213            store internal SQLAlchemy-specific state in attributes named with `_sa`,214            and those objects can't (and shouldn't be) serialized to JSON.215            """216        ),217    ] = True,218) -> Any:219    """220    Convert any object to something that can be encoded in JSON.221222    This is used internally by FastAPI to make sure anything you return can be223    encoded as JSON before it is sent to the client.224225    You can also use it yourself, for example to convert objects before saving them226    in a database that supports only JSON.227228    Read more about it in the229    [FastAPI docs for JSON Compatible Encoder](https://fastapi.tiangolo.com/tutorial/encoder/).230    """231    custom_encoder = custom_encoder or {}232    if custom_encoder:233        if type(obj) in custom_encoder:234            return custom_encoder[type(obj)](obj)235        else:236            for encoder_type, encoder_instance in custom_encoder.items():237                if isinstance(obj, encoder_type):238                    return encoder_instance(obj)239    if include is not None and not isinstance(include, (set, dict)):240        include = set(include)  # type: ignore[assignment]  # ty: ignore[invalid-assignment]241    if exclude is not None and not isinstance(exclude, (set, dict)):242        exclude = set(exclude)  # type: ignore[assignment]  # ty: ignore[invalid-assignment]243    if isinstance(obj, BaseModel):244        obj_dict = obj.model_dump(245            mode="json",246            include=include,247            exclude=exclude,248            by_alias=by_alias,249            exclude_unset=exclude_unset,250            exclude_none=exclude_none,251            exclude_defaults=exclude_defaults,252        )253        return jsonable_encoder(254            obj_dict,255            exclude_none=exclude_none,256            exclude_defaults=exclude_defaults,257            sqlalchemy_safe=sqlalchemy_safe,258        )259    if dataclasses.is_dataclass(obj):260        assert not isinstance(obj, type)261        obj_dict = dataclasses.asdict(obj)262        return jsonable_encoder(263            obj_dict,264            include=include,265            exclude=exclude,266            by_alias=by_alias,267            exclude_unset=exclude_unset,268            exclude_defaults=exclude_defaults,269            exclude_none=exclude_none,270            custom_encoder=custom_encoder,271            sqlalchemy_safe=sqlalchemy_safe,272        )273    if isinstance(obj, Enum):274        return obj.value275    if isinstance(obj, PurePath):276        return str(obj)277    if isinstance(obj, (str, int, float, type(None))):278        return obj279    if isinstance(obj, PydanticUndefinedType):280        return None281    if isinstance(obj, dict):282        encoded_dict = {}283        allowed_keys = set(obj.keys())284        if include is not None:285            allowed_keys &= set(include)286        if exclude is not None:287            allowed_keys -= set(exclude)288        for key, value in obj.items():289            if (290                (291                    not sqlalchemy_safe292                    or (not isinstance(key, str))293                    or (not key.startswith("_sa"))294                )295                and (value is not None or not exclude_none)296                and key in allowed_keys297            ):298                encoded_key = jsonable_encoder(299                    key,300                    by_alias=by_alias,301                    exclude_unset=exclude_unset,302                    exclude_none=exclude_none,303                    custom_encoder=custom_encoder,304                    sqlalchemy_safe=sqlalchemy_safe,305                )306                encoded_value = jsonable_encoder(307                    value,308                    by_alias=by_alias,309                    exclude_unset=exclude_unset,310                    exclude_none=exclude_none,311                    custom_encoder=custom_encoder,312                    sqlalchemy_safe=sqlalchemy_safe,313                )314                encoded_dict[encoded_key] = encoded_value315        return encoded_dict316    if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):317        encoded_list = []318        for item in obj:319            encoded_list.append(320                jsonable_encoder(321                    item,322                    include=include,323                    exclude=exclude,324                    by_alias=by_alias,325                    exclude_unset=exclude_unset,326                    exclude_defaults=exclude_defaults,327                    exclude_none=exclude_none,328                    custom_encoder=custom_encoder,329                    sqlalchemy_safe=sqlalchemy_safe,330                )331            )332        return encoded_list333334    if type(obj) in ENCODERS_BY_TYPE:335        return ENCODERS_BY_TYPE[type(obj)](obj)336    for encoder, classes_tuple in encoders_by_class_tuples.items():337        if isinstance(obj, classes_tuple):338            return encoder(obj)339    if is_pydantic_v1_model_instance(obj):340        raise PydanticV1NotSupportedError(341            "pydantic.v1 models are no longer supported by FastAPI."342            f" Please update the model {obj!r}."343        )344    try:345        data = dict(obj)346    except Exception as e:347        errors: list[Exception] = []348        errors.append(e)349        try:350            data = vars(obj)351        except Exception as e:352            errors.append(e)353            raise ValueError(errors) from e354    return jsonable_encoder(355        data,356        include=include,357        exclude=exclude,358        by_alias=by_alias,359        exclude_unset=exclude_unset,360        exclude_defaults=exclude_defaults,361        exclude_none=exclude_none,362        custom_encoder=custom_encoder,363        sqlalchemy_safe=sqlalchemy_safe,364    )

Code quality findings 20

Ensure functions have docstrings for documentation
missing-docstring
def isoformat(o: datetime.date | datetime.time) -> str:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(exponent, int) and exponent >= 0:
Ensure functions have docstrings for documentation
missing-docstring
def generate_encoders_by_class_tuples(
Ensure functions have docstrings for documentation
missing-docstring
def jsonable_encoder(
Use isinstance() for type checking instead of type()
type-check
if type(obj) in custom_encoder:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, encoder_type):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if include is not None and not isinstance(include, (set, dict)):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if exclude is not None and not isinstance(exclude, (set, dict)):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, BaseModel):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
assert not isinstance(obj, type)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, Enum):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, PurePath):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, (str, int, float, type(None))):
Use isinstance() for type checking instead of type()
type-check
if isinstance(obj, (str, int, float, type(None))):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, PydanticUndefinedType):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
or (not isinstance(key, str))
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):
Use isinstance() for type checking instead of type()
type-check
if type(obj) in ENCODERS_BY_TYPE:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, classes_tuple):

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.