Coverage for pydantic/_internal/_decorators.py: 97.72%

315 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-13 19:35 +0000

1"""Logic related to validators applied to models etc. via the `@field_validator` and `@model_validator` decorators.""" 

2 

3from __future__ import annotations as _annotations 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

4 

5from collections import deque 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

6from collections.abc import Iterable 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

7from dataclasses import dataclass, field 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

8from functools import cached_property, partial, partialmethod 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

9from inspect import Parameter, Signature, isdatadescriptor, ismethoddescriptor, signature 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

10from itertools import islice 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

11from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, Literal, TypeVar, Union 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

12 

13from pydantic_core import PydanticUndefined, core_schema 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

14from typing_extensions import TypeAlias, is_typeddict 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

15 

16from ..errors import PydanticUserError 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

17from ._core_utils import get_type_ref 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

18from ._internal_dataclass import slots_true 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

19from ._namespace_utils import GlobalsNamespace, MappingNamespace 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

20from ._typing_extra import get_function_type_hints 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

21from ._utils import can_be_positional 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

22 

23if TYPE_CHECKING: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

24 from ..fields import ComputedFieldInfo 

25 from ..functional_validators import FieldValidatorModes 

26 

27 

28@dataclass(**slots_true) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

29class ValidatorDecoratorInfo: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

30 """A container for data from `@validator` so that we can access it 

31 while building the pydantic-core schema. 

32 

33 Attributes: 

34 decorator_repr: A class variable representing the decorator string, '@validator'. 

35 fields: A tuple of field names the validator should be called on. 

36 mode: The proposed validator mode. 

37 each_item: For complex objects (sets, lists etc.) whether to validate individual 

38 elements rather than the whole object. 

39 always: Whether this method and other validators should be called even if the value is missing. 

40 check_fields: Whether to check that the fields actually exist on the model. 

41 """ 

42 

43 decorator_repr: ClassVar[str] = '@validator' 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

44 

45 fields: tuple[str, ...] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

46 mode: Literal['before', 'after'] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

47 each_item: bool 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

48 always: bool 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

49 check_fields: bool | None 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

50 

51 

52@dataclass(**slots_true) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

53class FieldValidatorDecoratorInfo: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

54 """A container for data from `@field_validator` so that we can access it 

55 while building the pydantic-core schema. 

56 

57 Attributes: 

58 decorator_repr: A class variable representing the decorator string, '@field_validator'. 

59 fields: A tuple of field names the validator should be called on. 

60 mode: The proposed validator mode. 

61 check_fields: Whether to check that the fields actually exist on the model. 

62 json_schema_input_type: The input type of the function. This is only used to generate 

63 the appropriate JSON Schema (in validation mode) and can only specified 

64 when `mode` is either `'before'`, `'plain'` or `'wrap'`. 

65 """ 

66 

67 decorator_repr: ClassVar[str] = '@field_validator' 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

68 

69 fields: tuple[str, ...] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

70 mode: FieldValidatorModes 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

71 check_fields: bool | None 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

72 json_schema_input_type: Any 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

73 

74 

75@dataclass(**slots_true) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

76class RootValidatorDecoratorInfo: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

77 """A container for data from `@root_validator` so that we can access it 

78 while building the pydantic-core schema. 

79 

80 Attributes: 

81 decorator_repr: A class variable representing the decorator string, '@root_validator'. 

82 mode: The proposed validator mode. 

83 """ 

84 

85 decorator_repr: ClassVar[str] = '@root_validator' 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

86 mode: Literal['before', 'after'] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

87 

88 

89@dataclass(**slots_true) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

90class FieldSerializerDecoratorInfo: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

91 """A container for data from `@field_serializer` so that we can access it 

92 while building the pydantic-core schema. 

93 

94 Attributes: 

95 decorator_repr: A class variable representing the decorator string, '@field_serializer'. 

96 fields: A tuple of field names the serializer should be called on. 

97 mode: The proposed serializer mode. 

98 return_type: The type of the serializer's return value. 

99 when_used: The serialization condition. Accepts a string with values `'always'`, `'unless-none'`, `'json'`, 

100 and `'json-unless-none'`. 

101 check_fields: Whether to check that the fields actually exist on the model. 

102 """ 

103 

104 decorator_repr: ClassVar[str] = '@field_serializer' 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

105 fields: tuple[str, ...] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

106 mode: Literal['plain', 'wrap'] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

107 return_type: Any 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

108 when_used: core_schema.WhenUsed 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

109 check_fields: bool | None 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

110 

111 

112@dataclass(**slots_true) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

113class ModelSerializerDecoratorInfo: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

114 """A container for data from `@model_serializer` so that we can access it 

115 while building the pydantic-core schema. 

116 

117 Attributes: 

118 decorator_repr: A class variable representing the decorator string, '@model_serializer'. 

119 mode: The proposed serializer mode. 

120 return_type: The type of the serializer's return value. 

121 when_used: The serialization condition. Accepts a string with values `'always'`, `'unless-none'`, `'json'`, 

122 and `'json-unless-none'`. 

123 """ 

