Coverage for pydantic/_internal/_typing_extra.py: 95.24%

199 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-20 16:49 +0000

1"""Logic for interacting with type annotations, mostly extensions, shims and hacks to wrap Python's typing module.""" 

2 

3from __future__ import annotations 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

4 

5import collections.abc 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

6import re 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

7import sys 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

8import types 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

9import typing 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

10from functools import partial 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

11from typing import TYPE_CHECKING, Any, Callable, cast 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

12 

13import typing_extensions 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

14from typing_extensions import deprecated, get_args, get_origin 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

15from typing_inspection import typing_objects 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

16from typing_inspection.introspection import is_union_origin 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

17 

18from pydantic.version import version_short 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

19 

20from ._namespace_utils import GlobalsNamespace, MappingNamespace, NsResolver, get_module_ns_of 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

21 

22if sys.version_info < (3, 10): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

23 NoneType = type(None) 1abgcdef

24 EllipsisType = type(Ellipsis) 1abgOcdef

25else: 

26 from types import EllipsisType as EllipsisType 1FGzAnopqhiLOHIBCrstujkMPJKDEvwxylmN

27 from types import NoneType as NoneType 1FGzAnopqhiLOHIBCrstujkMPJKDEvwxylmN

28 

29if sys.version_info >= (3, 14): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

30 import annotationlib 1hiLjkMlmN

31 

32if TYPE_CHECKING: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

33 from pydantic import BaseModel 

34 

35# As per https://typing-extensions.readthedocs.io/en/latest/#runtime-use-of-types, 

36# always check for both `typing` and `typing_extensions` variants of a typing construct. 

37# (this is implemented differently than the suggested approach in the `typing_extensions` 

38# docs for performance). 

39 

40 

41_t_annotated = typing.Annotated 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

42_te_annotated = typing_extensions.Annotated 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

43 

44 

45def is_annotated(tp: Any, /) -> bool: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

46 """Return whether the provided argument is a `Annotated` special form. 

47 

48 ```python {test="skip" lint="skip"} 

49 is_annotated(Annotated[int, ...]) 

50 #> True 

51 ``` 

52 """ 

53 origin = get_origin(tp) 

54 return origin is _t_annotated or origin is _te_annotated 

55 

56 

57def annotated_type(tp: Any, /) -> Any | None: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

58 """Return the type of the `Annotated` special form, or `None`.""" 

59 return tp.__origin__ if typing_objects.is_annotated(get_origin(tp)) else None 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

60 

61 

62def unpack_type(tp: Any, /) -> Any | None: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

63 """Return the type wrapped by the `Unpack` special form, or `None`.""" 

64 return get_args(tp)[0] if typing_objects.is_unpack(get_origin(tp)) else None 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

65 

66 

67def is_hashable(tp: Any, /) -> bool: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

68 """Return whether the provided argument is the `Hashable` class. 

69 

70 ```python {test="skip" lint="skip"} 

71 is_hashable(Hashable) 

72 #> True 

73 ``` 

74 """ 

75 # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes, 

76 # hence the second check: 

77 return tp is collections.abc.Hashable or get_origin(tp) is collections.abc.Hashable 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

78 

79 

80def is_callable(tp: Any, /) -> bool: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

81 """Return whether the provided argument is a `Callable`, parametrized or not. 

82 

83 ```python {test="skip" lint="skip"} 

84 is_callable(Callable[[int], str]) 

85 #> True 

86 is_callable(typing.Callable) 

87 #> True 

88 is_callable(collections.abc.Callable) 

89 #> True 

90 ``` 

91 """ 

92 # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes, 

93 # hence the second check: 

94 return tp is collections.abc.Callable or get_origin(tp) is collections.abc.Callable 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

95 

96 

97_classvar_re = re.compile(r'((\w+\.)?Annotated\[)?(\w+\.)?ClassVar\[') 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

98 

99 

100def is_classvar_annotation(tp: Any, /) -> bool: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

