Source code for litestar.plugins.pydantic.plugins.init

from __future__ import annotations

from contextlib import suppress
from functools import partial
from typing import TYPE_CHECKING, Any, TypeGuard, TypeVar, cast

import pydantic

from litestar._signature.types import ExtendedMsgSpecValidationError
from litestar.plugins import InitPlugin
from litestar.utils import is_class_and_subclass

if TYPE_CHECKING:
    from collections.abc import Callable

    from litestar.config.app import AppConfig
    from litestar.plugins.pydantic.types import PydanticV2FieldsListType

T = TypeVar("T")


def _dec_pydantic_v2(model_type: type[pydantic.BaseModel], value: Any, strict: bool) -> pydantic.BaseModel:
    try:
        return model_type.model_validate(value, strict=strict)
    except pydantic.ValidationError as e:
        hide_input = model_type.model_config.get("hide_input_in_errors", False)
        raise ExtendedMsgSpecValidationError(
            errors=cast("list[dict[str, Any]]", e.errors(include_input=not hide_input))
        ) from e


_base_encoders: dict[Any, Callable[[Any], Any]] = {
    pydantic.EmailStr: str,
    pydantic.NameEmail: str,
    pydantic.ByteSize: lambda val: val.real,
}


def is_pydantic_v2_model_class(annotation: Any) -> TypeGuard[type[pydantic.BaseModel]]:  # pyright: ignore[reportInvalidTypeForm]
    return is_class_and_subclass(annotation, pydantic.BaseModel)  # pyright: ignore[reportOptionalMemberAccess]


[docs] class PydanticInitPlugin(InitPlugin): __slots__ = ( "exclude", "exclude_defaults", "exclude_none", "exclude_unset", "include", "prefer_alias", "round_trip", "validate_strict", )
[docs] def __init__( self, exclude: PydanticV2FieldsListType | None = None, exclude_defaults: bool = False, exclude_none: bool = False, exclude_unset: bool = False, include: PydanticV2FieldsListType | None = None, prefer_alias: bool = False, validate_strict: bool = False, round_trip: bool = False, ) -> None: """Pydantic Plugin to support serialization / validation of Pydantic types / models :param exclude: Fields to exclude during serialization :param exclude_defaults: Fields to exclude during serialization when they are set to their default value :param exclude_none: Fields to exclude during serialization when they are set to ``None`` :param exclude_unset: Fields to exclude during serialization when they arenot set :param include: Fields to exclude during serialization :param prefer_alias: Use the ``by_alias=True`` flag when dumping models :param validate_strict: Use ``strict=True`` when calling ``.model_validate`` on Pydantic 2.x models :param round_trip: use ``round_trip=True`` when calling ``.model_dump`` and ``.model_dump_json`` on Pydantic 2.x models """ self.exclude = exclude self.exclude_defaults = exclude_defaults self.exclude_none = exclude_none self.exclude_unset = exclude_unset self.include = include self.prefer_alias = prefer_alias self.validate_strict = validate_strict self.round_trip = round_trip
@classmethod def encoders( cls, exclude: PydanticV2FieldsListType | None = None, exclude_defaults: bool = False, exclude_none: bool = False, exclude_unset: bool = False, include: PydanticV2FieldsListType | None = None, prefer_alias: bool = False, round_trip: bool = False, ) -> dict[Any, Callable[[Any], Any]]: return { **_base_encoders, **cls._create_pydantic_v2_encoders( prefer_alias=prefer_alias, exclude=exclude, # type: ignore[arg-type] exclude_defaults=exclude_defaults, exclude_none=exclude_none, exclude_unset=exclude_unset, include=include, # type: ignore[arg-type] round_trip=round_trip, ), } @classmethod def decoders(cls, validate_strict: bool = False) -> list[tuple[Callable[[Any], bool], Callable[[Any, Any], Any]]]: return [(is_pydantic_v2_model_class, partial(_dec_pydantic_v2, strict=validate_strict))] @staticmethod def _create_pydantic_v2_encoders( exclude: PydanticV2FieldsListType | None = None, exclude_defaults: bool = False, exclude_none: bool = False, exclude_unset: bool = False, include: PydanticV2FieldsListType | None = None, prefer_alias: bool = False, round_trip: bool = False, ) -> dict[Any, Callable[[Any], Any]]: encoders: dict[Any, Callable[[Any], Any]] = { pydantic.BaseModel: lambda model: model.model_dump( # pyright: ignore[reportOptionalMemberAccess] by_alias=prefer_alias, exclude=exclude, exclude_defaults=exclude_defaults, exclude_none=exclude_none, exclude_unset=exclude_unset, include=include, mode="json", round_trip=round_trip, ), pydantic.types.SecretStr: lambda val: "**********" if val else "", # pyright: ignore[reportOptionalMemberAccess] pydantic.types.SecretBytes: lambda val: "**********" if val else "", # pyright: ignore[reportOptionalMemberAccess] pydantic.AnyUrl: str, # pyright: ignore[reportOptionalMemberAccess] } with suppress(ImportError): from pydantic_extra_types import color encoders[color.Color] = str return encoders
[docs] def on_app_init(self, app_config: AppConfig) -> AppConfig: app_config.type_encoders = { **self.encoders( prefer_alias=self.prefer_alias, exclude=self.exclude, exclude_defaults=self.exclude_defaults, exclude_none=self.exclude_none, exclude_unset=self.exclude_unset, include=self.include, round_trip=self.round_trip, ), **(app_config.type_encoders or {}), } app_config.type_decoders = [ *self.decoders(validate_strict=self.validate_strict), *(app_config.type_decoders or []), ] return app_config