124 

125 decorator_repr: ClassVar[str] = '@model_serializer' 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

126 mode: Literal['plain', 'wrap'] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

127 return_type: Any 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

128 when_used: core_schema.WhenUsed 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

129 

130 

131@dataclass(**slots_true) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

132class ModelValidatorDecoratorInfo: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

133 """A container for data from `@model_validator` so that we can access it 

134 while building the pydantic-core schema. 

135 

136 Attributes: 

137 decorator_repr: A class variable representing the decorator string, '@model_validator'. 

138 mode: The proposed serializer mode. 

139 """ 

140 

141 decorator_repr: ClassVar[str] = '@model_validator' 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

142 mode: Literal['wrap', 'before', 'after'] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

143 

144 

145DecoratorInfo: TypeAlias = """Union[ 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

146 ValidatorDecoratorInfo, 

147 FieldValidatorDecoratorInfo, 

148 RootValidatorDecoratorInfo, 

149 FieldSerializerDecoratorInfo, 

150 ModelSerializerDecoratorInfo, 

151 ModelValidatorDecoratorInfo, 

152 ComputedFieldInfo, 

153]""" 

154 

155ReturnType = TypeVar('ReturnType') 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

156DecoratedType: TypeAlias = ( 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

157 'Union[classmethod[Any, Any, ReturnType], staticmethod[Any, ReturnType], Callable[..., ReturnType], property]' 

158) 

159 

160 

161@dataclass # can't use slots here since we set attributes on `__post_init__` 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

162class PydanticDescriptorProxy(Generic[ReturnType]): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

163 """Wrap a classmethod, staticmethod, property or unbound function 

164 and act as a descriptor that allows us to detect decorated items 

165 from the class' attributes. 

166 

167 This class' __get__ returns the wrapped item's __get__ result, 

168 which makes it transparent for classmethods and staticmethods. 

169 

170 Attributes: 

171 wrapped: The decorator that has to be wrapped. 

172 decorator_info: The decorator info. 

173 shim: A wrapper function to wrap V1 style function. 

174 """ 

175 

176 wrapped: DecoratedType[ReturnType] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

177 decorator_info: DecoratorInfo 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

178 shim: Callable[[Callable[..., Any]], Callable[..., Any]] | None = None 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

179 

180 def __post_init__(self): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

181 for attr in 'setter', 'deleter': 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

182 if hasattr(self.wrapped, attr): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

183 f = partial(self._call_wrapped_attr, name=attr) 1stabcdefABzyuvghijklCDwxmnopqrEF

184 setattr(self, attr, f) 1stabcdefABzyuvghijklCDwxmnopqrEF

185 

186 def _call_wrapped_attr(self, func: Callable[[Any], None], *, name: str) -> PydanticDescriptorProxy[ReturnType]: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

187 self.wrapped = getattr(self.wrapped, name)(func) 1stabcdefABzyuvghijklCDwxmnopqrEF

188 if isinstance(self.wrapped, property): 188 ↛ 194line 188 didn't jump to line 194 because the condition on line 188 was always true1stabcdefABzyuvghijklCDwxmnopqrEF

189 # update ComputedFieldInfo.wrapped_property 

190 from ..fields import ComputedFieldInfo 1stabcdefABzyuvghijklCDwxmnopqrEF

191 

192 if isinstance(self.decorator_info, ComputedFieldInfo): 192 ↛ 194line 192 didn't jump to line 194 because the condition on line 192 was always true1stabcdefABzyuvghijklCDwxmnopqrEF

193 self.decorator_info.wrapped_property = self.wrapped 1stabcdefABzyuvghijklCDwxmnopqrEF

194 return self 1stabcdefABzyuvghijklCDwxmnopqrEF

195 

196 def __get__(self, obj: object | None, obj_type: type[object] | None = None) -> PydanticDescriptorProxy[ReturnType]: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

197 try: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

198 return self.wrapped.__get__(obj, obj_type) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

199 except AttributeError: 1stabcdefzyuvghijklwxmnopqr

200 # not a descriptor, e.g. a partial object 

201 return self.wrapped # type: ignore[return-value] 1stabcdefzyuvghijklwxmnopqr

202 

203 def __set_name__(self, instance: Any, name: str) -> None: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

204 if hasattr(self.wrapped, '__set_name__'): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

205 self.wrapped.__set_name__(instance, name) # pyright: ignore[reportFunctionMemberAccess] 1stabcdefABzyuvghijklCDwxmnopqrEF

206 

207 def __getattr__(self, name: str, /) -> Any: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

208 """Forward checks for __isabstractmethod__ and such.""" 

209 return getattr(self.wrapped, name) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

210 

211 

212DecoratorInfoType = TypeVar('DecoratorInfoType', bound=DecoratorInfo) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

213 

214 

215@dataclass(**slots_true) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

216class Decorator(Generic[DecoratorInfoType]): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