101 """Return whether the provided argument represents a class variable annotation. 

102 

103 Although not explicitly stated by the typing specification, `ClassVar` can be used 

104 inside `Annotated` and as such, this function checks for this specific scenario. 

105 

106 Because this function is used to detect class variables before evaluating forward references 

107 (or because evaluation failed), we also implement a naive regex match implementation. This is 

108 required because class variables are inspected before fields are collected, so we try to be 

109 as accurate as possible. 

110 """ 

111 if typing_objects.is_classvar(tp): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

112 return True 1abFGzAnopqhigOcdHIBCrstujkefJKDEvwxylm

113 

114 origin = get_origin(tp) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

115 

116 if typing_objects.is_classvar(origin): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

117 return True 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

118 

119 if typing_objects.is_annotated(origin): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

120 annotated_type = tp.__origin__ 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

121 if typing_objects.is_classvar(annotated_type) or typing_objects.is_classvar(get_origin(annotated_type)): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

122 return True 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

123 

124 str_ann: str | None = None 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

125 if isinstance(tp, typing.ForwardRef): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

126 str_ann = tp.__forward_arg__ 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

127 if isinstance(tp, str): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

128 str_ann = tp 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

129 

130 if str_ann is not None and _classvar_re.match(str_ann): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

131 # stdlib dataclasses do something similar, although a bit more advanced 

132 # (see `dataclass._is_type`). 

133 return True 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

134 

135 return False 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

136 

137 

138_t_final = typing.Final 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

139_te_final = typing_extensions.Final 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

140 

141 

142# TODO implement `is_finalvar_annotation` as Final can be wrapped with other special forms: 

143def is_finalvar(tp: Any, /) -> bool: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

144 """Return whether the provided argument is a `Final` special form, parametrized or not. 

145 

146 ```python {test="skip" lint="skip"} 

147 is_finalvar(Final[int]) 

148 #> True 

149 is_finalvar(Final) 

150 #> True 

151 """ 

152 # Final is not necessarily parametrized: 

153 if tp is _t_final or tp is _te_final: 153 ↛ 154line 153 didn't jump to line 154 because the condition on line 153 was never true1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

154 return True 

155 origin = get_origin(tp) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

156 return origin is _t_final or origin is _te_final 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

157 

158 

159_NONE_TYPES: tuple[Any, ...] = (None, NoneType, typing.Literal[None], typing_extensions.Literal[None]) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

160 

161 

162def is_none_type(tp: Any, /) -> bool: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

163 """Return whether the argument represents the `None` type as part of an annotation. 

164 

165 ```python {test="skip" lint="skip"} 

166 is_none_type(None) 

167 #> True 

168 is_none_type(NoneType) 

169 #> True 

170 is_none_type(Literal[None]) 

171 #> True 

172 is_none_type(type[None]) 

173 #> False 

174 """ 

175 return tp in _NONE_TYPES 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

176 

177 

178def is_namedtuple(tp: Any, /) -> bool: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

179 """Return whether the provided argument is a named tuple class. 

180 

181 The class can be created using `typing.NamedTuple` or `collections.namedtuple`. 

182 Parametrized generic classes are *not* assumed to be named tuples. 

183 """ 

184 from ._utils import lenient_issubclass # circ. import 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

185 

186 return lenient_issubclass(tp, tuple) and hasattr(tp, '_fields') 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

187 

188 

189# TODO In 2.12, delete this export. It is currently defined only to not break 

190# pydantic-settings which relies on it: 

191origin_is_union = is_union_origin 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

192 

193 

194def is_generic_alias(tp: Any, /) -> bool: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

195 return isinstance(tp, (types.GenericAlias, typing._GenericAlias)) # pyright: ignore[reportAttributeAccessIssue] 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

196 

197 

198# TODO: Ideally, we should avoid relying on the private `typing` constructs: 

199 

200if sys.version_info < (3, 10): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

201 WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # pyright: ignore[reportAttributeAccessIssue] 1abgcdef

202else: 

203 WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias, types.UnionType) # pyright: ignore[reportAttributeAccessIssue] 1FGzAnopqhiLOHIBCrstujkMPJKDEvwxylmN

204 

205 

