Coverage for pydantic/_internal/_typing_extra.py: 95.09%
193 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-11 13:08 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-11 13:08 +0000
1"""Logic for interacting with type annotations, mostly extensions, shims and hacks to wrap Python's typing module."""
3from __future__ import annotations 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
5import collections.abc 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
6import re 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
7import sys 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
8import types 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
9import typing 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
10from functools import partial 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
11from typing import TYPE_CHECKING, Any, Callable, cast 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
13import typing_extensions 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
14from typing_extensions import deprecated, get_args, get_origin 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
15from typing_inspection import typing_objects 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
16from typing_inspection.introspection import is_union_origin 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
18from pydantic.version import version_short 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
20from ._namespace_utils import GlobalsNamespace, MappingNamespace, NsResolver, get_module_ns_of 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
22if sys.version_info < (3, 10): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
23 NoneType = type(None) 1abgcdef
24 EllipsisType = type(Ellipsis) 1abgIcdef
25else:
26 from types import EllipsisType as EllipsisType 1CDwxhijklIEFyzmnopqJGHABrstuv
27 from types import NoneType as NoneType 1CDwxhijklIEFyzmnopqJGHABrstuv
29if TYPE_CHECKING: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
30 from pydantic import BaseModel
32# As per https://typing-extensions.readthedocs.io/en/latest/#runtime-use-of-types,
33# always check for both `typing` and `typing_extensions` variants of a typing construct.
34# (this is implemented differently than the suggested approach in the `typing_extensions`
35# docs for performance).
38_t_annotated = typing.Annotated 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
39_te_annotated = typing_extensions.Annotated 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
42def is_annotated(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
43 """Return whether the provided argument is a `Annotated` special form.
45 ```python {test="skip" lint="skip"}
46 is_annotated(Annotated[int, ...])
47 #> True
48 ```
49 """
50 origin = get_origin(tp)
51 return origin is _t_annotated or origin is _te_annotated
54def annotated_type(tp: Any, /) -> Any | None: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
55 """Return the type of the `Annotated` special form, or `None`."""
56 return tp.__origin__ if typing_objects.is_annotated(get_origin(tp)) else None 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
59def unpack_type(tp: Any, /) -> Any | None: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
60 """Return the type wrapped by the `Unpack` special form, or `None`."""
61 return get_args(tp)[0] if typing_objects.is_unpack(get_origin(tp)) else None 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
64def is_hashable(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
65 """Return whether the provided argument is the `Hashable` class.
67 ```python {test="skip" lint="skip"}
68 is_hashable(Hashable)
69 #> True
70 ```
71 """
72 # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes,
73 # hence the second check:
74 return tp is collections.abc.Hashable or get_origin(tp) is collections.abc.Hashable 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
77def is_callable(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
78 """Return whether the provided argument is a `Callable`, parametrized or not.
80 ```python {test="skip" lint="skip"}
81 is_callable(Callable[[int], str])
82 #> True
83 is_callable(typing.Callable)
84 #> True
85 is_callable(collections.abc.Callable)
86 #> True
87 ```
88 """
89 # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes,
90 # hence the second check:
91 return tp is collections.abc.Callable or get_origin(tp) is collections.abc.Callable 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
94_classvar_re = re.compile(r'((\w+\.)?Annotated\[)?(\w+\.)?ClassVar\[') 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
97def is_classvar_annotation(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
98 """Return whether the provided argument represents a class variable annotation.
100 Although not explicitly stated by the typing specification, `ClassVar` can be used
101 inside `Annotated` and as such, this function checks for this specific scenario.
103 Because this function is used to detect class variables before evaluating forward references
104 (or because evaluation failed), we also implement a naive regex match implementation. This is
105 required because class variables are inspected before fields are collected, so we try to be
106 as accurate as possible.
107 """
108 if typing_objects.is_classvar(tp): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
109 return True 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
111 origin = get_origin(tp) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
113 if typing_objects.is_classvar(origin): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
114 return True 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
116 if typing_objects.is_annotated(origin): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
117 annotated_type = tp.__origin__ 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
118 if typing_objects.is_classvar(annotated_type) or typing_objects.is_classvar(get_origin(annotated_type)): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
119 return True 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
121 str_ann: str | None = None 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
122 if isinstance(tp, typing.ForwardRef): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
123 str_ann = tp.__forward_arg__ 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
124 if isinstance(tp, str): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
125 str_ann = tp 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
127 if str_ann is not None and _classvar_re.match(str_ann): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
128 # stdlib dataclasses do something similar, although a bit more advanced
129 # (see `dataclass._is_type`).
130 return True 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
132 return False 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
135_t_final = typing.Final 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
136_te_final = typing_extensions.Final 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
139# TODO implement `is_finalvar_annotation` as Final can be wrapped with other special forms:
140def is_finalvar(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
141 """Return whether the provided argument is a `Final` special form, parametrized or not.
143 ```python {test="skip" lint="skip"}
144 is_finalvar(Final[int])
145 #> True
146 is_finalvar(Final)
147 #> True
148 """
149 # Final is not necessarily parametrized:
150 if tp is _t_final or tp is _te_final: 150 ↛ 151line 150 didn't jump to line 151 because the condition on line 150 was never true1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
151 return True
152 origin = get_origin(tp) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
153 return origin is _t_final or origin is _te_final 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
156_NONE_TYPES: tuple[Any, ...] = (None, NoneType, typing.Literal[None], typing_extensions.Literal[None]) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
159def is_none_type(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
160 """Return whether the argument represents the `None` type as part of an annotation.
162 ```python {test="skip" lint="skip"}
163 is_none_type(None)
164 #> True
165 is_none_type(NoneType)
166 #> True
167 is_none_type(Literal[None])
168 #> True
169 is_none_type(type[None])
170 #> False
171 """
172 return tp in _NONE_TYPES 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
175def is_namedtuple(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
176 """Return whether the provided argument is a named tuple class.
178 The class can be created using `typing.NamedTuple` or `collections.namedtuple`.
179 Parametrized generic classes are *not* assumed to be named tuples.
180 """
181 from ._utils import lenient_issubclass # circ. import 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
183 return lenient_issubclass(tp, tuple) and hasattr(tp, '_fields') 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
186# TODO In 2.12, delete this export. It is currently defined only to not break
187# pydantic-settings which relies on it:
188origin_is_union = is_union_origin 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
191def is_generic_alias(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
192 return isinstance(tp, (types.GenericAlias, typing._GenericAlias)) # pyright: ignore[reportAttributeAccessIssue] 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
195# TODO: Ideally, we should avoid relying on the private `typing` constructs:
197if sys.version_info < (3, 10): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
198 WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # pyright: ignore[reportAttributeAccessIssue] 1abgcdef
199else:
200 WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias, types.UnionType) # pyright: ignore[reportAttributeAccessIssue] 1CDwxhijklIEFyzmnopqJGHABrstuv
203# Similarly, we shouldn't rely on this `_Final` class, which is even more private than `_GenericAlias`:
204typing_base: Any = typing._Final # pyright: ignore[reportAttributeAccessIssue] 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
207### Annotation evaluations functions:
210def parent_frame_namespace(*, parent_depth: int = 2, force: bool = False) -> dict[str, Any] | None: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
211 """Fetch the local namespace of the parent frame where this function is called.
213 Using this function is mostly useful to resolve forward annotations pointing to members defined in a local namespace,
214 such as assignments inside a function. Using the standard library tools, it is currently not possible to resolve
215 such annotations:
217 ```python {lint="skip" test="skip"}
218 from typing import get_type_hints
220 def func() -> None:
221 Alias = int
223 class C:
224 a: 'Alias'
226 # Raises a `NameError: 'Alias' is not defined`
227 get_type_hints(C)
228 ```
230 Pydantic uses this function when a Pydantic model is being defined to fetch the parent frame locals. However,
231 this only allows us to fetch the parent frame namespace and not other parents (e.g. a model defined in a function,
232 itself defined in another function). Inspecting the next outer frames (using `f_back`) is not reliable enough
233 (see https://discuss.python.org/t/20659).
235 Because this function is mostly used to better resolve forward annotations, nothing is returned if the parent frame's
236 code object is defined at the module level. In this case, the locals of the frame will be the same as the module
237 globals where the class is defined (see `_namespace_utils.get_module_ns_of`). However, if you still want to fetch
238 the module globals (e.g. when rebuilding a model, where the frame where the rebuild call is performed might contain
239 members that you want to use for forward annotations evaluation), you can use the `force` parameter.
241 Args:
242 parent_depth: The depth at which to get the frame. Defaults to 2, meaning the parent frame where this function
243 is called will be used.
244 force: Whether to always return the frame locals, even if the frame's code object is defined at the module level.
246 Returns:
247 The locals of the namespace, or `None` if it was skipped as per the described logic.
248 """
249 frame = sys._getframe(parent_depth) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
251 if frame.f_code.co_name.startswith('<generic parameters of'): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
252 # As `parent_frame_namespace` is mostly called in `ModelMetaclass.__new__`,
253 # the parent frame can be the annotation scope if the PEP 695 generic syntax is used.
254 # (see https://docs.python.org/3/reference/executionmodel.html#annotation-scopes,
255 # https://docs.python.org/3/reference/compound_stmts.html#generic-classes).
256 # In this case, the code name is set to `<generic parameters of MyClass>`,
257 # and we need to skip this frame as it is irrelevant.
258 frame = cast(types.FrameType, frame.f_back) # guaranteed to not be `None` 1hijklmnopqrstuv
260 # note, we don't copy frame.f_locals here (or during the last return call), because we don't expect the namespace to be
261 # modified down the line if this becomes a problem, we could implement some sort of frozen mapping structure to enforce this.
262 if force: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
263 return frame.f_locals 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
265 # If either of the following conditions are true, the class is defined at the top module level.
266 # To better understand why we need both of these checks, see
267 # https://github.com/pydantic/pydantic/pull/10113#discussion_r1714981531.
268 if frame.f_back is None or frame.f_code.co_name == '<module>': 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
269 return None 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
271 return frame.f_locals 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
274def _type_convert(arg: Any) -> Any: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
275 """Convert `None` to `NoneType` and strings to `ForwardRef` instances.
277 This is a backport of the private `typing._type_convert` function. When
278 evaluating a type, `ForwardRef._evaluate` ends up being called, and is
279 responsible for making this conversion. However, we still have to apply
280 it for the first argument passed to our type evaluation functions, similarly
281 to the `typing.get_type_hints` function.
282 """
283 if arg is None: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
284 return NoneType 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
285 if isinstance(arg, str): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
286 # Like `typing.get_type_hints`, assume the arg can be in any context,
287 # hence the proper `is_argument` and `is_class` args:
288 return _make_forward_ref(arg, is_argument=False, is_class=True) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
289 return arg 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
292def get_model_type_hints( 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
293 obj: type[BaseModel],
294 *,
295 ns_resolver: NsResolver | None = None,
296) -> dict[str, tuple[Any, bool]]:
297 """Collect annotations from a Pydantic model class, including those from parent classes.
299 Args:
300 obj: The Pydantic model to inspect.
301 ns_resolver: A namespace resolver instance to use. Defaults to an empty instance.
303 Returns:
304 A dictionary mapping annotation names to a two-tuple: the first element is the evaluated
305 type or the original annotation if a `NameError` occurred, the second element is a boolean
306 indicating if whether the evaluation succeeded.
307 """
308 hints: dict[str, Any] | dict[str, tuple[Any, bool]] = {} 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
309 ns_resolver = ns_resolver or NsResolver() 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
311 for base in reversed(obj.__mro__): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
312 ann: dict[str, Any] | None = base.__dict__.get('__annotations__') 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
313 if not ann or isinstance(ann, types.GetSetDescriptorType): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
314 continue 1CDwxhijklIEFyzmnopqJGHABrstuv
315 with ns_resolver.push(base): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
316 globalns, localns = ns_resolver.types_namespace 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
317 for name, value in ann.items(): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
318 if name.startswith('_'): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
319 # For private attributes, we only need the annotation to detect the `ClassVar` special form.
320 # For this reason, we still try to evaluate it, but we also catch any possible exception (on
321 # top of the `NameError`s caught in `try_eval_type`) that could happen so that users are free
322 # to use any kind of forward annotation for private fields (e.g. circular imports, new typing
323 # syntax, etc).
324 try: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
325 hints[name] = try_eval_type(value, globalns, localns) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
326 except Exception: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
327 hints[name] = (value, False) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
328 else:
329 hints[name] = try_eval_type(value, globalns, localns) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
330 return hints 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
333def get_cls_type_hints( 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
334 obj: type[Any],
335 *,
336 ns_resolver: NsResolver | None = None,
337) -> dict[str, Any]:
338 """Collect annotations from a class, including those from parent classes.
340 Args:
341 obj: The class to inspect.
342 ns_resolver: A namespace resolver instance to use. Defaults to an empty instance.
343 """
344 hints: dict[str, Any] | dict[str, tuple[Any, bool]] = {} 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
345 ns_resolver = ns_resolver or NsResolver() 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
347 for base in reversed(obj.__mro__): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
348 ann: dict[str, Any] | None = base.__dict__.get('__annotations__') 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
349 if not ann or isinstance(ann, types.GetSetDescriptorType): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
350 continue 1CDwxhijklIEFyzmnopqGHABrstuv
351 with ns_resolver.push(base): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
352 globalns, localns = ns_resolver.types_namespace 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
353 for name, value in ann.items(): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
354 hints[name] = eval_type(value, globalns, localns) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
355 return hints 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
358def try_eval_type( 1abCDwxhijklcdEFyzmnopqJefGHABrstuv
359 value: Any,
360 globalns: GlobalsNamespace | None = None,
361 localns: MappingNamespace | None = None,
362) -> tuple[Any, bool]:
363 """Try evaluating the annotation using the provided namespaces.
365 Args:
366 value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance
367 of `str`, it will be converted to a `ForwardRef`.
368 localns: The global namespace to use during annotation evaluation.
369 globalns: The local namespace to use during annotation evaluation.
371 Returns:
372 A two-tuple containing the possibly evaluated type and a boolean indicating
373 whether the evaluation succeeded or not.
374 """
375 value = _type_convert(value) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
377 try: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
378 return eval_type_backport(value, globalns, localns), True 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
379 except NameError: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
380 return value, False 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
383def eval_type( 1abCDwxhijklcdEFyzmnopqJefGHABrstuv
384 value: Any,
385 globalns: GlobalsNamespace | None = None,
386 localns: MappingNamespace | None = None,
387) -> Any:
388 """Evaluate the annotation using the provided namespaces.
390 Args:
391 value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance
392 of `str`, it will be converted to a `ForwardRef`.
393 localns: The global namespace to use during annotation evaluation.
394 globalns: The local namespace to use during annotation evaluation.
395 """
396 value = _type_convert(value) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
397 return eval_type_backport(value, globalns, localns) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
400@deprecated( 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
401 '`eval_type_lenient` is deprecated, use `try_eval_type` instead.',
402 category=None,
403)
404def eval_type_lenient( 1abCDwxhijklcdEFyzmnopqJefGHABrstuv
405 value: Any,
406 globalns: GlobalsNamespace | None = None,
407 localns: MappingNamespace | None = None,
408) -> Any:
409 ev, _ = try_eval_type(value, globalns, localns)
410 return ev
413def eval_type_backport( 1abCDwxhijklcdEFyzmnopqJefGHABrstuv
414 value: Any,
415 globalns: GlobalsNamespace | None = None,
416 localns: MappingNamespace | None = None,
417 type_params: tuple[Any, ...] | None = None,
418) -> Any:
419 """An enhanced version of `typing._eval_type` which will fall back to using the `eval_type_backport`
420 package if it's installed to let older Python versions use newer typing constructs.
422 Specifically, this transforms `X | Y` into `typing.Union[X, Y]` and `list[X]` into `typing.List[X]`
423 (as well as all the types made generic in PEP 585) if the original syntax is not supported in the
424 current Python version.
426 This function will also display a helpful error if the value passed fails to evaluate.
427 """
428 try: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
429 return _eval_type_backport(value, globalns, localns, type_params) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
430 except TypeError as e: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
431 if 'Unable to evaluate type annotation' in str(e): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
432 raise 1abgcdef
434 # If it is a `TypeError` and value isn't a `ForwardRef`, it would have failed during annotation definition.
435 # Thus we assert here for type checking purposes:
436 assert isinstance(value, typing.ForwardRef) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
438 message = f'Unable to evaluate type annotation {value.__forward_arg__!r}.' 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
439 if sys.version_info >= (3, 11): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
440 e.add_note(message) 1wxhijklyzmnopqABrstuv
441 raise 1wxhijklyzmnopqABrstuv
442 else:
443 raise TypeError(message) from e 1abCDgIcdEFefGH
444 except RecursionError as e: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
445 # TODO ideally recursion errors should be checked in `eval_type` above, but `eval_type_backport`
446 # is used directly in some places.
447 message = ( 1abCDwxhijklcdEFyzmnopqefGHABrstuv
448 "If you made use of an implicit recursive type alias (e.g. `MyType = list['MyType']), "
449 'consider using PEP 695 type aliases instead. For more details, refer to the documentation: '
450 f'https://docs.pydantic.dev/{version_short()}/concepts/types/#named-recursive-types'
451 )
452 if sys.version_info >= (3, 11): 1abCDwxhijklcdEFyzmnopqefGHABrstuv
453 e.add_note(message) 1wxhijklyzmnopqABrstuv
454 raise 1wxhijklyzmnopqABrstuv
455 else:
456 raise RecursionError(f'{e.args[0]}\n{message}') 1abCDcdEFefGH
459def _eval_type_backport( 1abCDwxhijklcdEFyzmnopqJefGHABrstuv
460 value: Any,
461 globalns: GlobalsNamespace | None = None,
462 localns: MappingNamespace | None = None,
463 type_params: tuple[Any, ...] | None = None,
464) -> Any:
465 try: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
466 return _eval_type(value, globalns, localns, type_params) 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
467 except TypeError as e: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
468 if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
469 raise 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
471 try: 1abgcdef
472 from eval_type_backport import eval_type_backport 1abgcdef
473 except ImportError: 1abgcdef
474 raise TypeError( 1abgcdef
475 f'Unable to evaluate type annotation {value.__forward_arg__!r}. If you are making use '
476 'of the new typing syntax (unions using `|` since Python 3.10 or builtins subscripting '
477 'since Python 3.9), you should either replace the use of new syntax with the existing '
478 '`typing` constructs or install the `eval_type_backport` package.'
479 ) from e
481 return eval_type_backport( 1abgcdef
482 value,
483 globalns,
484 localns, # pyright: ignore[reportArgumentType], waiting on a new `eval_type_backport` release.
485 try_default=False,
486 )
489def _eval_type( 1abCDwxhijklcdEFyzmnopqJefGHABrstuv
490 value: Any,
491 globalns: GlobalsNamespace | None = None,
492 localns: MappingNamespace | None = None,
493 type_params: tuple[Any, ...] | None = None,
494) -> Any:
495 if sys.version_info >= (3, 13): 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
496 return typing._eval_type( # type: ignore 1jklopqJtuv
497 value, globalns, localns, type_params=type_params
498 )
499 else:
500 return typing._eval_type( # type: ignore 1abCDwxhigIcdEFyzmnefGHABrs
501 value, globalns, localns
502 )
505def is_backport_fixable_error(e: TypeError) -> bool: 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
506 msg = str(e) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
508 return sys.version_info < (3, 10) and msg.startswith('unsupported operand type(s) for |: ') 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
511def get_function_type_hints( 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
512 function: Callable[..., Any],
513 *,
514 include_keys: set[str] | None = None,
515 globalns: GlobalsNamespace | None = None,
516 localns: MappingNamespace | None = None,
517) -> dict[str, Any]:
518 """Return type hints for a function.
520 This is similar to the `typing.get_type_hints` function, with a few differences:
521 - Support `functools.partial` by using the underlying `func` attribute.
522 - Do not wrap type annotation of a parameter with `Optional` if it has a default value of `None`
523 (related bug: https://github.com/python/cpython/issues/90353, only fixed in 3.11+).
524 """
525 try: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
526 if isinstance(function, partial): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
527 annotations = function.func.__annotations__ 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
528 else:
529 annotations = function.__annotations__ 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
530 except AttributeError:
531 # Some functions (e.g. builtins) don't have annotations:
532 return {}
534 if globalns is None: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
535 globalns = get_module_ns_of(function) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
536 type_params: tuple[Any, ...] | None = None 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
537 if localns is None: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
538 # If localns was specified, it is assumed to already contain type params. This is because
539 # Pydantic has more advanced logic to do so (see `_namespace_utils.ns_for_function`).
540 type_params = getattr(function, '__type_params__', ()) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
542 type_hints = {} 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
543 for name, value in annotations.items(): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
544 if include_keys is not None and name not in include_keys: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
545 continue 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
546 if value is None: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
547 value = NoneType 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
548 elif isinstance(value, str): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
549 value = _make_forward_ref(value) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
551 type_hints[name] = eval_type_backport(value, globalns, localns, type_params) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
553 return type_hints 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv
556if sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): 556 ↛ 558line 556 didn't jump to line 558 because the condition on line 556 was never true1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
558 def _make_forward_ref(
559 arg: Any,
560 is_argument: bool = True,
561 *,
562 is_class: bool = False,
563 ) -> typing.ForwardRef:
564 """Wrapper for ForwardRef that accounts for the `is_class` argument missing in older versions.
565 The `module` argument is omitted as it breaks <3.9.8, =3.10.0 and isn't used in the calls below.
567 See https://github.com/python/cpython/pull/28560 for some background.
568 The backport happened on 3.9.8, see:
569 https://github.com/pydantic/pydantic/discussions/6244#discussioncomment-6275458,
570 and on 3.10.1 for the 3.10 branch, see:
571 https://github.com/pydantic/pydantic/issues/6912
573 Implemented as EAFP with memory.
574 """
575 return typing.ForwardRef(arg, is_argument)
577else:
578 _make_forward_ref = typing.ForwardRef 1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
581if sys.version_info >= (3, 10): 581 ↛ 585line 581 didn't jump to line 585 because the condition on line 581 was always true1abCDwxhijklgIcdEFyzmnopqJefGHABrstuv
582 get_type_hints = typing.get_type_hints 1CDwxhijklIEFyzmnopqJGHABrstuv
584else:
585 """
586 For older versions of python, we have a custom implementation of `get_type_hints` which is a close as possible to
587 the implementation in CPython 3.10.8.
588 """
590 @typing.no_type_check 1abgcdef
591 def get_type_hints( # noqa: C901 1abcdef
592 obj: Any, 1g
593 globalns: dict[str, Any] | None = None, 1g
594 localns: dict[str, Any] | None = None, 1g
595 include_extras: bool = False, 1g
596 ) -> dict[str, Any]: # pragma: no cover 1g
597 """Taken verbatim from python 3.10.8 unchanged, except:
598 * type annotations of the function definition above.
599 * prefixing `typing.` where appropriate
600 * Use `_make_forward_ref` instead of `typing.ForwardRef` to handle the `is_class` argument.
602 https://github.com/python/cpython/blob/aaaf5174241496afca7ce4d4584570190ff972fe/Lib/typing.py#L1773-L1875
604 DO NOT CHANGE THIS METHOD UNLESS ABSOLUTELY NECESSARY.
605 ======================================================
607 Return type hints for an object.
609 This is often the same as obj.__annotations__, but it handles
610 forward references encoded as string literals, adds Optional[t] if a
611 default value equal to None is set and recursively replaces all
612 'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
614 The argument may be a module, class, method, or function. The annotations
615 are returned as a dictionary. For classes, annotations include also
616 inherited members.
618 TypeError is raised if the argument is not of a type that can contain
619 annotations, and an empty dictionary is returned if no annotations are
620 present.
622 BEWARE -- the behavior of globalns and localns is counterintuitive
623 (unless you are familiar with how eval() and exec() work). The
624 search order is locals first, then globals.
626 - If no dict arguments are passed, an attempt is made to use the
627 globals from obj (or the respective module's globals for classes),
628 and these are also used as the locals. If the object does not appear
629 to have globals, an empty dictionary is used. For classes, the search
630 order is globals first then locals.
632 - If one dict argument is passed, it is used for both globals and
633 locals.
635 - If two dict arguments are passed, they specify globals and
636 locals, respectively.
637 """
638 if getattr(obj, '__no_type_check__', None): 1abgcdef
639 return {}
640 # Classes require a special treatment.
641 if isinstance(obj, type): 1abgcdef
642 hints = {} 1abgcdef
643 for base in reversed(obj.__mro__): 1abgcdef
644 if globalns is None: 1abgcdef
645 base_globals = getattr(sys.modules.get(base.__module__, None), '__dict__', {}) 1abgcdef
646 else:
647 base_globals = globalns
648 ann = base.__dict__.get('__annotations__', {}) 1abgcdef
649 if isinstance(ann, types.GetSetDescriptorType): 1abgcdef
650 ann = {}
651 base_locals = dict(vars(base)) if localns is None else localns 1abgcdef
652 if localns is None and globalns is None: 1abgcdef
653 # This is surprising, but required. Before Python 3.10,
654 # get_type_hints only evaluated the globalns of
655 # a class. To maintain backwards compatibility, we reverse
656 # the globalns and localns order so that eval() looks into
657 # *base_globals* first rather than *base_locals*.
658 # This only affects ForwardRefs.
659 base_globals, base_locals = base_locals, base_globals
660 for name, value in ann.items(): 1abgcdef
661 if value is None: 1abgcdef
662 value = type(None)
663 if isinstance(value, str): 1abgcdef
664 value = _make_forward_ref(value, is_argument=False, is_class=True) 1abgcdef
666 value = eval_type_backport(value, base_globals, base_locals) 1abgcdef
667 hints[name] = value 1abgcdef
668 if not include_extras and hasattr(typing, '_strip_annotations'): 1abgcdef
669 return { 1abgcdef
670 k: typing._strip_annotations(t) # type: ignore 1abgcdef
671 for k, t in hints.items() 1abgcdef
672 }
673 else:
674 return hints
676 if globalns is None: 1abgcdef
677 if isinstance(obj, types.ModuleType): 1abgcdef
678 globalns = obj.__dict__
679 else:
680 nsobj = obj 1abgcdef
681 # Find globalns for the unwrapped object.
682 while hasattr(nsobj, '__wrapped__'): 1abgcdef
683 nsobj = nsobj.__wrapped__
684 globalns = getattr(nsobj, '__globals__', {}) 1abgcdef
685 if localns is None: 1abgcdef
686 localns = globalns 1abgcdef
687 elif localns is None:
688 localns = globalns
689 hints = getattr(obj, '__annotations__', None) 1abgcdef
690 if hints is None: 1abgcdef
691 # Return empty annotations for something that _could_ have them.
692 if isinstance(obj, typing._allowed_types): # type: ignore
693 return {}
694 else:
695 raise TypeError(f'{obj!r} is not a module, class, method, or function.')
696 defaults = typing._get_defaults(obj) # type: ignore 1abgcdef
697 hints = dict(hints) 1abgcdef
698 for name, value in hints.items(): 1abgcdef
699 if value is None: 1abgcdef
700 value = type(None)
701 if isinstance(value, str): 1abgcdef
702 # class-level forward refs were handled above, this must be either
703 # a module-level annotation or a function argument annotation
705 value = _make_forward_ref( 1abgcdef
706 value, 1abgcdef
707 is_argument=not isinstance(obj, types.ModuleType), 1abgcdef
708 is_class=False, 1abgcdef
709 )
710 value = eval_type_backport(value, globalns, localns) 1abgcdef
711 if name in defaults and defaults[name] is None: 1abgcdef
712 value = typing.Optional[value]
713 hints[name] = value 1abgcdef
714 return hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore 1abgcdef