217 """A generic container class to join together the decorator metadata 

218 (metadata from decorator itself, which we have when the 

219 decorator is called but not when we are building the core-schema) 

220 and the bound function (which we have after the class itself is created). 

221 

222 Attributes: 

223 cls_ref: The class ref. 

224 cls_var_name: The decorated function name. 

225 func: The decorated function. 

226 shim: A wrapper function to wrap V1 style function. 

227 info: The decorator info. 

228 """ 

229 

230 cls_ref: str 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

231 cls_var_name: str 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

232 func: Callable[..., Any] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

233 shim: Callable[[Any], Any] | None 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

234 info: DecoratorInfoType 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

235 

236 @staticmethod 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

237 def build( 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

238 cls_: Any, 

239 *, 

240 cls_var_name: str, 

241 shim: Callable[[Any], Any] | None, 

242 info: DecoratorInfoType, 

243 ) -> Decorator[DecoratorInfoType]: 

244 """Build a new decorator. 

245 

246 Args: 

247 cls_: The class. 

248 cls_var_name: The decorated function name. 

249 shim: A wrapper function to wrap V1 style function. 

250 info: The decorator info. 

251 

252 Returns: 

253 The new decorator instance. 

254 """ 

255 func = get_attribute_from_bases(cls_, cls_var_name) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

256 if shim is not None: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

257 func = shim(func) 1stabcdefABzyuvghijklCDwxmnopqrEF

258 func = unwrap_wrapped_function(func, unwrap_partial=False) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

259 if not callable(func): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

260 # This branch will get hit for classmethod properties 

261 attribute = get_attribute_from_base_dicts(cls_, cls_var_name) # prevents the binding call to `__get__` 1stabcdefzyuvghijklwxmnopqr

262 if isinstance(attribute, PydanticDescriptorProxy): 262 ↛ 264line 262 didn't jump to line 264 because the condition on line 262 was always true1stabcdefzyuvghijklwxmnopqr

263 func = unwrap_wrapped_function(attribute.wrapped) 1stabcdefzyuvghijklwxmnopqr

264 return Decorator( 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

265 cls_ref=get_type_ref(cls_), 

266 cls_var_name=cls_var_name, 

267 func=func, 

268 shim=shim, 

269 info=info, 

270 ) 

271 

272 def bind_to_cls(self, cls: Any) -> Decorator[DecoratorInfoType]: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

273 """Bind the decorator to a class. 

274 

275 Args: 

276 cls: the class. 

277 

278 Returns: 

279 The new decorator instance. 

280 """ 

281 return self.build( 1stabcdefABzyuvghijklCDwxmnopqrEF

282 cls, 

283 cls_var_name=self.cls_var_name, 

284 shim=self.shim, 

285 info=self.info, 

286 ) 

287 

288 

289def get_bases(tp: type[Any]) -> tuple[type[Any], ...]: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

290 """Get the base classes of a class or typeddict. 

291 

292 Args: 

293 tp: The type or class to get the bases. 

294 

295 Returns: 

296 The base classes. 

297 """ 

298 if is_typeddict(tp): 1stabcdefABzyuvghijklCDwxmnopqrEF

299 return tp.__orig_bases__ # type: ignore 1stabcdefABzyuvghijklCDwxmnopqrEF

300 try: 1stabcdefABzyuvghijklCDwxmnopqrEF

301 return tp.__bases__ 1stabcdefABzyuvghijklCDwxmnopqrEF

302 except AttributeError: 1stabcdefABzyuvghijklCDwxmnopqrEF

303 return () 1stabcdefABzyuvghijklCDwxmnopqrEF

304 

305 

306def mro(tp: type[Any]) -> tuple[type[Any], ...]: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

307 """Calculate the Method Resolution Order of bases using the C3 algorithm. 

308 

309 See https://www.python.org/download/releases/2.3/mro/ 

310 """ 

311 # try to use the existing mro, for performance mainly 

312 # but also because it helps verify the implementation below 

313 if not is_typeddict(tp): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

314 try: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

315 return tp.__mro__ 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

316 except AttributeError: 1stabcdefABzyuvghijklCDwxmnopqrEF

317 # GenericAlias and some other cases 

318 pass 1stabcdefABzyuvghijklCDwxmnopqrEF

319 

320 bases = get_bases(tp) 1stabcdefABzyuvghijklCDwxmnopqrEF

321 return (tp,) + mro_for_bases(bases) 1stabcdefABzyuvghijklCDwxmnopqrEF

322 

323 

324def mro_for_bases(bases: tuple[type[Any], ...]) -> tuple[type[Any], ...]: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

325 def merge_seqs(seqs: list[deque[type[Any]]]) -> Iterable[type[Any]]: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

326 while True: 1abcdefAByghijklCDGHIJKLmnopqrEF

327 non_empty = [seq for seq in seqs if seq] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

328 if not non_empty: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

329 # Nothing left to process, we're done. 

330 return 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

331 candidate: type[Any] | None = None 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