206# Similarly, we shouldn't rely on this `_Final` class, which is even more private than `_GenericAlias`: 

207typing_base: Any = typing._Final # pyright: ignore[reportAttributeAccessIssue] 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

208 

209 

210### Annotation evaluations functions: 

211 

212 

213def parent_frame_namespace(*, parent_depth: int = 2, force: bool = False) -> dict[str, Any] | None: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

214 """Fetch the local namespace of the parent frame where this function is called. 

215 

216 Using this function is mostly useful to resolve forward annotations pointing to members defined in a local namespace, 

217 such as assignments inside a function. Using the standard library tools, it is currently not possible to resolve 

218 such annotations: 

219 

220 ```python {lint="skip" test="skip"} 

221 from typing import get_type_hints 

222 

223 def func() -> None: 

224 Alias = int 

225 

226 class C: 

227 a: 'Alias' 

228 

229 # Raises a `NameError: 'Alias' is not defined` 

230 get_type_hints(C) 

231 ``` 

232 

233 Pydantic uses this function when a Pydantic model is being defined to fetch the parent frame locals. However, 

234 this only allows us to fetch the parent frame namespace and not other parents (e.g. a model defined in a function, 

235 itself defined in another function). Inspecting the next outer frames (using `f_back`) is not reliable enough 

236 (see https://discuss.python.org/t/20659). 

237 

238 Because this function is mostly used to better resolve forward annotations, nothing is returned if the parent frame's 

239 code object is defined at the module level. In this case, the locals of the frame will be the same as the module 

240 globals where the class is defined (see `_namespace_utils.get_module_ns_of`). However, if you still want to fetch 

241 the module globals (e.g. when rebuilding a model, where the frame where the rebuild call is performed might contain 

242 members that you want to use for forward annotations evaluation), you can use the `force` parameter. 

243 

244 Args: 

245 parent_depth: The depth at which to get the frame. Defaults to 2, meaning the parent frame where this function 

246 is called will be used. 

247 force: Whether to always return the frame locals, even if the frame's code object is defined at the module level. 

248 

249 Returns: 

250 The locals of the namespace, or `None` if it was skipped as per the described logic. 

251 """ 

252 frame = sys._getframe(parent_depth) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

253 

254 if frame.f_code.co_name.startswith('<generic parameters of'): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

255 # As `parent_frame_namespace` is mostly called in `ModelMetaclass.__new__`, 

256 # the parent frame can be the annotation scope if the PEP 695 generic syntax is used. 

257 # (see https://docs.python.org/3/reference/executionmodel.html#annotation-scopes, 

258 # https://docs.python.org/3/reference/compound_stmts.html#generic-classes). 

259 # In this case, the code name is set to `<generic parameters of MyClass>`, 

260 # and we need to skip this frame as it is irrelevant. 

261 frame = cast(types.FrameType, frame.f_back) # guaranteed to not be `None` 1nopqhiLrstujkMvwxylmN

262 

263 # note, we don't copy frame.f_locals here (or during the last return call), because we don't expect the namespace to be 

264 # modified down the line if this becomes a problem, we could implement some sort of frozen mapping structure to enforce this. 

265 if force: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

266 return frame.f_locals 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

267 

268 # If either of the following conditions are true, the class is defined at the top module level. 

269 # To better understand why we need both of these checks, see 

270 # https://github.com/pydantic/pydantic/pull/10113#discussion_r1714981531. 

271 if frame.f_back is None or frame.f_code.co_name == '<module>': 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

272 return None 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

273 

274 return frame.f_locals 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

275 

276 

277def _type_convert(arg: Any) -> Any: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

278 """Convert `None` to `NoneType` and strings to `ForwardRef` instances. 

279 

280 This is a backport of the private `typing._type_convert` function. When 

281 evaluating a type, `ForwardRef._evaluate` ends up being called, and is 

282 responsible for making this conversion. However, we still have to apply 

283 it for the first argument passed to our type evaluation functions, similarly 

284 to the `typing.get_type_hints` function. 

285 """ 

286 if arg is None: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

