summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.11/site-packages/litestar/contrib/pydantic/utils.py')
-rw-r--r--venv/lib/python3.11/site-packages/litestar/contrib/pydantic/utils.py214
1 files changed, 214 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/utils.py b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/utils.py
new file mode 100644
index 0000000..6aee322
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/utils.py
@@ -0,0 +1,214 @@
+# 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()
+ }