332 for seq in non_empty: # Find merge candidates among seq heads. 332 ↛ 340line 332 didn't jump to line 340 because the loop on line 332 didn't complete1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

333 candidate = seq[0] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

334 not_head = [s for s in non_empty if candidate in islice(s, 1, None)] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

335 if not_head: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

336 # Reject the candidate. 

337 candidate = None 1stabcdefABzyuvghijklCDwxmnopqrEF

338 else: 

339 break 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

340 if not candidate: 340 ↛ 341line 340 didn't jump to line 341 because the condition on line 340 was never true1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

341 raise TypeError('Inconsistent hierarchy, no C3 MRO is possible') 

342 yield candidate 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

343 for seq in non_empty: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

344 # Remove candidate. 

345 if seq[0] == candidate: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

346 seq.popleft() 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

347 

348 seqs = [deque(mro(base)) for base in bases] + [deque(bases)] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

349 return tuple(merge_seqs(seqs)) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

350 

351 

352_sentinel = object() 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

353 

354 

355def get_attribute_from_bases(tp: type[Any] | tuple[type[Any], ...], name: str) -> Any: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

356 """Get the attribute from the next class in the MRO that has it, 

357 aiming to simulate calling the method on the actual class. 

358 

359 The reason for iterating over the mro instead of just getting 

360 the attribute (which would do that for us) is to support TypedDict, 

361 which lacks a real __mro__, but can have a virtual one constructed 

362 from its bases (as done here). 

363 

364 Args: 

365 tp: The type or class to search for the attribute. If a tuple, this is treated as a set of base classes. 

366 name: The name of the attribute to retrieve. 

367 

368 Returns: 

369 Any: The attribute value, if found. 

370 

371 Raises: 

372 AttributeError: If the attribute is not found in any class in the MRO. 

373 """ 

374 if isinstance(tp, tuple): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

375 for base in mro_for_bases(tp): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

376 attribute = base.__dict__.get(name, _sentinel) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

377 if attribute is not _sentinel: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

378 attribute_get = getattr(attribute, '__get__', None) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

379 if attribute_get is not None: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

380 return attribute_get(None, tp) 1stabcdefABzyuvghijklCDwxmnopqrEF

381 return attribute 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

382 raise AttributeError(f'{name} not found in {tp}') 1stabcdefABzyuvghijklCDwxmnopqrEF

383 else: 

384 try: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

385 return getattr(tp, name) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

386 except AttributeError: 1stabcdefABzyuvghijklCDwxmnopqrEF

387 return get_attribute_from_bases(mro(tp), name) 1stabcdefABzyuvghijklCDwxmnopqrEF

388 

389 

390def get_attribute_from_base_dicts(tp: type[Any], name: str) -> Any: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

391 """Get an attribute out of the `__dict__` following the MRO. 

392 This prevents the call to `__get__` on the descriptor, and allows 

393 us to get the original function for classmethod properties. 

394 

395 Args: 

396 tp: The type or class to search for the attribute. 

397 name: The name of the attribute to retrieve. 

398 

399 Returns: 

400 Any: The attribute value, if found. 

401 

402 Raises: 

403 KeyError: If the attribute is not found in any class's `__dict__` in the MRO. 

404 """ 

405 for base in reversed(mro(tp)): 405 ↛ 408line 405 didn't jump to line 408 because the loop on line 405 didn't complete1stabcdefzyuvghijklwxmnopqr

406 if name in base.__dict__: 1stabcdefzyuvghijklwxmnopqr

407 return base.__dict__[name] 1stabcdefzyuvghijklwxmnopqr

408 return tp.__dict__[name] # raise the error 

409 

410 

411@dataclass(**slots_true) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

412class DecoratorInfos: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

413 """Mapping of name in the class namespace to decorator info. 

414 

415 note that the name in the class namespace is the function or attribute name 

416 not the field name! 

417 """ 

418 

419 validators: dict[str, Decorator[ValidatorDecoratorInfo]] = field(default_factory=dict) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

420 field_validators: dict[str, Decorator[FieldValidatorDecoratorInfo]] = field(default_factory=dict) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

421 root_validators: dict[str, Decorator[RootValidatorDecoratorInfo]] = field(default_factory=dict) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

422 field_serializers: dict[str, Decorator[FieldSerializerDecoratorInfo]] = field(default_factory=dict) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

423 model_serializers: dict[str, Decorator[ModelSerializerDecoratorInfo]] = field(default_factory=dict) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

424 model_validators: dict[str, Decorator[ModelValidatorDecoratorInfo]] = field(default_factory=dict) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

425 computed_fields: dict[str, Decorator[ComputedFieldInfo]] = field(default_factory=dict) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

426 

427 @staticmethod 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

428 def build(model_dc: type[Any]) -> DecoratorInfos: # noqa: C901 (ignore complexity) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