287 return NoneType 1abFGzAnopqhigOcdHIBCrstujkefJKDEvwxylm

288 if isinstance(arg, str): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

289 # Like `typing.get_type_hints`, assume the arg can be in any context, 

290 # hence the proper `is_argument` and `is_class` args: 

291 return _make_forward_ref(arg, is_argument=False, is_class=True) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

292 return arg 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

293 

294 

295def safe_get_annotations(cls: type[Any]) -> dict[str, Any]: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

296 """Get the annotations for the provided class, accounting for potential deferred forward references. 

297 

298 Starting with Python 3.14, accessing the `__annotations__` attribute might raise a `NameError` if 

299 a referenced symbol isn't defined yet. In this case, we return the annotation in the *forward ref* 

300 format. 

301 """ 

302 if sys.version_info >= (3, 14): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

303 return annotationlib.get_annotations(cls, format=annotationlib.Format.FORWARDREF) 1hiLjkMlmN

304 else: 

305 return cls.__dict__.get('__annotations__', {}) 1abFGzAnopqgOcdHIBCrstuPefJKDEvwxy

306 

307 

308def get_model_type_hints( 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

309 obj: type[BaseModel], 

310 *, 

311 ns_resolver: NsResolver | None = None, 

312) -> dict[str, tuple[Any, bool]]: 

313 """Collect annotations from a Pydantic model class, including those from parent classes. 

314 

315 Args: 

316 obj: The Pydantic model to inspect. 

317 ns_resolver: A namespace resolver instance to use. Defaults to an empty instance. 

318 

319 Returns: 

320 A dictionary mapping annotation names to a two-tuple: the first element is the evaluated 

321 type or the original annotation if a `NameError` occurred, the second element is a boolean 

322 indicating if whether the evaluation succeeded. 

323 """ 

324 hints: dict[str, Any] | dict[str, tuple[Any, bool]] = {} 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

325 ns_resolver = ns_resolver or NsResolver() 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

326 

327 for base in reversed(obj.__mro__): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

328 # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals 

329 # from the ns_resolver, but we want to be able to know which specific field failed 

330 # to evaluate: 

331 ann = safe_get_annotations(base) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

332 

333 if not ann: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

334 continue 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

335 

336 with ns_resolver.push(base): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

337 globalns, localns = ns_resolver.types_namespace 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

338 for name, value in ann.items(): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

339 if name.startswith('_'): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

340 # For private attributes, we only need the annotation to detect the `ClassVar` special form. 

341 # For this reason, we still try to evaluate it, but we also catch any possible exception (on 

342 # top of the `NameError`s caught in `try_eval_type`) that could happen so that users are free 

343 # to use any kind of forward annotation for private fields (e.g. circular imports, new typing 

344 # syntax, etc). 

345 try: 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

346 hints[name] = try_eval_type(value, globalns, localns) 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

347 except Exception: 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

348 hints[name] = (value, False) 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

349 else: 

350 hints[name] = try_eval_type(value, globalns, localns) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

351 return hints 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

352 

353 

354def get_cls_type_hints( 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

355 obj: type[Any], 

356 *, 

357 ns_resolver: NsResolver | None = None, 

358) -> dict[str, Any]: 

359 """Collect annotations from a class, including those from parent classes. 

360 

361 Args: 

362 obj: The class to inspect. 

363 ns_resolver: A namespace resolver instance to use. Defaults to an empty instance. 

364 """ 

365 hints: dict[str, Any] = {} 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

366 ns_resolver = ns_resolver or NsResolver() 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

367 

368 for base in reversed(obj.__mro__): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

369 # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals 

370 # from the ns_resolver, but we want to be able to know which specific field failed 

371 # to evaluate: 

372 ann = safe_get_annotations(base) 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

373 

374 if not ann: 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

375 continue 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

376 

377 with ns_resolver.push(base): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

378 globalns, localns = ns_resolver.types_namespace 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

379 for name, value in ann.items(): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

380 hints[name] = eval_type(value, globalns, localns) 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

381 return hints 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

382 

383 

