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