429 """We want to collect all DecFunc instances that exist as 

430 attributes in the namespace of the class (a BaseModel or dataclass) 

431 that called us 

432 But we want to collect these in the order of the bases 

433 So instead of getting them all from the leaf class (the class that called us), 

434 we traverse the bases from root (the oldest ancestor class) to leaf 

435 and collect all of the instances as we go, taking care to replace 

436 any duplicate ones with the last one we see to mimic how function overriding 

437 works with inheritance. 

438 If we do replace any functions we put the replacement into the position 

439 the replaced function was in; that is, we maintain the order. 

440 """ 

441 # reminder: dicts are ordered and replacement does not alter the order 

442 res = DecoratorInfos() 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

443 for base in reversed(mro(model_dc)[1:]): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

444 existing: DecoratorInfos | None = base.__dict__.get('__pydantic_decorators__') 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

445 if existing is None: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

446 existing = DecoratorInfos.build(base) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

447 res.validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.validators.items()}) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

448 res.field_validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.field_validators.items()}) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

449 res.root_validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.root_validators.items()}) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

450 res.field_serializers.update({k: v.bind_to_cls(model_dc) for k, v in existing.field_serializers.items()}) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

451 res.model_serializers.update({k: v.bind_to_cls(model_dc) for k, v in existing.model_serializers.items()}) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

452 res.model_validators.update({k: v.bind_to_cls(model_dc) for k, v in existing.model_validators.items()}) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

453 res.computed_fields.update({k: v.bind_to_cls(model_dc) for k, v in existing.computed_fields.items()}) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

454 

455 to_replace: list[tuple[str, Any]] = [] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

456 

457 for var_name, var_value in vars(model_dc).items(): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

458 if isinstance(var_value, PydanticDescriptorProxy): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

459 info = var_value.decorator_info 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

460 if isinstance(info, ValidatorDecoratorInfo): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

461 res.validators[var_name] = Decorator.build( 1stabcdefABzyuvghijklCDwxmnopqrEF

462 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info 

463 ) 

464 elif isinstance(info, FieldValidatorDecoratorInfo): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

465 res.field_validators[var_name] = Decorator.build( 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

466 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info 

467 ) 

468 elif isinstance(info, RootValidatorDecoratorInfo): 1stabcdefABzyuvghijklCDwxmnopqrEF

469 res.root_validators[var_name] = Decorator.build( 1stabcdefABzyuvghijklCDwxmnopqrEF

470 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info 

471 ) 

472 elif isinstance(info, FieldSerializerDecoratorInfo): 1stabcdefABzyuvghijklCDwxmnopqrEF

473 # check whether a serializer function is already registered for fields 

474 for field_serializer_decorator in res.field_serializers.values(): 1stabcdefABzyuvghijklCDwxmnopqrEF

475 # check that each field has at most one serializer function. 

476 # serializer functions for the same field in subclasses are allowed, 

477 # and are treated as overrides 

478 if field_serializer_decorator.cls_var_name == var_name: 1stabcdefABzyuvghijklCDwxmnopqrEF

479 continue 1stabcdefABzyuvghijklCDwxmnopqrEF

480 for f in info.fields: 1stabcdefABzyuvghijklCDwxmnopqrEF

481 if f in field_serializer_decorator.info.fields: 1stabcdefABzyuvghijklCDwxmnopqrEF

482 raise PydanticUserError( 1stabcdefABzyuvghijklCDwxmnopqrEF

483 'Multiple field serializer functions were defined ' 

484 f'for field {f!r}, this is not allowed.', 

485 code='multiple-field-serializers', 

486 ) 

487 res.field_serializers[var_name] = Decorator.build( 1stabcdefABzyuvghijklCDwxmnopqrEF

488 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info 

489 ) 

490 elif isinstance(info, ModelValidatorDecoratorInfo): 1stabcdefABzyuvghijklCDwxmnopqrEF

491 res.model_validators[var_name] = Decorator.build( 1stabcdefABzyuvghijklCDwxmnopqrEF

492 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info 

493 ) 

494 elif isinstance(info, ModelSerializerDecoratorInfo): 1stabcdefABzyuvghijklCDwxmnopqrEF

495 res.model_serializers[var_name] = Decorator.build( 1stabcdefABzyuvghijklCDwxmnopqrEF

496 model_dc, cls_var_name=var_name, shim=var_value.shim, info=info 

497 ) 

498 else: 

499 from ..fields import ComputedFieldInfo 1stabcdefABzyuvghijklCDwxmnopqrEF

500 

501 isinstance(var_value, ComputedFieldInfo) 1stabcdefABzyuvghijklCDwxmnopqrEF

502 res.computed_fields[var_name] = Decorator.build( 1stabcdefABzyuvghijklCDwxmnopqrEF

503 model_dc, cls_var_name=var_name, shim=None, info=info 

504 ) 

505 to_replace.append((var_name, var_value.wrapped)) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

506 if to_replace: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

507 # If we can save `__pydantic_decorators__` on the class we'll be able to check for it above 

508 # so then we don't need to re-process the type, which means we can discard our descriptor wrappers 

509 # and replace them with the thing they are wrapping (see the other setattr call below) 