384def try_eval_type( 1abFGzAnopqhiLcdHIBCrstujkMPefJKDEvwxylmN

385 value: Any, 

386 globalns: GlobalsNamespace | None = None, 

387 localns: MappingNamespace | None = None, 

388) -> tuple[Any, bool]: 

389 """Try evaluating the annotation using the provided namespaces. 

390 

391 Args: 

392 value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance 

393 of `str`, it will be converted to a `ForwardRef`. 

394 localns: The global namespace to use during annotation evaluation. 

395 globalns: The local namespace to use during annotation evaluation. 

396 

397 Returns: 

398 A two-tuple containing the possibly evaluated type and a boolean indicating 

399 whether the evaluation succeeded or not. 

400 """ 

401 value = _type_convert(value) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

402 

403 try: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

404 return eval_type_backport(value, globalns, localns), True 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

405 except NameError: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

406 return value, False 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

407 

408 

409def eval_type( 1abFGzAnopqhiLcdHIBCrstujkMPefJKDEvwxylmN

410 value: Any, 

411 globalns: GlobalsNamespace | None = None, 

412 localns: MappingNamespace | None = None, 

413) -> Any: 

414 """Evaluate the annotation using the provided namespaces. 

415 

416 Args: 

417 value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance 

418 of `str`, it will be converted to a `ForwardRef`. 

419 localns: The global namespace to use during annotation evaluation. 

420 globalns: The local namespace to use during annotation evaluation. 

421 """ 

422 value = _type_convert(value) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

423 return eval_type_backport(value, globalns, localns) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

424 

425 

426@deprecated( 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

427 '`eval_type_lenient` is deprecated, use `try_eval_type` instead.', 

428 category=None, 

429) 

430def eval_type_lenient( 1abFGzAnopqhiLcdHIBCrstujkMPefJKDEvwxylmN

431 value: Any, 

432 globalns: GlobalsNamespace | None = None, 

433 localns: MappingNamespace | None = None, 

434) -> Any: 

435 ev, _ = try_eval_type(value, globalns, localns) 

436 return ev 

437 

438 

439def eval_type_backport( 1abFGzAnopqhiLcdHIBCrstujkMPefJKDEvwxylmN

440 value: Any, 

441 globalns: GlobalsNamespace | None = None, 

442 localns: MappingNamespace | None = None, 

443 type_params: tuple[Any, ...] | None = None, 

444) -> Any: 

445 """An enhanced version of `typing._eval_type` which will fall back to using the `eval_type_backport` 

446 package if it's installed to let older Python versions use newer typing constructs. 

447 

448 Specifically, this transforms `X | Y` into `typing.Union[X, Y]` and `list[X]` into `typing.List[X]` 

449 (as well as all the types made generic in PEP 585) if the original syntax is not supported in the 

450 current Python version. 

451 

452 This function will also display a helpful error if the value passed fails to evaluate. 

453 """ 

454 try: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

455 return _eval_type_backport(value, globalns, localns, type_params) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

456 except TypeError as e: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

457 if 'Unable to evaluate type annotation' in str(e): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

458 raise 1abgcdef

459 

460 # If it is a `TypeError` and value isn't a `ForwardRef`, it would have failed during annotation definition. 

461 # Thus we assert here for type checking purposes: 

462 assert isinstance(value, typing.ForwardRef) 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

463 

464 message = f'Unable to evaluate type annotation {value.__forward_arg__!r}.' 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

465 if sys.version_info >= (3, 11): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

466 e.add_note(message) 1zAnopqhiLBCrstujkMDEvwxylmN

467 raise 1zAnopqhiLBCrstujkMDEvwxylmN

468 else: 

469 raise TypeError(message) from e 1abFGgOcdHIefJK

470 except RecursionError as e: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

471 # TODO ideally recursion errors should be checked in `eval_type` above, but `eval_type_backport` 

472 # is used directly in some places. 

473 message = ( 1abFGzAnopqhicdHIBCrstujkefJKDEvwxylm

474 "If you made use of an implicit recursive type alias (e.g. `MyType = list['MyType']), " 

475 'consider using PEP 695 type aliases instead. For more details, refer to the documentation: ' 

476 f'https://docs.pydantic.dev/{version_short()}/concepts/types/#named-recursive-types' 

477 ) 

