Coverage for pydantic/_internal/_decorators.py: 98.28%
315 statements
« prev ^ index » next coverage.py v7.5.3, created at 2024-06-21 17:00 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2024-06-21 17:00 +0000
1"""Logic related to validators applied to models etc. via the `@field_validator` and `@model_validator` decorators."""
3from __future__ import annotations as _annotations 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
5from collections import deque 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
6from dataclasses import dataclass, field 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
7from functools import cached_property, partial, partialmethod 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
8from inspect import Parameter, Signature, isdatadescriptor, ismethoddescriptor, signature 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
9from itertools import islice 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
10from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, Iterable, TypeVar, Union 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
12from pydantic_core import PydanticUndefined, core_schema 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
13from typing_extensions import Literal, TypeAlias, is_typeddict 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
15from ..errors import PydanticUserError 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
16from ._core_utils import get_type_ref 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
17from ._internal_dataclass import slots_true 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
18from ._typing_extra import get_function_type_hints 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
20if TYPE_CHECKING: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
21 from ..fields import ComputedFieldInfo
22 from ..functional_validators import FieldValidatorModes
25@dataclass(**slots_true) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
26class ValidatorDecoratorInfo: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
42 fields: tuple[str, ...] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
43 mode: Literal['before', 'after'] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
44 each_item: bool 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
45 always: bool 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
46 check_fields: bool | None 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
49@dataclass(**slots_true) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
50class FieldValidatorDecoratorInfo: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
63 fields: tuple[str, ...] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
64 mode: FieldValidatorModes 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
65 check_fields: bool | None 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
68@dataclass(**slots_true) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
69class RootValidatorDecoratorInfo: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
79 mode: Literal['before', 'after'] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
82@dataclass(**slots_true) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
83class FieldSerializerDecoratorInfo: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
98 fields: tuple[str, ...] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
99 mode: Literal['plain', 'wrap'] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
100 return_type: Any 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
101 when_used: core_schema.WhenUsed 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
102 check_fields: bool | None 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
105@dataclass(**slots_true) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
106class ModelSerializerDecoratorInfo: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
119 mode: Literal['plain', 'wrap'] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
120 return_type: Any 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
121 when_used: core_schema.WhenUsed 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
124@dataclass(**slots_true) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
125class ModelValidatorDecoratorInfo: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
135 mode: Literal['wrap', 'before', 'after'] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
138DecoratorInfo: TypeAlias = """Union[ 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
139 ValidatorDecoratorInfo,
140 FieldValidatorDecoratorInfo,
141 RootValidatorDecoratorInfo,
142 FieldSerializerDecoratorInfo,
143 ModelSerializerDecoratorInfo,
144 ModelValidatorDecoratorInfo,
145 ComputedFieldInfo,
146]"""
148ReturnType = TypeVar('ReturnType') 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
149DecoratedType: TypeAlias = ( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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__` 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
155class PydanticDescriptorProxy(Generic[ReturnType]): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
170 decorator_info: DecoratorInfo 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
171 shim: Callable[[Callable[..., Any]], Callable[..., Any]] | None = None 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
173 def __post_init__(self): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
174 for attr in 'setter', 'deleter': 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
175 if hasattr(self.wrapped, attr): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
176 f = partial(self._call_wrapped_attr, name=attr) 1zAstabcdefByCDuvghijklEFwxmnopqr
177 setattr(self, attr, f) 1zAstabcdefByCDuvghijklEFwxmnopqr
179 def _call_wrapped_attr(self, func: Callable[[Any], None], *, name: str) -> PydanticDescriptorProxy[ReturnType]: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
180 self.wrapped = getattr(self.wrapped, name)(func) 1zAstabcdefByCDuvghijklEFwxmnopqr
181 return self 1zAstabcdefByCDuvghijklEFwxmnopqr
183 def __get__(self, obj: object | None, obj_type: type[object] | None = None) -> PydanticDescriptorProxy[ReturnType]: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
184 try: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
185 return self.wrapped.__get__(obj, obj_type) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
191 if hasattr(self.wrapped, '__set_name__'): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
192 self.wrapped.__set_name__(instance, name) # pyright: ignore[reportFunctionMemberAccess] 1zAstabcdefByCDuvghijklEFwxmnopqr
194 def __getattr__(self, __name: str) -> Any: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
195 """Forward checks for __isabstractmethod__ and such."""
196 return getattr(self.wrapped, __name) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
199DecoratorInfoType = TypeVar('DecoratorInfoType', bound=DecoratorInfo) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
202@dataclass(**slots_true) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
203class Decorator(Generic[DecoratorInfoType]): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
218 cls_var_name: str 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
219 func: Callable[..., Any] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
220 shim: Callable[[Any], Any] | None 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
221 info: DecoratorInfoType 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
223 @staticmethod 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
224 def build( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
243 if shim is not None: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
244 func = shim(func) 1zAstabcdefByCDuvghijklEFwxmnopqr
245 func = unwrap_wrapped_function(func, unwrap_partial=False) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
246 if not callable(func): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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]: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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( 1zAstabcdefByCDuvghijklEFwxmnopqr
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], ...]: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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): 1zAstabcdefByCDuvghijklEFwxmnopqr
286 return tp.__orig_bases__ # type: ignore 1zAstabcdefByCDuvghijklEFwxmnopqr
287 try: 1zAstabcdefByCDuvghijklEFwxmnopqr
288 return tp.__bases__ 1zAstabcdefByCDuvghijklEFwxmnopqr
289 except AttributeError: 1zAstabcdefByCDuvghijklEFwxmnopqr
290 return () 1zAstabcdefByCDuvghijklEFwxmnopqr
293def mro(tp: type[Any]) -> tuple[type[Any], ...]: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
301 try: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
302 return tp.__mro__ 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
303 except AttributeError: 1zAstabcdefByCDuvghijklEFwxmnopqr
304 # GenericAlias and some other cases
305 pass 1zAstabcdefByCDuvghijklEFwxmnopqr
307 bases = get_bases(tp) 1zAstabcdefByCDuvghijklEFwxmnopqr
308 return (tp,) + mro_for_bases(bases) 1zAstabcdefByCDuvghijklEFwxmnopqr
311def mro_for_bases(bases: tuple[type[Any], ...]) -> tuple[type[Any], ...]: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
312 def merge_seqs(seqs: list[deque[type[Any]]]) -> Iterable[type[Any]]: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
313 while True: 1abcdefyghijklGHIJKLMmnopqr
314 non_empty = [seq for seq in seqs if seq] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
315 if not non_empty: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
316 # Nothing left to process, we're done.
317 return 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
318 candidate: type[Any] | None = None 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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 complete1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
320 candidate = seq[0] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
321 not_head = [s for s in non_empty if candidate in islice(s, 1, None)] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
322 if not_head: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
323 # Reject the candidate.
324 candidate = None 1zAstabcdefByCDuvghijklEFwxmnopqr
325 else:
326 break 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
327 if not candidate: 327 ↛ 328line 327 didn't jump to line 328, because the condition on line 327 was never true1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
328 raise TypeError('Inconsistent hierarchy, no C3 MRO is possible')
329 yield candidate 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
330 for seq in non_empty: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
331 # Remove candidate.
332 if seq[0] == candidate: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
333 seq.popleft() 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
335 seqs = [deque(mro(base)) for base in bases] + [deque(bases)] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
336 return tuple(merge_seqs(seqs)) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
339_sentinel = object() 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
342def get_attribute_from_bases(tp: type[Any] | tuple[type[Any], ...], name: str) -> Any: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
362 for base in mro_for_bases(tp): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
363 attribute = base.__dict__.get(name, _sentinel) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
364 if attribute is not _sentinel: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
365 attribute_get = getattr(attribute, '__get__', None) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
366 if attribute_get is not None: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
367 return attribute_get(None, tp) 1zAstabcdefByCDuvghijklEFwxmnopqr
368 return attribute 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
369 raise AttributeError(f'{name} not found in {tp}') 1zAstabcdefByCDuvghijklEFwxmnopqr
370 else:
371 try: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
372 return getattr(tp, name) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
373 except AttributeError: 1zAstabcdefByCDuvghijklEFwxmnopqr
374 return get_attribute_from_bases(mro(tp), name) 1zAstabcdefByCDuvghijklEFwxmnopqr
377def get_attribute_from_base_dicts(tp: type[Any], name: str) -> Any: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
399class DecoratorInfos: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
407 field_validators: dict[str, Decorator[FieldValidatorDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
408 root_validators: dict[str, Decorator[RootValidatorDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
409 field_serializers: dict[str, Decorator[FieldSerializerDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
410 model_serializers: dict[str, Decorator[ModelSerializerDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
411 model_validators: dict[str, Decorator[ModelValidatorDecoratorInfo]] = field(default_factory=dict) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
412 computed_fields: dict[str, Decorator[ComputedFieldInfo]] = field(default_factory=dict) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
414 @staticmethod 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
415 def build(model_dc: type[Any]) -> DecoratorInfos: # noqa: C901 (ignore complexity) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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() 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
430 for base in reversed(mro(model_dc)[1:]): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
431 existing: DecoratorInfos | None = base.__dict__.get('__pydantic_decorators__') 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
432 if existing is None: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
433 existing = DecoratorInfos.build(base) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
434 res.validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.validators.items()}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
435 res.field_validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.field_validators.items()}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
436 res.root_validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.root_validators.items()}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
437 res.field_serializers.update({k: v.bind_to_cls(model_dc) for k, v in existing.field_serializers.items()}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
438 res.model_serializers.update({k: v.bind_to_cls(model_dc) for k, v in existing.model_serializers.items()}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
439 res.model_validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.model_validators.items()}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
440 res.computed_fields.update({k: v.bind_to_cls(model_dc) for k, v in existing.computed_fields.items()}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
442 to_replace: list[tuple[str, Any]] = [] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
444 for var_name, var_value in vars(model_dc).items(): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
445 if isinstance(var_value, PydanticDescriptorProxy): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
446 info = var_value.decorator_info 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
447 if isinstance(info, ValidatorDecoratorInfo): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
448 res.validators[var_name] = Decorator.build( 1zAstabcdefByCDuvghijklEFwxmnopqr
449 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
450 )
451 elif isinstance(info, FieldValidatorDecoratorInfo): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
452 res.field_validators[var_name] = Decorator.build( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
453 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
454 )
455 elif isinstance(info, RootValidatorDecoratorInfo): 1zAstabcdefByCDuvghijklEFwxmnopqr
456 res.root_validators[var_name] = Decorator.build( 1zAstabcdefByCDuvghijklEFwxmnopqr
457 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
458 )
459 elif isinstance(info, FieldSerializerDecoratorInfo): 1zAstabcdefByCDuvghijklEFwxmnopqr
460 # check whether a serializer function is already registered for fields
461 for field_serializer_decorator in res.field_serializers.values(): 1zAstabcdefByCDuvghijklEFwxmnopqr
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: 1zAstabcdefByCDuvghijklEFwxmnopqr
466 continue 1zAstabcdefByCDuvghijklEFwxmnopqr
467 for f in info.fields: 1zAstabcdefByCDuvghijklEFwxmnopqr
468 if f in field_serializer_decorator.info.fields: 1zAstabcdefByCDuvghijklEFwxmnopqr
469 raise PydanticUserError( 1zAstabcdefByCDuvghijklEFwxmnopqr
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( 1zAstabcdefByCDuvghijklEFwxmnopqr
475 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
476 )
477 elif isinstance(info, ModelValidatorDecoratorInfo): 1zAstabcdefByCDuvghijklEFwxmnopqr
478 res.model_validators[var_name] = Decorator.build( 1zAstabcdefByCDuvghijklEFwxmnopqr
479 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
480 )
481 elif isinstance(info, ModelSerializerDecoratorInfo): 1zAstabcdefByCDuvghijklEFwxmnopqr
482 res.model_serializers[var_name] = Decorator.build( 1zAstabcdefByCDuvghijklEFwxmnopqr
483 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info
484 )
485 else:
486 from ..fields import ComputedFieldInfo 1zAstabcdefByCDuvghijklEFwxmnopqr
488 isinstance(var_value, ComputedFieldInfo) 1zAstabcdefByCDuvghijklEFwxmnopqr
489 res.computed_fields[var_name] = Decorator.build( 1zAstabcdefByCDuvghijklEFwxmnopqr
490 model_dc, cls_var_name=var_name, shim=None, info=info
491 )
492 to_replace.append((var_name, var_value.wrapped)) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
493 if to_replace: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
499 for name, value in to_replace: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
500 setattr(model_dc, name, value) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
501 return res 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
504def inspect_validator(validator: Callable[..., Any], mode: FieldValidatorModes) -> bool: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
517 sig = signature(validator) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
518 except (ValueError, TypeError): 1zAstabcdefCDuvghijklEFwxmnopqr
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 1zAstabcdefCDuvghijklEFwxmnopqr
522 n_positional = count_positional_required_params(sig) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
523 if mode == 'wrap': 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
524 if n_positional == 3: 1zAstabcdefByCDuvghijklEFwxmnopqr
525 return True 1zAstabcdefByCDuvghijklEFwxmnopqr
526 elif n_positional == 2: 1zAstabcdefByCDuvghijklEFwxmnopqr
527 return False 1zAstabcdefByCDuvghijklEFwxmnopqr
528 else:
529 assert mode in {'before', 'after', 'plain'}, f"invalid mode: {mode!r}, expected 'before', 'after' or 'plain" 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
530 if n_positional == 2: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
531 return True 1zAstabcdefByCDuvghijklEFwxmnopqr
532 elif n_positional == 1: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
533 return False 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
535 raise PydanticUserError( 1zAstabcdefByCDuvghijklEFwxmnopqr
536 f'Unrecognized field_validator function signature for {validator} with `mode={mode}`:{sig}',
537 code='validator-signature',
538 )
541def inspect_field_serializer( 1zAstabcdefCDuvghijklNOGHIJKLMEFwxmnopqr
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: 1zAstabcdefByCDuvghijklEFwxmnopqr
559 sig = signature(serializer) 1zAstabcdefByCDuvghijklEFwxmnopqr
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) 1zAstabcdefByCDuvghijklEFwxmnopqr
566 is_field_serializer = first is not None and first.name == 'self' 1zAstabcdefByCDuvghijklEFwxmnopqr
568 n_positional = count_positional_required_params(sig) 1zAstabcdefByCDuvghijklEFwxmnopqr
569 if is_field_serializer: 1zAstabcdefByCDuvghijklEFwxmnopqr
570 # -1 to correct for self parameter
571 info_arg = _serializer_info_arg(mode, n_positional - 1) 1zAstabcdefByCDuvghijklEFwxmnopqr
572 else:
573 info_arg = _serializer_info_arg(mode, n_positional) 1zAstabcdefByCDuvghijklEFwxmnopqr
575 if info_arg is None: 1zAstabcdefByCDuvghijklEFwxmnopqr
576 raise PydanticUserError( 1zAstabcdefByCDuvghijklEFwxmnopqr
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: 1zAstabcdefByCDuvghijklEFwxmnopqr
581 raise PydanticUserError( 1zAstabcdefByCDuvghijklEFwxmnopqr
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 1zAstabcdefByCDuvghijklEFwxmnopqr
589def inspect_annotated_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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: 1zAstabcdefByCDuvghijklEFwxmnopqr
602 sig = signature(serializer) 1zAstabcdefByCDuvghijklEFwxmnopqr
603 except (ValueError, TypeError): 1zAstabcdefCDuvghijklEFwxmnopqr
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 1zAstabcdefCDuvghijklEFwxmnopqr
607 info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) 1zAstabcdefByCDuvghijklEFwxmnopqr
608 if info_arg is None: 1zAstabcdefByCDuvghijklEFwxmnopqr
609 raise PydanticUserError( 1zAstabcdefByCDuvghijklEFwxmnopqr
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 1zAstabcdefByCDuvghijklEFwxmnopqr
617def inspect_model_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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): 1zAstabcdefByCDuvghijklEFwxmnopqr
630 raise PydanticUserError( 1zAstabcdefByCDuvghijklEFwxmnopqr
631 '`@model_serializer` must be applied to instance methods', code='model-serializer-instance-method'
632 )
634 sig = signature(serializer) 1zAstabcdefByCDuvghijklEFwxmnopqr
635 info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) 1zAstabcdefByCDuvghijklEFwxmnopqr
636 if info_arg is None: 1zAstabcdefByCDuvghijklEFwxmnopqr
637 raise PydanticUserError( 1zAstabcdefByCDuvghijklEFwxmnopqr
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 1zAstabcdefByCDuvghijklEFwxmnopqr
645def _serializer_info_arg(mode: Literal['plain', 'wrap'], n_positional: int) -> bool | None: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
646 if mode == 'plain': 1zAstabcdefByCDuvghijklEFwxmnopqr
647 if n_positional == 1: 1zAstabcdefByCDuvghijklEFwxmnopqr
648 # (input_value: Any, /) -> Any
649 return False 1zAstabcdefByCDuvghijklEFwxmnopqr
650 elif n_positional == 2: 1zAstabcdefByCDuvghijklEFwxmnopqr
651 # (model: Any, input_value: Any, /) -> Any
652 return True 1zAstabcdefByCDuvghijklEFwxmnopqr
653 else:
654 assert mode == 'wrap', f"invalid mode: {mode!r}, expected 'plain' or 'wrap'" 1zAstabcdefByCDuvghijklEFwxmnopqr
655 if n_positional == 2: 1zAstabcdefByCDuvghijklEFwxmnopqr
656 # (input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any
657 return False 1zAstabcdefByCDuvghijklEFwxmnopqr
658 elif n_positional == 3: 1zAstabcdefByCDuvghijklEFwxmnopqr
659 # (input_value: Any, serializer: SerializerFunctionWrapHandler, info: SerializationInfo, /) -> Any
660 return True 1zAstabcdefByCDuvghijklEFwxmnopqr
662 return None 1zAstabcdefByCDuvghijklEFwxmnopqr
665AnyDecoratorCallable: TypeAlias = ( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
666 'Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any], Callable[..., Any]]'
667)
670def is_instance_method_from_sig(function: AnyDecoratorCallable) -> bool: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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)) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
683 first = next(iter(sig.parameters.values()), None) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
684 if first and first.name == 'self': 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
685 return True 1zAstabcdefByCDuvghijklEFwxmnopqr
686 return False 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
689def ensure_classmethod_based_on_signature(function: AnyDecoratorCallable) -> Any: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
702 return function 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
705def _is_classmethod_from_sig(function: AnyDecoratorCallable) -> bool: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
706 sig = signature(unwrap_wrapped_function(function)) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
707 first = next(iter(sig.parameters.values()), None) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
708 if first and first.name == 'cls': 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
709 return True 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
710 return False 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
713def unwrap_wrapped_function( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
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, otherwise don't.
725 decorators.
726 unwrap_class_static_method: If True (default), also unwrap classmethod and staticmethod
727 decorators. If False, only unwrap partial and partialmethod decorators.
729 Returns:
730 The underlying function of the wrapped function.
731 """
732 all: set[Any] = {property, cached_property} 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
734 if unwrap_partial: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
735 all.update({partial, partialmethod}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
737 if unwrap_class_static_method: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
738 all.update({staticmethod, classmethod}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
740 while isinstance(func, tuple(all)): 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
741 if unwrap_class_static_method and isinstance(func, (classmethod, staticmethod)): 1zAstabcdefByCDuvghijklEFwxmnopqr
742 func = func.__func__ 1zAstabcdefByCDuvghijklEFwxmnopqr
743 elif isinstance(func, (partial, partialmethod)): 1zAstabcdefByCDuvghijklEFwxmnopqr
744 func = func.func 1zAstabcdefByCDuvghijklEFwxmnopqr
745 elif isinstance(func, property): 1zAstabcdefByCDuvghijklEFwxmnopqr
746 func = func.fget # arbitrary choice, convenient for computed fields 1zAstabcdefByCDuvghijklEFwxmnopqr
747 else:
748 # Make coverage happy as it can only get here in the last possible case
749 assert isinstance(func, cached_property) 1zAstabcdefByCDuvghijklEFwxmnopqr
750 func = func.func # type: ignore 1zAstabcdefByCDuvghijklEFwxmnopqr
752 return func 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
755def get_function_return_type( 1zAstabcdefCDuvghijklNOGHIJKLMEFwxmnopqr
756 func: Any, explicit_return_type: Any, types_namespace: dict[str, Any] | None = None
757) -> Any:
758 """Get the function return type.
760 It gets the return type from the type annotation if `explicit_return_type` is `None`.
761 Otherwise, it returns `explicit_return_type`.
763 Args:
764 func: The function to get its return type.
765 explicit_return_type: The explicit return type.
766 types_namespace: The types namespace, defaults to `None`.
768 Returns:
769 The function return type.
770 """
771 if explicit_return_type is PydanticUndefined: 1zAstabcdefByCDuvghijklEFwxmnopqr
772 # try to get it from the type annotation
773 hints = get_function_type_hints( 1zAstabcdefByCDuvghijklEFwxmnopqr
774 unwrap_wrapped_function(func), include_keys={'return'}, types_namespace=types_namespace
775 )
776 return hints.get('return', PydanticUndefined) 1zAstabcdefByCDuvghijklEFwxmnopqr
777 else:
778 return explicit_return_type 1zAstabcdefByCDuvghijklEFwxmnopqr
781def count_positional_required_params(sig: Signature) -> int: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
782 """Get the number of positional (required) arguments of a signature.
784 This function should only be used to inspect signatures of validation and serialization functions.
785 The first argument (the value being serialized or validated) is counted as a required argument
786 even if a default value exists.
788 Returns:
789 The number of positional arguments of a signature.
790 """
791 parameters = list(sig.parameters.values()) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
792 return sum( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
793 1
794 for param in parameters
795 if can_be_positional(param)
796 # First argument is the value being validated/serialized, and can have a default value
797 # (e.g. `float`, which has signature `(x=0, /)`). We assume other parameters (the info arg
798 # for instance) should be required, and thus without any default value.
799 and (param.default is Parameter.empty or param == parameters[0])
800 )
803def can_be_positional(param: Parameter) -> bool: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
804 return param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
807def ensure_property(f: Any) -> Any: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr
808 """Ensure that a function is a `property` or `cached_property`, or is a valid descriptor.
810 Args:
811 f: The function to check.
813 Returns:
814 The function, or a `property` or `cached_property` instance wrapping the function.
815 """
816 if ismethoddescriptor(f) or isdatadescriptor(f): 1zAstabcdefByCDuvghijklEFwxmnopqr
817 return f 1zAstabcdefByCDuvghijklEFwxmnopqr
818 else:
819 return property(f) 1zAstabcdefByCDuvghijklEFwxmnopqr