510 # which allows validator class methods to also function as regular class methods 

511 model_dc.__pydantic_decorators__ = res 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

512 for name, value in to_replace: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

513 setattr(model_dc, name, value) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

514 return res 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

515 

516 

517def inspect_validator(validator: Callable[..., Any], mode: FieldValidatorModes) -> bool: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

518 """Look at a field or model validator function and determine whether it takes an info argument. 

519 

520 An error is raised if the function has an invalid signature. 

521 

522 Args: 

523 validator: The validator function to inspect. 

524 mode: The proposed validator mode. 

525 

526 Returns: 

527 Whether the validator takes an info argument. 

528 """ 

529 try: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

530 sig = signature(validator) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

531 except (ValueError, TypeError): 1stabcdefABuvghijklCDwxmnopqrEF

532 # `inspect.signature` might not be able to infer a signature, e.g. with C objects. 

533 # In this case, we assume no info argument is present: 

534 return False 1stabcdefABuvghijklCDwxmnopqrEF

535 n_positional = count_positional_required_params(sig) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

536 if mode == 'wrap': 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

537 if n_positional == 3: 1stabcdefABzyuvghijklCDwxmnopqrEF

538 return True 1stabcdefABzyuvghijklCDwxmnopqrEF

539 elif n_positional == 2: 1stabcdefABzyuvghijklCDwxmnopqrEF

540 return False 1stabcdefABzyuvghijklCDwxmnopqrEF

541 else: 

542 assert mode in {'before', 'after', 'plain'}, f"invalid mode: {mode!r}, expected 'before', 'after' or 'plain" 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

543 if n_positional == 2: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

544 return True 1stabcdefABzyuvghijklCDwxmnopqrEF

545 elif n_positional == 1: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

546 return False 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

547 

548 raise PydanticUserError( 1stabcdefABzyuvghijklCDwxmnopqrEF

549 f'Unrecognized field_validator function signature for {validator} with `mode={mode}`:{sig}', 

550 code='validator-signature', 

551 ) 

552 

553 

554def inspect_field_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> tuple[bool, bool]: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

555 """Look at a field serializer function and determine if it is a field serializer, 

556 and whether it takes an info argument. 

557 

558 An error is raised if the function has an invalid signature. 

559 

560 Args: 

561 serializer: The serializer function to inspect. 

562 mode: The serializer mode, either 'plain' or 'wrap'. 

563 

564 Returns: 

565 Tuple of (is_field_serializer, info_arg). 

566 """ 

567 try: 1stabcdefABzyuvghijklCDwxmnopqrEF

568 sig = signature(serializer) 1stabcdefABzyuvghijklCDwxmnopqrEF

569 except (ValueError, TypeError): 

570 # `inspect.signature` might not be able to infer a signature, e.g. with C objects. 

571 # In this case, we assume no info argument is present and this is not a method: 

572 return (False, False) 

573 

574 first = next(iter(sig.parameters.values()), None) 1stabcdefABzyuvghijklCDwxmnopqrEF

575 is_field_serializer = first is not None and first.name == 'self' 1stabcdefABzyuvghijklCDwxmnopqrEF

576 

577 n_positional = count_positional_required_params(sig) 1stabcdefABzyuvghijklCDwxmnopqrEF

578 if is_field_serializer: 1stabcdefABzyuvghijklCDwxmnopqrEF

579 # -1 to correct for self parameter 

580 info_arg = _serializer_info_arg(mode, n_positional - 1) 1stabcdefABzyuvghijklCDwxmnopqrEF

581 else: 

582 info_arg = _serializer_info_arg(mode, n_positional) 1stabcdefABzyuvghijklCDwxmnopqrEF

583 

584 if info_arg is None: 1stabcdefABzyuvghijklCDwxmnopqrEF

585 raise PydanticUserError( 1stabcdefABzyuvghijklCDwxmnopqrEF

586 f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}', 

587 code='field-serializer-signature', 

588 ) 

589 

590 return is_field_serializer, info_arg 1stabcdefABzyuvghijklCDwxmnopqrEF

591 

592 

593def inspect_annotated_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

594 """Look at a serializer function used via `Annotated` and determine whether it takes an info argument. 

595 

596 An error is raised if the function has an invalid signature. 

597 

598 Args: 

599 serializer: The serializer function to check. 

600 mode: The serializer mode, either 'plain' or 'wrap'. 

601 

602 Returns: 

603 info_arg 

604 """ 

605 try: 1stabcdefABzyuvghijklCDwxmnopqrEF

606 sig = signature(serializer) 1stabcdefABzyuvghijklCDwxmnopqrEF

607 except (ValueError, TypeError): 1stabcdefABuvghijklCDwxmnopqrEF

608 # `inspect.signature` might not be able to infer a signature, e.g. with C objects. 

609 # In this case, we assume no info argument is present: 

610 return False 1stabcdefABuvghijklCDwxmnopqrEF

611 info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) 1stabcdefABzyuvghijklCDwxmnopqrEF