478 if sys.version_info >= (3, 11): 1abFGzAnopqhicdHIBCrstujkefJKDEvwxylm

479 e.add_note(message) 1zAnopqhiBCrstujkDEvwxylm

480 raise 1zAnopqhiBCrstujkDEvwxylm

481 else: 

482 raise RecursionError(f'{e.args[0]}\n{message}') 1abFGcdHIefJK

483 

484 

485def _eval_type_backport( 1abFGzAnopqhiLcdHIBCrstujkMPefJKDEvwxylmN

486 value: Any, 

487 globalns: GlobalsNamespace | None = None, 

488 localns: MappingNamespace | None = None, 

489 type_params: tuple[Any, ...] | None = None, 

490) -> Any: 

491 try: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

492 return _eval_type(value, globalns, localns, type_params) 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

493 except TypeError as e: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

494 if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

495 raise 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

496 

497 try: 1abgcdef

498 from eval_type_backport import eval_type_backport 1abgcdef

499 except ImportError: 1abgcdef

500 raise TypeError( 1abgcdef

501 f'Unable to evaluate type annotation {value.__forward_arg__!r}. If you are making use ' 

502 'of the new typing syntax (unions using `|` since Python 3.10 or builtins subscripting ' 

503 'since Python 3.9), you should either replace the use of new syntax with the existing ' 

504 '`typing` constructs or install the `eval_type_backport` package.' 

505 ) from e 

506 

507 return eval_type_backport( 1abgcdef

508 value, 

509 globalns, 

510 localns, # pyright: ignore[reportArgumentType], waiting on a new `eval_type_backport` release. 

511 try_default=False, 

512 ) 

513 

514 

515def _eval_type( 1abFGzAnopqhiLcdHIBCrstujkMPefJKDEvwxylmN

516 value: Any, 

517 globalns: GlobalsNamespace | None = None, 

518 localns: MappingNamespace | None = None, 

519 type_params: tuple[Any, ...] | None = None, 

520) -> Any: 

521 if sys.version_info >= (3, 13): 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

522 return typing._eval_type( # type: ignore 1pqhiLtujkMPxylmN

523 value, globalns, localns, type_params=type_params 

524 ) 

525 else: 

526 return typing._eval_type( # type: ignore 1abFGzAnogOcdHIBCrsefJKDEvw

527 value, globalns, localns 

528 ) 

529 

530 

531def is_backport_fixable_error(e: TypeError) -> bool: 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

532 msg = str(e) 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

533 

534 return sys.version_info < (3, 10) and msg.startswith('unsupported operand type(s) for |: ') 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

535 

536 

537def get_function_type_hints( 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

538 function: Callable[..., Any], 

539 *, 

540 include_keys: set[str] | None = None, 

541 globalns: GlobalsNamespace | None = None, 

542 localns: MappingNamespace | None = None, 

543) -> dict[str, Any]: 

544 """Return type hints for a function. 

545 

546 This is similar to the `typing.get_type_hints` function, with a few differences: 

547 - Support `functools.partial` by using the underlying `func` attribute. 

548 - Do not wrap type annotation of a parameter with `Optional` if it has a default value of `None` 

549 (related bug: https://github.com/python/cpython/issues/90353, only fixed in 3.11+). 

550 """ 

551 try: 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

552 if isinstance(function, partial): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

553 annotations = function.func.__annotations__ 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

554 else: 

555 annotations = function.__annotations__ 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

556 except AttributeError: 

557 # Some functions (e.g. builtins) don't have annotations: 

558 return {} 

559 

560 if globalns is None: 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

561 globalns = get_module_ns_of(function) 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

562 type_params: tuple[Any, ...] | None = None 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

563 if localns is None: 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

564 # If localns was specified, it is assumed to already contain type params. This is because 

565 # Pydantic has more advanced logic to do so (see `_namespace_utils.ns_for_function`). 

