Coverage for pydantic/_internal/_typing_extra.py: 95.24%
199 statements
« prev ^ index » next coverage.py v7.10.0, created at 2025-07-26 11:49 +0000
« prev ^ index » next coverage.py v7.10.0, created at 2025-07-26 11:49 +0000
1"""Logic for interacting with type annotations, mostly extensions, shims and hacks to wrap Python's typing module."""
3from __future__ import annotations 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
5import collections.abc 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
6import re 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
7import sys 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
8import types 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
9import typing 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
10from functools import partial 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
11from typing import TYPE_CHECKING, Any, Callable, cast 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
13import typing_extensions 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
14from typing_extensions import deprecated, get_args, get_origin 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
15from typing_inspection import typing_objects 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
16from typing_inspection.introspection import is_union_origin 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
18from pydantic.version import version_short 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
20from ._namespace_utils import GlobalsNamespace, MappingNamespace, NsResolver, get_module_ns_of 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
22if sys.version_info < (3, 10): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
23 NoneType = type(None) 1abgcdef
24 EllipsisType = type(Ellipsis) 1abgOcdef
25else:
26 from types import EllipsisType as EllipsisType 1IJCDqrsthijOKLEFuvwxklmPMNGHyzABnop
27 from types import NoneType as NoneType 1IJCDqrsthijOKLEFuvwxklmPMNGHyzABnop
29if sys.version_info >= (3, 14): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
30 import annotationlib 1hijklmnop
32if TYPE_CHECKING: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
33 from pydantic import BaseModel
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).
41_t_annotated = typing.Annotated 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
42_te_annotated = typing_extensions.Annotated 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
45def is_annotated(tp: Any, /) -> bool: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
46 """Return whether the provided argument is a `Annotated` special form.
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
57def annotated_type(tp: Any, /) -> Any | None: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
62def unpack_type(tp: Any, /) -> Any | None: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
67def is_hashable(tp: Any, /) -> bool: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
68 """Return whether the provided argument is the `Hashable` class.
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 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
80def is_callable(tp: Any, /) -> bool: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
81 """Return whether the provided argument is a `Callable`, parametrized or not.
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 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
97_classvar_re = re.compile(r'((\w+\.)?Annotated\[)?(\w+\.)?ClassVar\[') 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
100def is_classvar_annotation(tp: Any, /) -> bool: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
101 """Return whether the provided argument represents a class variable annotation.
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.
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): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
112 return True 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
114 origin = get_origin(tp) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
116 if typing_objects.is_classvar(origin): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
117 return True 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
119 if typing_objects.is_annotated(origin): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
120 annotated_type = tp.__origin__ 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
121 if typing_objects.is_classvar(annotated_type) or typing_objects.is_classvar(get_origin(annotated_type)): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
122 return True 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
124 str_ann: str | None = None 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
125 if isinstance(tp, typing.ForwardRef): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
126 str_ann = tp.__forward_arg__ 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
127 if isinstance(tp, str): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
128 str_ann = tp 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
130 if str_ann is not None and _classvar_re.match(str_ann): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
131 # stdlib dataclasses do something similar, although a bit more advanced
132 # (see `dataclass._is_type`).
133 return True 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
135 return False 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
138_t_final = typing.Final 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
139_te_final = typing_extensions.Final 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
142# TODO implement `is_finalvar_annotation` as Final can be wrapped with other special forms:
143def is_finalvar(tp: Any, /) -> bool: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
144 """Return whether the provided argument is a `Final` special form, parametrized or not.
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 true1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
154 return True
155 origin = get_origin(tp) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
156 return origin is _t_final or origin is _te_final 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
159_NONE_TYPES: tuple[Any, ...] = (None, NoneType, typing.Literal[None], typing_extensions.Literal[None]) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
162def is_none_type(tp: Any, /) -> bool: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
163 """Return whether the argument represents the `None` type as part of an annotation.
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 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
178def is_namedtuple(tp: Any, /) -> bool: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
179 """Return whether the provided argument is a named tuple class.
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 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
186 return lenient_issubclass(tp, tuple) and hasattr(tp, '_fields') 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
194def is_generic_alias(tp: Any, /) -> bool: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
195 return isinstance(tp, (types.GenericAlias, typing._GenericAlias)) # pyright: ignore[reportAttributeAccessIssue] 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
198# TODO: Ideally, we should avoid relying on the private `typing` constructs:
200if sys.version_info < (3, 10): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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] 1IJCDqrsthijOKLEFuvwxklmPMNGHyzABnop
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] 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
210### Annotation evaluations functions:
213def parent_frame_namespace(*, parent_depth: int = 2, force: bool = False) -> dict[str, Any] | None: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
214 """Fetch the local namespace of the parent frame where this function is called.
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:
220 ```python {lint="skip" test="skip"}
221 from typing import get_type_hints
223 def func() -> None:
224 Alias = int
226 class C:
227 a: 'Alias'
229 # Raises a `NameError: 'Alias' is not defined`
230 get_type_hints(C)
231 ```
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).
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.
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.
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) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
254 if frame.f_code.co_name.startswith('<generic parameters of'): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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` 1qrsthijuvwxklmyzABnop
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: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
266 return frame.f_locals 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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>': 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
272 return None 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
274 return frame.f_locals 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
277def _type_convert(arg: Any) -> Any: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
278 """Convert `None` to `NoneType` and strings to `ForwardRef` instances.
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: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
287 return NoneType 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
288 if isinstance(arg, str): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
292 return arg 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
295def safe_get_annotations(cls: type[Any]) -> dict[str, Any]: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
296 """Get the annotations for the provided class, accounting for potential deferred forward references.
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): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
303 return annotationlib.get_annotations(cls, format=annotationlib.Format.FORWARDREF) 1hijklmnop
304 else:
305 return cls.__dict__.get('__annotations__', {}) 1abIJCDqrstgOcdKLEFuvwxPefMNGHyzAB
308def get_model_type_hints( 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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.
315 Args:
316 obj: The Pydantic model to inspect.
317 ns_resolver: A namespace resolver instance to use. Defaults to an empty instance.
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]] = {} 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
325 ns_resolver = ns_resolver or NsResolver() 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
327 for base in reversed(obj.__mro__): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
333 if not ann: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
334 continue 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
336 with ns_resolver.push(base): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
337 globalns, localns = ns_resolver.types_namespace 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
338 for name, value in ann.items(): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
339 if name.startswith('_'): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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: 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
346 hints[name] = try_eval_type(value, globalns, localns) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
347 except Exception: 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
348 hints[name] = (value, False) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
349 else:
350 hints[name] = try_eval_type(value, globalns, localns) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
351 return hints 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
354def get_cls_type_hints( 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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.
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] = {} 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
366 ns_resolver = ns_resolver or NsResolver() 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
368 for base in reversed(obj.__mro__): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
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) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
374 if not ann: 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
375 continue 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
377 with ns_resolver.push(base): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
378 globalns, localns = ns_resolver.types_namespace 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
379 for name, value in ann.items(): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
380 hints[name] = eval_type(value, globalns, localns) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
381 return hints 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
384def try_eval_type( 1abIJCDqrsthijcdKLEFuvwxklmPefMNGHyzABnop
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.
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.
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) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
403 try: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
404 return eval_type_backport(value, globalns, localns), True 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
405 except NameError: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
406 return value, False 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
409def eval_type( 1abIJCDqrsthijcdKLEFuvwxklmPefMNGHyzABnop
410 value: Any,
411 globalns: GlobalsNamespace | None = None,
412 localns: MappingNamespace | None = None,
413) -> Any:
414 """Evaluate the annotation using the provided namespaces.
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) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
423 return eval_type_backport(value, globalns, localns) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
426@deprecated( 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
427 '`eval_type_lenient` is deprecated, use `try_eval_type` instead.',
428 category=None,
429)
430def eval_type_lenient( 1abIJCDqrsthijcdKLEFuvwxklmPefMNGHyzABnop
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
439def eval_type_backport( 1abIJCDqrsthijcdKLEFuvwxklmPefMNGHyzABnop
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.
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.
452 This function will also display a helpful error if the value passed fails to evaluate.
453 """
454 try: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
455 return _eval_type_backport(value, globalns, localns, type_params) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
456 except TypeError as e: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
457 if 'Unable to evaluate type annotation' in str(e): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
458 raise 1abgcdef
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) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
464 message = f'Unable to evaluate type annotation {value.__forward_arg__!r}.' 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
465 if sys.version_info >= (3, 11): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
466 e.add_note(message) 1CDqrsthijEFuvwxklmGHyzABnop
467 raise 1CDqrsthijEFuvwxklmGHyzABnop
468 else:
469 raise TypeError(message) from e 1abIJgOcdKLefMN
470 except RecursionError as e: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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 = ( 1abIJCDqrsthijcdKLEFuvwxklmefMNGHyzABnop
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): 1abIJCDqrsthijcdKLEFuvwxklmefMNGHyzABnop
479 e.add_note(message) 1CDqrsthijEFuvwxklmGHyzABnop
480 raise 1CDqrsthijEFuvwxklmGHyzABnop
481 else:
482 raise RecursionError(f'{e.args[0]}\n{message}') 1abIJcdKLefMN
485def _eval_type_backport( 1abIJCDqrsthijcdKLEFuvwxklmPefMNGHyzABnop
486 value: Any,
487 globalns: GlobalsNamespace | None = None,
488 localns: MappingNamespace | None = None,
489 type_params: tuple[Any, ...] | None = None,
490) -> Any:
491 try: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
492 return _eval_type(value, globalns, localns, type_params) 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
493 except TypeError as e: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
494 if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
495 raise 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
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
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 )
515def _eval_type( 1abIJCDqrsthijcdKLEFuvwxklmPefMNGHyzABnop
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): 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
522 return typing._eval_type( # type: ignore 1sthijwxklmPABnop
523 value, globalns, localns, type_params=type_params
524 )
525 else:
526 return typing._eval_type( # type: ignore 1abIJCDqrgOcdKLEFuvefMNGHyz
527 value, globalns, localns
528 )
531def is_backport_fixable_error(e: TypeError) -> bool: 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
532 msg = str(e) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
534 return sys.version_info < (3, 10) and msg.startswith('unsupported operand type(s) for |: ') 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
537def get_function_type_hints( 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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.
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: 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
552 if isinstance(function, partial): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
553 annotations = function.func.__annotations__ 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
554 else:
555 annotations = function.__annotations__ 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
556 except AttributeError:
557 # Some functions (e.g. builtins) don't have annotations:
558 return {}
560 if globalns is None: 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
561 globalns = get_module_ns_of(function) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
562 type_params: tuple[Any, ...] | None = None 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
563 if localns is None: 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
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__', ()) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
568 type_hints = {} 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
569 for name, value in annotations.items(): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
570 if include_keys is not None and name not in include_keys: 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
571 continue 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
572 if value is None: 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
573 value = NoneType 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
574 elif isinstance(value, str): 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
575 value = _make_forward_ref(value) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
577 type_hints[name] = eval_type_backport(value, globalns, localns, type_params) 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
579 return type_hints 1abIJCDqrsthijgOcdKLEFuvwxklmefMNGHyzABnop
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 true1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
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.
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
599 Implemented as EAFP with memory.
600 """
601 return typing.ForwardRef(arg, is_argument)
603else:
604 _make_forward_ref = typing.ForwardRef 1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
607if sys.version_info >= (3, 10): 607 ↛ 611line 607 didn't jump to line 611 because the condition on line 607 was always true1abIJCDqrsthijgOcdKLEFuvwxklmPefMNGHyzABnop
608 get_type_hints = typing.get_type_hints 1IJCDqrsthijOKLEFuvwxklmPMNGHyzABnop
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 """
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.
628 https://github.com/python/cpython/blob/aaaf5174241496afca7ce4d4584570190ff972fe/Lib/typing.py#L1773-L1875
630 DO NOT CHANGE THIS METHOD UNLESS ABSOLUTELY NECESSARY.
631 ======================================================
633 Return type hints for an object.
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').
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.
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.
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.
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.
658 - If one dict argument is passed, it is used for both globals and
659 locals.
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
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
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
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