612 if info_arg is None: 1stabcdefABzyuvghijklCDwxmnopqrEF

613 raise PydanticUserError( 1stabcdefABzyuvghijklCDwxmnopqrEF

614 f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}', 

615 code='field-serializer-signature', 

616 ) 

617 else: 

618 return info_arg 1stabcdefABzyuvghijklCDwxmnopqrEF

619 

620 

621def inspect_model_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

622 """Look at a model serializer function and determine whether it takes an info argument. 

623 

624 An error is raised if the function has an invalid signature. 

625 

626 Args: 

627 serializer: The serializer function to check. 

628 mode: The serializer mode, either 'plain' or 'wrap'. 

629 

630 Returns: 

631 `info_arg` - whether the function expects an info argument. 

632 """ 

633 if isinstance(serializer, (staticmethod, classmethod)) or not is_instance_method_from_sig(serializer): 1stabcdefABzyuvghijklCDwxmnopqrEF

634 raise PydanticUserError( 1stabcdefABzyuvghijklCDwxmnopqrEF

635 '`@model_serializer` must be applied to instance methods', code='model-serializer-instance-method' 

636 ) 

637 

638 sig = signature(serializer) 1stabcdefABzyuvghijklCDwxmnopqrEF

639 info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) 1stabcdefABzyuvghijklCDwxmnopqrEF

640 if info_arg is None: 1stabcdefABzyuvghijklCDwxmnopqrEF

641 raise PydanticUserError( 1stabcdefABzyuvghijklCDwxmnopqrEF

642 f'Unrecognized model_serializer function signature for {serializer} with `mode={mode}`:{sig}', 

643 code='model-serializer-signature', 

644 ) 

645 else: 

646 return info_arg 1stabcdefABzyuvghijklCDwxmnopqrEF

647 

648 

649def _serializer_info_arg(mode: Literal['plain', 'wrap'], n_positional: int) -> bool | None: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

650 if mode == 'plain': 1stabcdefABzyuvghijklCDwxmnopqrEF

651 if n_positional == 1: 1stabcdefABzyuvghijklCDwxmnopqrEF

652 # (input_value: Any, /) -> Any 

653 return False 1stabcdefABzyuvghijklCDwxmnopqrEF

654 elif n_positional == 2: 1stabcdefABzyuvghijklCDwxmnopqrEF

655 # (model: Any, input_value: Any, /) -> Any 

656 return True 1stabcdefABzyuvghijklCDwxmnopqrEF

657 else: 

658 assert mode == 'wrap', f"invalid mode: {mode!r}, expected 'plain' or 'wrap'" 1stabcdefABzyuvghijklCDwxmnopqrEF

659 if n_positional == 2: 1stabcdefABzyuvghijklCDwxmnopqrEF

660 # (input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any 

661 return False 1stabcdefABzyuvghijklCDwxmnopqrEF

662 elif n_positional == 3: 1stabcdefABzyuvghijklCDwxmnopqrEF

663 # (input_value: Any, serializer: SerializerFunctionWrapHandler, info: SerializationInfo, /) -> Any 

664 return True 1stabcdefABzyuvghijklCDwxmnopqrEF

665 

666 return None 1stabcdefABzyuvghijklCDwxmnopqrEF

667 

668 

669AnyDecoratorCallable: TypeAlias = ( 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

670 'Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any], Callable[..., Any]]' 

671) 

672 

673 

674def is_instance_method_from_sig(function: AnyDecoratorCallable) -> bool: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

675 """Whether the function is an instance method. 

676 

677 It will consider a function as instance method if the first parameter of 

678 function is `self`. 

679 

680 Args: 

681 function: The function to check. 

682 

683 Returns: 

684 `True` if the function is an instance method, `False` otherwise. 

685 """ 

686 sig = signature(unwrap_wrapped_function(function)) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

687 first = next(iter(sig.parameters.values()), None) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

688 if first and first.name == 'self': 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

689 return True 1stabcdefABzyuvghijklCDwxmnopqrEF

690 return False 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

691 

692 

693def ensure_classmethod_based_on_signature(function: AnyDecoratorCallable) -> Any: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

694 """Apply the `@classmethod` decorator on the function. 

695 

696 Args: 

697 function: The function to apply the decorator on. 

698 

699 Return: 

700 The `@classmethod` decorator applied function. 

701 """ 

702 if not isinstance( 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

703 unwrap_wrapped_function(function, unwrap_class_static_method=False), classmethod 

704 ) and _is_classmethod_from_sig(function): 

705 return classmethod(function) # type: ignore[arg-type] 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

706 return function 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

707 

708 

709def _is_classmethod_from_sig(function: AnyDecoratorCallable) -> bool: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

710 sig = signature(unwrap_wrapped_function(function)) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

711 first = next(iter(sig.parameters.values()), None) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

712 if first and first.name == 'cls': 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

713 return True 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

714 return False 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

715 

716 

