# mypy: strict-equality=False from __future__ import annotations from typing import TYPE_CHECKING, Any from typing_extensions import Annotated, get_type_hints from litestar.params import KwargDefinition from litestar.types import Empty from litestar.typing import FieldDefinition from litestar.utils import deprecated, is_class_and_subclass from litestar.utils.predicates import is_generic from litestar.utils.typing import ( _substitute_typevars, get_origin_or_inner_type, get_type_hints_with_generics_resolved, normalize_type_annotation, ) # isort: off try: from pydantic import v1 as pydantic_v1 import pydantic as pydantic_v2 from pydantic.fields import PydanticUndefined as Pydantic2Undefined # type: ignore[attr-defined] from pydantic.v1.fields import Undefined as Pydantic1Undefined PYDANTIC_UNDEFINED_SENTINELS = {Pydantic1Undefined, Pydantic2Undefined} except ImportError: try: import pydantic as pydantic_v1 # type: ignore[no-redef] from pydantic.fields import Undefined as Pydantic1Undefined # type: ignore[attr-defined, no-redef] pydantic_v2 = Empty # type: ignore[assignment] PYDANTIC_UNDEFINED_SENTINELS = {Pydantic1Undefined} except ImportError: # pyright: ignore pydantic_v1 = Empty # type: ignore[assignment] pydantic_v2 = Empty # type: ignore[assignment] PYDANTIC_UNDEFINED_SENTINELS = set() # isort: on if TYPE_CHECKING: from typing_extensions import TypeGuard def is_pydantic_model_class( annotation: Any, ) -> TypeGuard[type[pydantic_v1.BaseModel | pydantic_v2.BaseModel]]: # pyright: ignore """Given a type annotation determine if the annotation is a subclass of pydantic's BaseModel. Args: annotation: A type. Returns: A typeguard determining whether the type is :data:`BaseModel pydantic.BaseModel>`. """ tests: list[bool] = [] if pydantic_v1 is not Empty: # pragma: no cover tests.append(is_class_and_subclass(annotation, pydantic_v1.BaseModel)) if pydantic_v2 is not Empty: # pragma: no cover tests.append(is_class_and_subclass(annotation, pydantic_v2.BaseModel)) return any(tests) def is_pydantic_model_instance( annotation: Any, ) -> TypeGuard[pydantic_v1.BaseModel | pydantic_v2.BaseModel]: # pyright: ignore """Given a type annotation determine if the annotation is an instance of pydantic's BaseModel. Args: annotation: A type. Returns: A typeguard determining whether the type is :data:`BaseModel pydantic.BaseModel>`. """ tests: list[bool] = [] if pydantic_v1 is not Empty: # pragma: no cover tests.append(isinstance(annotation, pydantic_v1.BaseModel)) if pydantic_v2 is not Empty: # pragma: no cover tests.append(isinstance(annotation, pydantic_v2.BaseModel)) return any(tests) def is_pydantic_constrained_field(annotation: Any) -> bool: """Check if the given annotation is a constrained pydantic type. Args: annotation: A type annotation Returns: True if pydantic is installed and the type is a constrained type, otherwise False. """ if pydantic_v1 is Empty: # pragma: no cover return False # type: ignore[unreachable] return any( is_class_and_subclass(annotation, constrained_type) # pyright: ignore for constrained_type in ( pydantic_v1.ConstrainedBytes, pydantic_v1.ConstrainedDate, pydantic_v1.ConstrainedDecimal, pydantic_v1.ConstrainedFloat, pydantic_v1.ConstrainedFrozenSet, pydantic_v1.ConstrainedInt, pydantic_v1.ConstrainedList, pydantic_v1.ConstrainedSet, pydantic_v1.ConstrainedStr, ) ) def pydantic_unwrap_and_get_origin(annotation: Any) -> Any | None: if pydantic_v2 is Empty or (pydantic_v1 is not Empty and is_class_and_subclass(annotation, pydantic_v1.BaseModel)): return get_origin_or_inner_type(annotation) origin = annotation.__pydantic_generic_metadata__["origin"] return normalize_type_annotation(origin) def pydantic_get_type_hints_with_generics_resolved( annotation: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None, include_extras: bool = False, model_annotations: dict[str, Any] | None = None, ) -> dict[str, Any]: if pydantic_v2 is Empty or (pydantic_v1 is not Empty and is_class_and_subclass(annotation, pydantic_v1.BaseModel)): return get_type_hints_with_generics_resolved(annotation, type_hints=model_annotations) origin = pydantic_unwrap_and_get_origin(annotation) if origin is None: if model_annotations is None: # pragma: no cover model_annotations = get_type_hints( annotation, globalns=globalns, localns=localns, include_extras=include_extras ) typevar_map = {p: p for p in annotation.__pydantic_generic_metadata__["parameters"]} else: if model_annotations is None: model_annotations = get_type_hints( origin, globalns=globalns, localns=localns, include_extras=include_extras ) args = annotation.__pydantic_generic_metadata__["args"] parameters = origin.__pydantic_generic_metadata__["parameters"] typevar_map = dict(zip(parameters, args)) return {n: _substitute_typevars(type_, typevar_map) for n, type_ in model_annotations.items()} @deprecated(version="2.6.2") def pydantic_get_unwrapped_annotation_and_type_hints(annotation: Any) -> tuple[Any, dict[str, Any]]: # pragma: pver """Get the unwrapped annotation and the type hints after resolving generics. Args: annotation: A type annotation. Returns: A tuple containing the unwrapped annotation and the type hints. """ if is_generic(annotation): origin = pydantic_unwrap_and_get_origin(annotation) return origin or annotation, pydantic_get_type_hints_with_generics_resolved(annotation, include_extras=True) return annotation, get_type_hints(annotation, include_extras=True) def is_pydantic_2_model( obj: type[pydantic_v1.BaseModel | pydantic_v2.BaseModel], # pyright: ignore ) -> TypeGuard[pydantic_v2.BaseModel]: # pyright: ignore return pydantic_v2 is not Empty and issubclass(obj, pydantic_v2.BaseModel) def is_pydantic_undefined(value: Any) -> bool: return any(v is value for v in PYDANTIC_UNDEFINED_SENTINELS) def create_field_definitions_for_computed_fields( model: type[pydantic_v1.BaseModel | pydantic_v2.BaseModel], # pyright: ignore prefer_alias: bool, ) -> dict[str, FieldDefinition]: """Create field definitions for computed fields. Args: model: A pydantic model. prefer_alias: Whether to prefer the alias or the name of the field. Returns: A dictionary containing the field definitions for the computed fields. """ pydantic_decorators = getattr(model, "__pydantic_decorators__", None) if pydantic_decorators is None: return {} def get_name(k: str, dec: Any) -> str: if not dec.info.alias: return k return dec.info.alias if prefer_alias else k # type: ignore[no-any-return] return { (name := get_name(k, dec)): FieldDefinition.from_annotation( Annotated[ dec.info.return_type, KwargDefinition(title=dec.info.title, description=dec.info.description, read_only=True), ], name=name, ) for k, dec in pydantic_decorators.computed_fields.items() }