566 type_params = getattr(function, '__type_params__', ()) 1abFGzAnopqhigOcdHIBCrstujkefJKDEvwxylm

567 

568 type_hints = {} 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

569 for name, value in annotations.items(): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

570 if include_keys is not None and name not in include_keys: 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

571 continue 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

572 if value is None: 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

573 value = NoneType 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

574 elif isinstance(value, str): 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

575 value = _make_forward_ref(value) 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

576 

577 type_hints[name] = eval_type_backport(value, globalns, localns, type_params) 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

578 

579 return type_hints 1abFGzAnopqhiLgOcdHIBCrstujkMefJKDEvwxylmN

580 

581 

582if sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): 582 ↛ 584line 582 didn't jump to line 584 because the condition on line 582 was never true1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

583 

584 def _make_forward_ref( 

585 arg: Any, 

586 is_argument: bool = True, 

587 *, 

588 is_class: bool = False, 

589 ) -> typing.ForwardRef: 

590 """Wrapper for ForwardRef that accounts for the `is_class` argument missing in older versions. 

591 The `module` argument is omitted as it breaks <3.9.8, =3.10.0 and isn't used in the calls below. 

592 

593 See https://github.com/python/cpython/pull/28560 for some background. 

594 The backport happened on 3.9.8, see: 

595 https://github.com/pydantic/pydantic/discussions/6244#discussioncomment-6275458, 

596 and on 3.10.1 for the 3.10 branch, see: 

597 https://github.com/pydantic/pydantic/issues/6912 

598 

599 Implemented as EAFP with memory. 

600 """ 

601 return typing.ForwardRef(arg, is_argument) 

602 

603else: 

604 _make_forward_ref = typing.ForwardRef 1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

605 

606 

607if sys.version_info >= (3, 10): 607 ↛ 611line 607 didn't jump to line 611 because the condition on line 607 was always true1abFGzAnopqhiLgOcdHIBCrstujkMPefJKDEvwxylmN

608 get_type_hints = typing.get_type_hints 1FGzAnopqhiLOHIBCrstujkMPJKDEvwxylmN

609 

610else: 

611 """ 

612 For older versions of python, we have a custom implementation of `get_type_hints` which is a close as possible to 

613 the implementation in CPython 3.10.8. 

614 """ 

615 

616 @typing.no_type_check 1abgcdef

617 def get_type_hints( # noqa: C901 1abcdef

618 obj: Any, 1g

619 globalns: dict[str, Any] | None = None, 1g

620 localns: dict[str, Any] | None = None, 1g

621 include_extras: bool = False, 1g

622 ) -> dict[str, Any]: # pragma: no cover 1g

623 """Taken verbatim from python 3.10.8 unchanged, except: 

624 * type annotations of the function definition above. 

625 * prefixing `typing.` where appropriate 

626 * Use `_make_forward_ref` instead of `typing.ForwardRef` to handle the `is_class` argument. 

627 

628 https://github.com/python/cpython/blob/aaaf5174241496afca7ce4d4584570190ff972fe/Lib/typing.py#L1773-L1875 

629 

630 DO NOT CHANGE THIS METHOD UNLESS ABSOLUTELY NECESSARY. 

631 ====================================================== 

632 

633 Return type hints for an object. 

634 

635 This is often the same as obj.__annotations__, but it handles 

636 forward references encoded as string literals, adds Optional[t] if a 

637 default value equal to None is set and recursively replaces all 

638 'Annotated[T, ...]' with 'T' (unless 'include_extras=True'). 

639 

640 The argument may be a module, class, method, or function. The annotations 

641 are returned as a dictionary. For classes, annotations include also 

642 inherited members. 

643 

644 TypeError is raised if the argument is not of a type that can contain 

645 annotations, and an empty dictionary is returned if no annotations are 

646 present. 

647 

648 BEWARE -- the behavior of globalns and localns is counterintuitive 

649 (unless you are familiar with how eval() and exec() work). The 

650 search order is locals first, then globals. 

651 

652 - If no dict arguments are passed, an attempt is made to use the 

653 globals from obj (or the respective module's globals for classes), 

654 and these are also used as the locals. If the object does not appear 

655 to have globals, an empty dictionary is used. For classes, the search 

656 order is globals first then locals. 

657 

658 - If one dict argument is passed, it is used for both globals and 

659 locals. 

660 

661 - If two dict arguments are passed, they specify globals and 

662 locals, respectively. 

663 """ 