717def unwrap_wrapped_function( 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

718 func: Any, 

719 *, 

720 unwrap_partial: bool = True, 

721 unwrap_class_static_method: bool = True, 

722) -> Any: 

723 """Recursively unwraps a wrapped function until the underlying function is reached. 

724 This handles property, functools.partial, functools.partialmethod, staticmethod, and classmethod. 

725 

726 Args: 

727 func: The function to unwrap. 

728 unwrap_partial: If True (default), unwrap partial and partialmethod decorators. 

729 unwrap_class_static_method: If True (default), also unwrap classmethod and staticmethod 

730 decorators. If False, only unwrap partial and partialmethod decorators. 

731 

732 Returns: 

733 The underlying function of the wrapped function. 

734 """ 

735 # Define the types we want to check against as a single tuple. 

736 unwrap_types = ( 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

737 (property, cached_property) 

738 + ((partial, partialmethod) if unwrap_partial else ()) 

739 + ((staticmethod, classmethod) if unwrap_class_static_method else ()) 

740 ) 

741 

742 while isinstance(func, unwrap_types): 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

743 if unwrap_class_static_method and isinstance(func, (classmethod, staticmethod)): 1stabcdefABzyuvghijklCDwxmnopqrEF

744 func = func.__func__ 1stabcdefABzyuvghijklCDwxmnopqrEF

745 elif isinstance(func, (partial, partialmethod)): 1stabcdefABzyuvghijklCDwxmnopqrEF

746 func = func.func 1stabcdefABzyuvghijklCDwxmnopqrEF

747 elif isinstance(func, property): 1stabcdefABzyuvghijklCDwxmnopqrEF

748 func = func.fget # arbitrary choice, convenient for computed fields 1stabcdefABzyuvghijklCDwxmnopqrEF

749 else: 

750 # Make coverage happy as it can only get here in the last possible case 

751 assert isinstance(func, cached_property) 1stabcdefABzyuvghijklCDwxmnopqrEF

752 func = func.func # type: ignore 1stabcdefABzyuvghijklCDwxmnopqrEF

753 

754 return func 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

755 

756 

757def get_function_return_type( 1stabcdefABuvghijklCDMGHIJKLwxmnopqrEF

758 func: Any, 

759 explicit_return_type: Any, 

760 globalns: GlobalsNamespace | None = None, 

761 localns: MappingNamespace | None = None, 

762) -> Any: 

763 """Get the function return type. 

764 

765 It gets the return type from the type annotation if `explicit_return_type` is `None`. 

766 Otherwise, it returns `explicit_return_type`. 

767 

768 Args: 

769 func: The function to get its return type. 

770 explicit_return_type: The explicit return type. 

771 globalns: The globals namespace to use during type annotation evaluation. 

772 localns: The locals namespace to use during type annotation evaluation. 

773 

774 Returns: 

775 The function return type. 

776 """ 

777 if explicit_return_type is PydanticUndefined: 1stabcdefABzyuvghijklCDwxmnopqrEF

778 # try to get it from the type annotation 

779 hints = get_function_type_hints( 1stabcdefABzyuvghijklCDwxmnopqrEF

780 unwrap_wrapped_function(func), 

781 include_keys={'return'}, 

782 globalns=globalns, 

783 localns=localns, 

784 ) 

785 return hints.get('return', PydanticUndefined) 1stabcdefABzyuvghijklCDwxmnopqrEF

786 else: 

787 return explicit_return_type 1stabcdefABzyuvghijklCDwxmnopqrEF

788 

789 

790def count_positional_required_params(sig: Signature) -> int: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

791 """Get the number of positional (required) arguments of a signature. 

792 

793 This function should only be used to inspect signatures of validation and serialization functions. 

794 The first argument (the value being serialized or validated) is counted as a required argument 

795 even if a default value exists. 

796 

797 Returns: 

798 The number of positional arguments of a signature. 

799 """ 

800 parameters = list(sig.parameters.values()) 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

801 return sum( 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

802 1 

803 for param in parameters 

804 if can_be_positional(param) 

805 # First argument is the value being validated/serialized, and can have a default value 

806 # (e.g. `float`, which has signature `(x=0, /)`). We assume other parameters (the info arg 

807 # for instance) should be required, and thus without any default value. 

808 and (param.default is Parameter.empty or param is parameters[0]) 

809 ) 

810 

811 

812def ensure_property(f: Any) -> Any: 1stabcdefABzyuvghijklCDMGHIJKLwxmnopqrEF

813 """Ensure that a function is a `property` or `cached_property`, or is a valid descriptor. 

814 

815 Args: 

816 f: The function to check. 

817 

818 Returns: 

819 The function, or a `property` or `cached_property` instance wrapping the function. 

820 """ 

821 if ismethoddescriptor(f) or isdatadescriptor(f): 1stabcdefABzyuvghijklCDwxmnopqrEF

822 return f 1stabcdefABzyuvghijklCDwxmnopqrEF

823 else: 

824 return property(f) 1stabcdefABzyuvghijklCDwxmnopqrEF