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

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

2 

3from __future__ import annotations as _annotations 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

4 

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

11 

12from pydantic_core import PydanticUndefined, core_schema 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

13from typing_extensions import Literal, TypeAlias, is_typeddict 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

14 

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

19 

20if TYPE_CHECKING: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

21 from ..fields import ComputedFieldInfo 

22 from ..functional_validators import FieldValidatorModes 

23 

24 

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. 

29 

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 """ 

39 

40 decorator_repr: ClassVar[str] = '@validator' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

41 

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

47 

48 

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. 

53 

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 """ 

60 

61 decorator_repr: ClassVar[str] = '@field_validator' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

62 

63 fields: tuple[str, ...] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

64 mode: FieldValidatorModes 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

65 check_fields: bool | None 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

66 

67 

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. 

72 

73 Attributes: 

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

75 mode: The proposed validator mode. 

76 """ 

77 

78 decorator_repr: ClassVar[str] = '@root_validator' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

79 mode: Literal['before', 'after'] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

80 

81 

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. 

86 

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 """ 

96 

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

103 

104 

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. 

109 

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 """ 

117 

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

122 

123 

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. 

128 

129 Attributes: 

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

131 mode: The proposed serializer mode. 

132 """ 

133 

134 decorator_repr: ClassVar[str] = '@model_validator' 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

135 mode: Literal['wrap', 'before', 'after'] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

136 

137 

138DecoratorInfo: TypeAlias = """Union[ 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

139 ValidatorDecoratorInfo, 

140 FieldValidatorDecoratorInfo, 

141 RootValidatorDecoratorInfo, 

142 FieldSerializerDecoratorInfo, 

143 ModelSerializerDecoratorInfo, 

144 ModelValidatorDecoratorInfo, 

145 ComputedFieldInfo, 

146]""" 

147 

148ReturnType = TypeVar('ReturnType') 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

149DecoratedType: TypeAlias = ( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

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

151) 

152 

153 

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. 

159 

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

161 which makes it transparent for classmethods and staticmethods. 

162 

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 """ 

168 

169 wrapped: DecoratedType[ReturnType] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

170 decorator_info: DecoratorInfo 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

171 shim: Callable[[Callable[..., Any]], Callable[..., Any]] | None = None 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

172 

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

178 

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

182 

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

189 

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

193 

194 def __getattr__(self, __name: str) -> Any: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

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

196 return getattr(self.wrapped, __name) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

197 

198 

199DecoratorInfoType = TypeVar('DecoratorInfoType', bound=DecoratorInfo) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

200 

201 

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). 

208 

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 """ 

216 

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

222 

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. 

232 

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. 

238 

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 ) 

258 

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

260 """Bind the decorator to a class. 

261 

262 Args: 

263 cls: the class. 

264 

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 ) 

274 

275 

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

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

278 

279 Args: 

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

281 

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

291 

292 

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

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

295 

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

306 

307 bases = get_bases(tp) 1zAstabcdefByCDuvghijklEFwxmnopqr

308 return (tp,) + mro_for_bases(bases) 1zAstabcdefByCDuvghijklEFwxmnopqr

309 

310 

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

334 

335 seqs = [deque(mro(base)) for base in bases] + [deque(bases)] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

336 return tuple(merge_seqs(seqs)) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

337 

338 

339_sentinel = object() 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

340 

341 

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. 

345 

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). 

350 

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. 

354 

355 Returns: 

356 Any: The attribute value, if found. 

357 

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

375 

376 

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. 

381 

382 Args: 

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

384 name: The name of the attribute to retrieve. 

385 

386 Returns: 

387 Any: The attribute value, if found. 

388 

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 

396 

397 

398@dataclass(**slots_true) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

399class DecoratorInfos: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

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

401 

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

403 not the field name! 

404 """ 

405 

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

413 

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

441 

442 to_replace: list[tuple[str, Any]] = [] 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

443 

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

487 

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

502 

503 

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. 

506 

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

508 

509 Args: 

510 validator: The validator function to inspect. 

511 mode: The proposed validator mode. 

512 

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

534 

535 raise PydanticUserError( 1zAstabcdefByCDuvghijklEFwxmnopqr

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

537 code='validator-signature', 

538 ) 

539 

540 

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. 

546 

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

548 

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. 

554 

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) 

564 

565 first = next(iter(sig.parameters.values()), None) 1zAstabcdefByCDuvghijklEFwxmnopqr

566 is_field_serializer = first is not None and first.name == 'self' 1zAstabcdefByCDuvghijklEFwxmnopqr

567 

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

574 

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 ) 

584 

585 else: 

586 return is_field_serializer, info_arg 1zAstabcdefByCDuvghijklEFwxmnopqr

587 

588 

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. 

591 

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

593 

594 Args: 

595 serializer: The serializer function to check. 

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

597 

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

615 

616 

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. 

619 

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

621 

622 Args: 

623 serializer: The serializer function to check. 

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

625 

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 ) 

633 

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

643 

644 

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

661 

662 return None 1zAstabcdefByCDuvghijklEFwxmnopqr

663 

664 

665AnyDecoratorCallable: TypeAlias = ( 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

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

667) 

668 

669 

670def is_instance_method_from_sig(function: AnyDecoratorCallable) -> bool: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

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

672 

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

674 function is `self`. 

675 

676 Args: 

677 function: The function to check. 

678 

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

687 

688 

689def ensure_classmethod_based_on_signature(function: AnyDecoratorCallable) -> Any: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

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

691 

692 Args: 

693 function: The function to apply the decorator on. 

694 

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

703 

704 

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

711 

712 

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. 

721 

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. 

728 

729 Returns: 

730 The underlying function of the wrapped function. 

731 """ 

732 all: set[Any] = {property, cached_property} 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

733 

734 if unwrap_partial: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

735 all.update({partial, partialmethod}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

736 

737 if unwrap_class_static_method: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

738 all.update({staticmethod, classmethod}) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

739 

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

751 

752 return func 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

753 

754 

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. 

759 

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

761 Otherwise, it returns `explicit_return_type`. 

762 

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`. 

767 

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

779 

780 

781def count_positional_required_params(sig: Signature) -> int: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

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

783 

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. 

787 

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 ) 

801 

802 

803def can_be_positional(param: Parameter) -> bool: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

804 return param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

805 

806 

807def ensure_property(f: Any) -> Any: 1zAstabcdefByCDuvghijklNOGHIJKLMEFwxmnopqr

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

809 

810 Args: 

811 f: The function to check. 

812 

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