664 if getattr(obj, '__no_type_check__', None): 1abgcdef

665 return {} 

666 # Classes require a special treatment. 

667 if isinstance(obj, type): 1abgcdef

668 hints = {} 1abgcdef

669 for base in reversed(obj.__mro__): 1abgcdef

670 if globalns is None: 1abgcdef

671 base_globals = getattr(sys.modules.get(base.__module__, None), '__dict__', {}) 1abgcdef

672 else: 

673 base_globals = globalns 

674 ann = base.__dict__.get('__annotations__', {}) 1abgcdef

675 if isinstance(ann, types.GetSetDescriptorType): 1abgcdef

676 ann = {} 

677 base_locals = dict(vars(base)) if localns is None else localns 1abgcdef

678 if localns is None and globalns is None: 1abgcdef

679 # This is surprising, but required. Before Python 3.10, 

680 # get_type_hints only evaluated the globalns of 

681 # a class. To maintain backwards compatibility, we reverse 

682 # the globalns and localns order so that eval() looks into 

683 # *base_globals* first rather than *base_locals*. 

684 # This only affects ForwardRefs. 

685 base_globals, base_locals = base_locals, base_globals 

686 for name, value in ann.items(): 1abgcdef

687 if value is None: 1abgcdef

688 value = type(None) 

689 if isinstance(value, str): 1abgcdef

690 value = _make_forward_ref(value, is_argument=False, is_class=True) 1abgcdef

691 

692 value = eval_type_backport(value, base_globals, base_locals) 1abgcdef

693 hints[name] = value 1abgcdef

694 if not include_extras and hasattr(typing, '_strip_annotations'): 1abgcdef

695 return { 1abgcdef

696 k: typing._strip_annotations(t) # type: ignore 1abgcdef

697 for k, t in hints.items() 1abgcdef

698 } 

699 else: 

700 return hints 

701 

702 if globalns is None: 1abgcdef

703 if isinstance(obj, types.ModuleType): 1abgcdef

704 globalns = obj.__dict__ 

705 else: 

706 nsobj = obj 1abgcdef

707 # Find globalns for the unwrapped object. 

708 while hasattr(nsobj, '__wrapped__'): 1abgcdef

709 nsobj = nsobj.__wrapped__ 

710 globalns = getattr(nsobj, '__globals__', {}) 1abgcdef

711 if localns is None: 1abgcdef

712 localns = globalns 1abgcdef

713 elif localns is None: 

714 localns = globalns 

715 hints = getattr(obj, '__annotations__', None) 1abgcdef

716 if hints is None: 1abgcdef

717 # Return empty annotations for something that _could_ have them. 

718 if isinstance(obj, typing._allowed_types): # type: ignore 

719 return {} 

720 else: 

721 raise TypeError(f'{obj!r} is not a module, class, method, or function.') 

722 defaults = typing._get_defaults(obj) # type: ignore 1abgcdef

723 hints = dict(hints) 1abgcdef

724 for name, value in hints.items(): 1abgcdef

725 if value is None: 1abgcdef

726 value = type(None) 

727 if isinstance(value, str): 1abgcdef

728 # class-level forward refs were handled above, this must be either 

729 # a module-level annotation or a function argument annotation 

730 

731 value = _make_forward_ref( 1abgcdef

732 value, 1abgcdef

733 is_argument=not isinstance(obj, types.ModuleType), 1abgcdef

734 is_class=False, 1abgcdef

735 ) 

736 value = eval_type_backport(value, globalns, localns) 1abgcdef

737 if name in defaults and defaults[name] is None: 1abgcdef

738 value = typing.Optional[value] 

739 hints[name] = value 1abgcdef

740 return hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore 1abgcdef