Coverage for pydantic/_internal/_decorators.py: 98.25%
311 statements
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-03 19:29 +0000
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-03 19:29 +0000
1"""Logic related to validators applied to models etc. via the `@field_validator` and `@model_validator` decorators."""
3from __future__ import annotations as _annotations 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
5from collections import deque 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
6from dataclasses import dataclass, field 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
7from functools import cached_property, partial, partialmethod 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
8from inspect import Parameter, Signature, isdatadescriptor, ismethoddescriptor, signature 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
9from itertools import islice 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
10from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, Iterable, TypeVar, Union 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
12from pydantic_core import PydanticUndefined, core_schema 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
13from typing_extensions import Literal, TypeAlias, is_typeddict 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
15from ..errors import PydanticUserError 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
16from ._core_utils import get_type_ref 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
17from ._internal_dataclass import slots_true 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
18from ._typing_extra import get_function_type_hints 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
20if TYPE_CHECKING: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
21 from ..fields import ComputedFieldInfo
22 from ..functional_validators import FieldValidatorModes
25@dataclass(**slots_true) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
26class ValidatorDecoratorInfo: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
27 """A container for data from `@validator` so that we can access it
28 while building the pydantic-core schema.
30 Attributes:
31 decorator_repr: A class variable representing the decorator string, '@validator'.
32 fields: A tuple of field names the validator should be called on.
33 mode: The proposed validator mode.
34 each_item: For complex objects (sets, lists etc.) whether to validate individual
35 elements rather than the whole object.
36 always: Whether this method and other validators should be called even if the value is missing.
37 check_fields: Whether to check that the fields actually exist on the model.
38 """
40 decorator_repr: ClassVar[str] = '@validator' 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
42 fields: tuple[str, ...] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
43 mode: Literal['before', 'after'] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
44 each_item: bool 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
45 always: bool 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
46 check_fields: bool | None 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
49@dataclass(**slots_true) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
50class FieldValidatorDecoratorInfo: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
51 """A container for data from `@field_validator` so that we can access it
52 while building the pydantic-core schema.
54 Attributes:
55 decorator_repr: A class variable representing the decorator string, '@field_validator'.
56 fields: A tuple of field names the validator should be called on.
57 mode: The proposed validator mode.
58 check_fields: Whether to check that the fields actually exist on the model.
59 """
61 decorator_repr: ClassVar[str] = '@field_validator' 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
63 fields: tuple[str, ...] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
64 mode: FieldValidatorModes 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
65 check_fields: bool | None 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
68@dataclass(**slots_true) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
69class RootValidatorDecoratorInfo: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
70 """A container for data from `@root_validator` so that we can access it
71 while building the pydantic-core schema.
73 Attributes:
74 decorator_repr: A class variable representing the decorator string, '@root_validator'.
75 mode: The proposed validator mode.
76 """
78 decorator_repr: ClassVar[str] = '@root_validator' 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
79 mode: Literal['before', 'after'] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
82@dataclass(**slots_true) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
83class FieldSerializerDecoratorInfo: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
84 """A container for data from `@field_serializer` so that we can access it
85 while building the pydantic-core schema.
87 Attributes:
88 decorator_repr: A class variable representing the decorator string, '@field_serializer'.
89 fields: A tuple of field names the serializer should be called on.
90 mode: The proposed serializer mode.
91 return_type: The type of the serializer's return value.
92 when_used: The serialization condition. Accepts a string with values `'always'`, `'unless-none'`, `'json'`,
93 and `'json-unless-none'`.
94 check_fields: Whether to check that the fields actually exist on the model.
95 """
97 decorator_repr: ClassVar[str] = '@field_serializer' 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
98 fields: tuple[str, ...] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
99 mode: Literal['plain', 'wrap'] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
100 return_type: Any 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
101 when_used: core_schema.WhenUsed 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
102 check_fields: bool | None 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
105@dataclass(**slots_true) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
106class ModelSerializerDecoratorInfo: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
107 """A container for data from `@model_serializer` so that we can access it
108 while building the pydantic-core schema.
110 Attributes:
111 decorator_repr: A class variable representing the decorator string, '@model_serializer'.
112 mode: The proposed serializer mode.
113 return_type: The type of the serializer's return value.
114 when_used: The serialization condition. Accepts a string with values `'always'`, `'unless-none'`, `'json'`,
115 and `'json-unless-none'`.
116 """
118 decorator_repr: ClassVar[str] = '@model_serializer' 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
119 mode: Literal['plain', 'wrap'] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
120 return_type: Any 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
121 when_used: core_schema.WhenUsed 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
124@dataclass(**slots_true) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
125class ModelValidatorDecoratorInfo: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
126 """A container for data from `@model_validator` so that we can access it
127 while building the pydantic-core schema.
129 Attributes:
130 decorator_repr: A class variable representing the decorator string, '@model_serializer'.
131 mode: The proposed serializer mode.
132 """
134 decorator_repr: ClassVar[str] = '@model_validator' 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
135 mode: Literal['wrap', 'before', 'after'] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
138DecoratorInfo: TypeAlias = """Union[ 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
139 ValidatorDecoratorInfo,
140 FieldValidatorDecoratorInfo,
141 RootValidatorDecoratorInfo,
142 FieldSerializerDecoratorInfo,
143 ModelSerializerDecoratorInfo,
144 ModelValidatorDecoratorInfo,
145 ComputedFieldInfo,
146]"""
148ReturnType = TypeVar('ReturnType') 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
149DecoratedType: TypeAlias = ( 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
150 'Union[classmethod[Any, Any, ReturnType], staticmethod[Any, ReturnType], Callable[..., ReturnType], property]'
151)
154@dataclass # can't use slots here since we set attributes on `__post_init__` 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
155class PydanticDescriptorProxy(Generic[ReturnType]): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
156 """Wrap a classmethod, staticmethod, property or unbound function
157 and act as a descriptor that allows us to detect decorated items
158 from the class' attributes.
160 This class' __get__ returns the wrapped item's __get__ result,
161 which makes it transparent for classmethods and staticmethods.
163 Attributes:
164 wrapped: The decorator that has to be wrapped.
165 decorator_info: The decorator info.
166 shim: A wrapper function to wrap V1 style function.
167 """
169 wrapped: DecoratedType[ReturnType] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
170 decorator_info: DecoratorInfo 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
171 shim: Callable[[Callable[..., Any]], Callable[..., Any]] | None = None 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
173 def __post_init__(self): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
174 for attr in 'setter', 'deleter': 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
175 if hasattr(self.wrapped, attr): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
176 f = partial(self._call_wrapped_attr, name=attr) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
177 setattr(self, attr, f) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
179 def _call_wrapped_attr(self, func: Callable[[Any], None], *, name: str) -> PydanticDescriptorProxy[ReturnType]: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
180 self.wrapped = getattr(self.wrapped, name)(func) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
181 return self 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
183 def __get__(self, obj: object | None, obj_type: type[object] | None = None) -> PydanticDescriptorProxy[ReturnType]: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
184 try: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
185 return self.wrapped.__get__(obj, obj_type) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
186 except AttributeError: 1zAstabcdefByCDuvghijklEFwxmnopqr
187 # not a descriptor, e.g. a partial object
188 return self.wrapped # type: ignore[return-value] 1zAstabcdefByCDuvghijklEFwxmnopqr
190 def __set_name__(self, instance: Any, name: str) -> None: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
191 if hasattr(self.wrapped, '__set_name__'): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
192 self.wrapped.__set_name__(instance, name) # pyright: ignore[reportFunctionMemberAccess] 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
194 def __getattr__(self, __name: str) -> Any: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
195 """Forward checks for __isabstractmethod__ and such."""
196 return getattr(self.wrapped, __name) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
199DecoratorInfoType = TypeVar('DecoratorInfoType', bound=DecoratorInfo) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
202@dataclass(**slots_true) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
203class Decorator(Generic[DecoratorInfoType]): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
204 """A generic container class to join together the decorator metadata
205 (metadata from decorator itself, which we have when the
206 decorator is called but not when we are building the core-schema)
207 and the bound function (which we have after the class itself is created).
209 Attributes:
210 cls_ref: The class ref.
211 cls_var_name: The decorated function name.
212 func: The decorated function.
213 shim: A wrapper function to wrap V1 style function.
214 info: The decorator info.
215 """
217 cls_ref: str 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
218 cls_var_name: str 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
219 func: Callable[..., Any] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
220 shim: Callable[[Any], Any] | None 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
221 info: DecoratorInfoType 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
223 @staticmethod 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
224 def build( 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
225 cls_: Any,
226 *,
227 cls_var_name: str,
228 shim: Callable[[Any], Any] | None,
229 info: DecoratorInfoType,
230 ) -> Decorator[DecoratorInfoType]:
231 """Build a new decorator.
233 Args:
234 cls_: The class.
235 cls_var_name: The decorated function name.
236 shim: A wrapper function to wrap V1 style function.
237 info: The decorator info.
239 Returns:
240 The new decorator instance.
241 """
242 func = get_attribute_from_bases(cls_, cls_var_name) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
243 if shim is not None: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
244 func = shim(func) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
245 func = unwrap_wrapped_function(func, unwrap_partial=False) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
246 if not callable(func): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
247 # This branch will get hit for classmethod properties
248 attribute = get_attribute_from_base_dicts(cls_, cls_var_name) # prevents the binding call to `__get__` 1stabcdefByuvghijklwxmnopqr
249 if isinstance(attribute, PydanticDescriptorProxy): 249 ↛ 251line 249 didn't jump to line 251 because the condition on line 249 was always true1stabcdefByuvghijklwxmnopqr
250 func = unwrap_wrapped_function(attribute.wrapped) 1stabcdefByuvghijklwxmnopqr
251 return Decorator( 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
252 cls_ref=get_type_ref(cls_),
253 cls_var_name=cls_var_name,
254 func=func,
255 shim=shim,
256 info=info,
257 )
259 def bind_to_cls(self, cls: Any) -> Decorator[DecoratorInfoType]: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
260 """Bind the decorator to a class.
262 Args:
263 cls: the class.
265 Returns:
266 The new decorator instance.
267 """
268 return self.build( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
269 cls,
270 cls_var_name=self.cls_var_name,
271 shim=self.shim,
272 info=self.info,
273 )
276def get_bases(tp: type[Any]) -> tuple[type[Any], ...]: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
277 """Get the base classes of a class or typeddict.
279 Args:
280 tp: The type or class to get the bases.
282 Returns:
283 The base classes.
284 """
285 if is_typeddict(tp): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
286 return tp.__orig_bases__ # type: ignore 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
287 try: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
288 return tp.__bases__ 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
289 except AttributeError: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
290 return () 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
293def mro(tp: type[Any]) -> tuple[type[Any], ...]: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
294 """Calculate the Method Resolution Order of bases using the C3 algorithm.
296 See https://www.python.org/download/releases/2.3/mro/
297 """
298 # try to use the existing mro, for performance mainly
299 # but also because it helps verify the implementation below
300 if not is_typeddict(tp): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
301 try: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
302 return tp.__mro__ 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
303 except AttributeError: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
304 # GenericAlias and some other cases
305 pass 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
307 bases = get_bases(tp) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
308 return (tp,) + mro_for_bases(bases) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
311def mro_for_bases(bases: tuple[type[Any], ...]) -> tuple[type[Any], ...]: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
312 def merge_seqs(seqs: list[deque[type[Any]]]) -> Iterable[type[Any]]: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
313 while True: 1abcdefGHyghijklIJMNOPQRSTmnopqrKL
314 non_empty = [seq for seq in seqs if seq] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
315 if not non_empty: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
316 # Nothing left to process, we're done.
317 return 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
318 candidate: type[Any] | None = None 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
319 for seq in non_empty: # Find merge candidates among seq heads. 319 ↛ 327line 319 didn't jump to line 327 because the loop on line 319 didn't complete1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
320 candidate = seq[0] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
321 not_head = [s for s in non_empty if candidate in islice(s, 1, None)] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
322 if not_head: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
323 # Reject the candidate.
324 candidate = None 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
325 else:
326 break 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
327 if not candidate: 327 ↛ 328line 327 didn't jump to line 328 because the condition on line 327 was never true1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
328 raise TypeError('Inconsistent hierarchy, no C3 MRO is possible')
329 yield candidate 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
330 for seq in non_empty: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
331 # Remove candidate.
332 if seq[0] == candidate: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
333 seq.popleft() 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
335 seqs = [deque(mro(base)) for base in bases] + [deque(bases)] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
336 return tuple(merge_seqs(seqs)) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
339_sentinel = object() 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
342def get_attribute_from_bases(tp: type[Any] | tuple[type[Any], ...], name: str) -> Any: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
343 """Get the attribute from the next class in the MRO that has it,
344 aiming to simulate calling the method on the actual class.
346 The reason for iterating over the mro instead of just getting
347 the attribute (which would do that for us) is to support TypedDict,
348 which lacks a real __mro__, but can have a virtual one constructed
349 from its bases (as done here).
351 Args:
352 tp: The type or class to search for the attribute. If a tuple, this is treated as a set of base classes.
353 name: The name of the attribute to retrieve.
355 Returns:
356 Any: The attribute value, if found.
358 Raises:
359 AttributeError: If the attribute is not found in any class in the MRO.
360 """
361 if isinstance(tp, tuple): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
362 for base in mro_for_bases(tp): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
363 attribute = base.__dict__.get(name, _sentinel) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
364 if attribute is not _sentinel: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
365 attribute_get = getattr(attribute, '__get__', None) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
366 if attribute_get is not None: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
367 return attribute_get(None, tp) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
368 return attribute 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
369 raise AttributeError(f'{name} not found in {tp}') 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
370 else:
371 try: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
372 return getattr(tp, name) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
373 except AttributeError: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
374 return get_attribute_from_bases(mro(tp), name) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
377def get_attribute_from_base_dicts(tp: type[Any], name: str) -> Any: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
378 """Get an attribute out of the `__dict__` following the MRO.
379 This prevents the call to `__get__` on the descriptor, and allows
380 us to get the original function for classmethod properties.
382 Args:
383 tp: The type or class to search for the attribute.
384 name: The name of the attribute to retrieve.
386 Returns:
387 Any: The attribute value, if found.
389 Raises:
390 KeyError: If the attribute is not found in any class's `__dict__` in the MRO.
391 """
392 for base in reversed(mro(tp)): 392 ↛ 395line 392 didn't jump to line 395 because the loop on line 392 didn't complete1stabcdefByuvghijklwxmnopqr
393 if name in base.__dict__: 1stabcdefByuvghijklwxmnopqr
394 return base.__dict__[name] 1stabcdefByuvghijklwxmnopqr
395 return tp.__dict__[name] # raise the error
398@dataclass(**slots_true) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
399class DecoratorInfos: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
400 """Mapping of name in the class namespace to decorator info.
402 note that the name in the class namespace is the function or attribute name
403 not the field name!
404 """
406 validators: dict[str, Decorator[ValidatorDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
407 field_validators: dict[str, Decorator[FieldValidatorDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
408 root_validators: dict[str, Decorator[RootValidatorDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
409 field_serializers: dict[str, Decorator[FieldSerializerDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
410 model_serializers: dict[str, Decorator[ModelSerializerDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
411 model_validators: dict[str, Decorator[ModelValidatorDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
412 computed_fields: dict[str, Decorator[ComputedFieldInfo]] = field(default_factory=dict) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
414 @staticmethod 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
415 def build(model_dc: type[Any]) -> DecoratorInfos: # noqa: C901 (ignore complexity) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
416 """We want to collect all DecFunc instances that exist as
417 attributes in the namespace of the class (a BaseModel or dataclass)
418 that called us
419 But we want to collect these in the order of the bases
420 So instead of getting them all from the leaf class (the class that called us),
421 we traverse the bases from root (the oldest ancestor class) to leaf
422 and collect all of the instances as we go, taking care to replace
423 any duplicate ones with the last one we see to mimic how function overriding
424 works with inheritance.
425 If we do replace any functions we put the replacement into the position
426 the replaced function was in; that is, we maintain the order.
427 """
428 # reminder: dicts are ordered and replacement does not alter the order
429 res = DecoratorInfos() 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
430 for base in reversed(mro(model_dc)[1:]): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
431 existing: DecoratorInfos | None = base.__dict__.get('__pydantic_decorators__') 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
432 if existing is None: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
433 existing = DecoratorInfos.build(base) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
434 res.validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.validators.items()}) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
435 res.field_validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.field_validators.items()}) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
436 res.root_validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.root_validators.items()}) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
437 res.field_serializers.update({k: v.bind_to_cls(model_dc) for k, v in existing.field_serializers.items()}) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
438 res.model_serializers.update({k: v.bind_to_cls(model_dc) for k, v in existing.model_serializers.items()}) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
439 res.model_validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.model_validators.items()}) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
440 res.computed_fields.update({k: v.bind_to_cls(model_dc) for k, v in existing.computed_fields.items()}) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
442 to_replace: list[tuple[str, Any]] = [] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
444 for var_name, var_value in vars(model_dc).items(): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
445 if isinstance(var_value, PydanticDescriptorProxy): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
446 info = var_value.decorator_info 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
447 if isinstance(info, ValidatorDecoratorInfo): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
448 res.validators[var_name] = Decorator.build( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
449 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
450 )
451 elif isinstance(info, FieldValidatorDecoratorInfo): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
452 res.field_validators[var_name] = Decorator.build( 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
453 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
454 )
455 elif isinstance(info, RootValidatorDecoratorInfo): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
456 res.root_validators[var_name] = Decorator.build( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
457 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
458 )
459 elif isinstance(info, FieldSerializerDecoratorInfo): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
460 # check whether a serializer function is already registered for fields
461 for field_serializer_decorator in res.field_serializers.values(): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
462 # check that each field has at most one serializer function.
463 # serializer functions for the same field in subclasses are allowed,
464 # and are treated as overrides
465 if field_serializer_decorator.cls_var_name == var_name: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
466 continue 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
467 for f in info.fields: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
468 if f in field_serializer_decorator.info.fields: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
469 raise PydanticUserError( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
470 'Multiple field serializer functions were defined '
471 f'for field {f!r}, this is not allowed.',
472 code='multiple-field-serializers',
473 )
474 res.field_serializers[var_name] = Decorator.build( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
475 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
476 )
477 elif isinstance(info, ModelValidatorDecoratorInfo): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
478 res.model_validators[var_name] = Decorator.build( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
479 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
480 )
481 elif isinstance(info, ModelSerializerDecoratorInfo): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
482 res.model_serializers[var_name] = Decorator.build( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
483 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
484 )
485 else:
486 from ..fields import ComputedFieldInfo 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
488 isinstance(var_value, ComputedFieldInfo) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
489 res.computed_fields[var_name] = Decorator.build( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
490 model_dc, cls_var_name=var_name, shim=None, info=info
491 )
492 to_replace.append((var_name, var_value.wrapped)) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
493 if to_replace: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
494 # If we can save `__pydantic_decorators__` on the class we'll be able to check for it above
495 # so then we don't need to re-process the type, which means we can discard our descriptor wrappers
496 # and replace them with the thing they are wrapping (see the other setattr call below)
497 # which allows validator class methods to also function as regular class methods
498 setattr(model_dc, '__pydantic_decorators__', res) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
499 for name, value in to_replace: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
500 setattr(model_dc, name, value) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
501 return res 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
504def inspect_validator(validator: Callable[..., Any], mode: FieldValidatorModes) -> bool: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
505 """Look at a field or model validator function and determine whether it takes an info argument.
507 An error is raised if the function has an invalid signature.
509 Args:
510 validator: The validator function to inspect.
511 mode: The proposed validator mode.
513 Returns:
514 Whether the validator takes an info argument.
515 """
516 try: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
517 sig = signature(validator) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
518 except (ValueError, TypeError): 1zAstabcdefGHCDuvghijklIJEFwxmnopqrKL
519 # `inspect.signature` might not be able to infer a signature, e.g. with C objects.
520 # In this case, we assume no info argument is present:
521 return False 1zAstabcdefGHCDuvghijklIJEFwxmnopqrKL
522 n_positional = count_positional_required_params(sig) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
523 if mode == 'wrap': 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
524 if n_positional == 3: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
525 return True 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
526 elif n_positional == 2: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
527 return False 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
528 else:
529 assert mode in {'before', 'after', 'plain'}, f"invalid mode: {mode!r}, expected 'before', 'after' or 'plain" 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
530 if n_positional == 2: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
531 return True 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
532 elif n_positional == 1: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
533 return False 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
535 raise PydanticUserError( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
536 f'Unrecognized field_validator function signature for {validator} with `mode={mode}`:{sig}',
537 code='validator-signature',
538 )
541def inspect_field_serializer( 1zAstabcdefGHCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
542 serializer: Callable[..., Any], mode: Literal['plain', 'wrap'], computed_field: bool = False
543) -> tuple[bool, bool]:
544 """Look at a field serializer function and determine if it is a field serializer,
545 and whether it takes an info argument.
547 An error is raised if the function has an invalid signature.
549 Args:
550 serializer: The serializer function to inspect.
551 mode: The serializer mode, either 'plain' or 'wrap'.
552 computed_field: When serializer is applied on computed_field. It doesn't require
553 info signature.
555 Returns:
556 Tuple of (is_field_serializer, info_arg).
557 """
558 try: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
559 sig = signature(serializer) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
560 except (ValueError, TypeError):
561 # `inspect.signature` might not be able to infer a signature, e.g. with C objects.
562 # In this case, we assume no info argument is present and this is not a method:
563 return (False, False)
565 first = next(iter(sig.parameters.values()), None) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
566 is_field_serializer = first is not None and first.name == 'self' 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
568 n_positional = count_positional_required_params(sig) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
569 if is_field_serializer: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
570 # -1 to correct for self parameter
571 info_arg = _serializer_info_arg(mode, n_positional - 1) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
572 else:
573 info_arg = _serializer_info_arg(mode, n_positional) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
575 if info_arg is None: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
576 raise PydanticUserError( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
577 f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}',
578 code='field-serializer-signature',
579 )
580 if info_arg and computed_field: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
581 raise PydanticUserError( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
582 'field_serializer on computed_field does not use info signature', code='field-serializer-signature'
583 )
585 else:
586 return is_field_serializer, info_arg 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
589def inspect_annotated_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
590 """Look at a serializer function used via `Annotated` and determine whether it takes an info argument.
592 An error is raised if the function has an invalid signature.
594 Args:
595 serializer: The serializer function to check.
596 mode: The serializer mode, either 'plain' or 'wrap'.
598 Returns:
599 info_arg
600 """
601 try: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
602 sig = signature(serializer) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
603 except (ValueError, TypeError): 1zAstabcdefGHCDuvghijklIJEFwxmnopqrKL
604 # `inspect.signature` might not be able to infer a signature, e.g. with C objects.
605 # In this case, we assume no info argument is present:
606 return False 1zAstabcdefGHCDuvghijklIJEFwxmnopqrKL
607 info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
608 if info_arg is None: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
609 raise PydanticUserError( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
610 f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}',
611 code='field-serializer-signature',
612 )
613 else:
614 return info_arg 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
617def inspect_model_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
618 """Look at a model serializer function and determine whether it takes an info argument.
620 An error is raised if the function has an invalid signature.
622 Args:
623 serializer: The serializer function to check.
624 mode: The serializer mode, either 'plain' or 'wrap'.
626 Returns:
627 `info_arg` - whether the function expects an info argument.
628 """
629 if isinstance(serializer, (staticmethod, classmethod)) or not is_instance_method_from_sig(serializer): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
630 raise PydanticUserError( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
631 '`@model_serializer` must be applied to instance methods', code='model-serializer-instance-method'
632 )
634 sig = signature(serializer) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
635 info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
636 if info_arg is None: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
637 raise PydanticUserError( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
638 f'Unrecognized model_serializer function signature for {serializer} with `mode={mode}`:{sig}',
639 code='model-serializer-signature',
640 )
641 else:
642 return info_arg 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
645def _serializer_info_arg(mode: Literal['plain', 'wrap'], n_positional: int) -> bool | None: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
646 if mode == 'plain': 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
647 if n_positional == 1: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
648 # (input_value: Any, /) -> Any
649 return False 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
650 elif n_positional == 2: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
651 # (model: Any, input_value: Any, /) -> Any
652 return True 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
653 else:
654 assert mode == 'wrap', f"invalid mode: {mode!r}, expected 'plain' or 'wrap'" 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
655 if n_positional == 2: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
656 # (input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any
657 return False 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
658 elif n_positional == 3: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
659 # (input_value: Any, serializer: SerializerFunctionWrapHandler, info: SerializationInfo, /) -> Any
660 return True 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
662 return None 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
665AnyDecoratorCallable: TypeAlias = ( 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
666 'Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any], Callable[..., Any]]'
667)
670def is_instance_method_from_sig(function: AnyDecoratorCallable) -> bool: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
671 """Whether the function is an instance method.
673 It will consider a function as instance method if the first parameter of
674 function is `self`.
676 Args:
677 function: The function to check.
679 Returns:
680 `True` if the function is an instance method, `False` otherwise.
681 """
682 sig = signature(unwrap_wrapped_function(function)) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
683 first = next(iter(sig.parameters.values()), None) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
684 if first and first.name == 'self': 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
685 return True 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
686 return False 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
689def ensure_classmethod_based_on_signature(function: AnyDecoratorCallable) -> Any: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
690 """Apply the `@classmethod` decorator on the function.
692 Args:
693 function: The function to apply the decorator on.
695 Return:
696 The `@classmethod` decorator applied function.
697 """
698 if not isinstance( 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
699 unwrap_wrapped_function(function, unwrap_class_static_method=False), classmethod
700 ) and _is_classmethod_from_sig(function):
701 return classmethod(function) # type: ignore[arg-type] 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
702 return function 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
705def _is_classmethod_from_sig(function: AnyDecoratorCallable) -> bool: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
706 sig = signature(unwrap_wrapped_function(function)) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
707 first = next(iter(sig.parameters.values()), None) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
708 if first and first.name == 'cls': 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
709 return True 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
710 return False 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
713def unwrap_wrapped_function( 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
714 func: Any,
715 *,
716 unwrap_partial: bool = True,
717 unwrap_class_static_method: bool = True,
718) -> Any:
719 """Recursively unwraps a wrapped function until the underlying function is reached.
720 This handles property, functools.partial, functools.partialmethod, staticmethod, and classmethod.
722 Args:
723 func: The function to unwrap.
724 unwrap_partial: If True (default), unwrap partial and partialmethod decorators.
725 unwrap_class_static_method: If True (default), also unwrap classmethod and staticmethod
726 decorators. If False, only unwrap partial and partialmethod decorators.
728 Returns:
729 The underlying function of the wrapped function.
730 """
731 # Define the types we want to check against as a single tuple.
732 unwrap_types = ( 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
733 (property, cached_property)
734 + ((partial, partialmethod) if unwrap_partial else ())
735 + ((staticmethod, classmethod) if unwrap_class_static_method else ())
736 )
738 while isinstance(func, unwrap_types): 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
739 if unwrap_class_static_method and isinstance(func, (classmethod, staticmethod)): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
740 func = func.__func__ 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
741 elif isinstance(func, (partial, partialmethod)): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
742 func = func.func 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
743 elif isinstance(func, property): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
744 func = func.fget # arbitrary choice, convenient for computed fields 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
745 else:
746 # Make coverage happy as it can only get here in the last possible case
747 assert isinstance(func, cached_property) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
748 func = func.func # type: ignore 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
750 return func 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
753def get_function_return_type( 1zAstabcdefGHCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
754 func: Any, explicit_return_type: Any, types_namespace: dict[str, Any] | None = None
755) -> Any:
756 """Get the function return type.
758 It gets the return type from the type annotation if `explicit_return_type` is `None`.
759 Otherwise, it returns `explicit_return_type`.
761 Args:
762 func: The function to get its return type.
763 explicit_return_type: The explicit return type.
764 types_namespace: The types namespace, defaults to `None`.
766 Returns:
767 The function return type.
768 """
769 if explicit_return_type is PydanticUndefined: 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
770 # try to get it from the type annotation
771 hints = get_function_type_hints( 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
772 unwrap_wrapped_function(func), include_keys={'return'}, types_namespace=types_namespace
773 )
774 return hints.get('return', PydanticUndefined) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
775 else:
776 return explicit_return_type 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
779def count_positional_required_params(sig: Signature) -> int: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
780 """Get the number of positional (required) arguments of a signature.
782 This function should only be used to inspect signatures of validation and serialization functions.
783 The first argument (the value being serialized or validated) is counted as a required argument
784 even if a default value exists.
786 Returns:
787 The number of positional arguments of a signature.
788 """
789 parameters = list(sig.parameters.values()) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
790 return sum( 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
791 1
792 for param in parameters
793 if can_be_positional(param)
794 # First argument is the value being validated/serialized, and can have a default value
795 # (e.g. `float`, which has signature `(x=0, /)`). We assume other parameters (the info arg
796 # for instance) should be required, and thus without any default value.
797 and (param.default is Parameter.empty or param == parameters[0])
798 )
801def can_be_positional(param: Parameter) -> bool: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
802 return param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
805def ensure_property(f: Any) -> Any: 1zAstabcdefGHByCDuvghijklIJUVMNOPQRSTEFwxmnopqrKL
806 """Ensure that a function is a `property` or `cached_property`, or is a valid descriptor.
808 Args:
809 f: The function to check.
811 Returns:
812 The function, or a `property` or `cached_property` instance wrapping the function.
813 """
814 if ismethoddescriptor(f) or isdatadescriptor(f): 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
815 return f 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL
816 else:
817 return property(f) 1zAstabcdefGHByCDuvghijklIJEFwxmnopqrKL