|
72 | 72 | "as_functiontype", |
73 | 73 | "ForwardRef", |
74 | 74 | "BASEDMYPY_TYPE_CHECKING", |
| 75 | + "get_type_hints", |
75 | 76 | ) |
76 | 77 |
|
77 | 78 | if TYPE_CHECKING: |
@@ -743,3 +744,110 @@ def _type_check(arg: object, msg: str) -> object: |
743 | 744 | if not callable(arg): |
744 | 745 | raise TypeError(f"{msg} Got {arg!r:.100}.") |
745 | 746 | return arg |
| 747 | + |
| 748 | + |
| 749 | +_strip_annotations = typing._strip_annotations # type: ignore[attr-defined] |
| 750 | + |
| 751 | + |
| 752 | +def get_type_hints( # type: ignore[no-any-explicit] |
| 753 | + obj: object |
| 754 | + | Callable[..., object] |
| 755 | + | FunctionType[..., object] |
| 756 | + | types.BuiltinFunctionType[..., object] |
| 757 | + | types.MethodType |
| 758 | + | types.ModuleType |
| 759 | + | types.WrapperDescriptorType |
| 760 | + | types.MethodWrapperType |
| 761 | + | types.MethodDescriptorType, |
| 762 | + globalns: dict[str, object] | None = None, |
| 763 | + localns: dict[str, object] | None = None, |
| 764 | + include_extras: bool = False, # noqa: FBT001, FBT002 |
| 765 | +) -> dict[str, object]: |
| 766 | + """Return type hints for an object. |
| 767 | +
|
| 768 | + same as `typing.get_type_hints` except: |
| 769 | + - supports based typing denotations |
| 770 | + - adds the class to the scope: |
| 771 | +
|
| 772 | + ```py |
| 773 | + class Base: |
| 774 | + def __init_subclass__(cls): |
| 775 | + get_type_hints(cls) |
| 776 | +
|
| 777 | + class A(Base): |
| 778 | + a: A |
| 779 | + ``` |
| 780 | + """ |
| 781 | + if getattr(obj, "__no_type_check__", None): # type: ignore[no-any-expr] |
| 782 | + return {} |
| 783 | + # Classes require a special treatment. |
| 784 | + if isinstance(obj, type): # type: ignore[no-any-expr] |
| 785 | + hints = {} |
| 786 | + for base in reversed(obj.__mro__): |
| 787 | + if globalns is None: |
| 788 | + base_globals = getattr(sys.modules.get(base.__module__, None), "__dict__", {}) # type: ignore[no-any-expr] |
| 789 | + else: |
| 790 | + base_globals = globalns |
| 791 | + ann = base.__dict__.get("__annotations__", {}) # type: ignore[no-any-expr] |
| 792 | + if isinstance(ann, types.GetSetDescriptorType): # type: ignore[no-any-expr] |
| 793 | + ann = {} # type: ignore[no-any-expr] |
| 794 | + base_locals = dict(vars(base)) if localns is None else localns # type: ignore[no-any-expr] |
| 795 | + if localns is None and globalns is None: |
| 796 | + # This is surprising, but required. Before Python 3.10, |
| 797 | + # get_type_hints only evaluated the globalns of |
| 798 | + # a class. To maintain backwards compatibility, we reverse |
| 799 | + # the globalns and localns order so that eval() looks into |
| 800 | + # *base_globals* first rather than *base_locals*. |
| 801 | + # This only affects ForwardRefs. |
| 802 | + base_globals, base_locals = base_locals, base_globals |
| 803 | + # start not copied section |
| 804 | + if base is obj: |
| 805 | + # add the class to the scope |
| 806 | + base_locals[obj.__name__] = obj # type: ignore[no-any-expr] |
| 807 | + # end not copied section |
| 808 | + for name, value in ann.items(): # type: ignore[no-any-expr] |
| 809 | + if value is None: # type: ignore[no-any-expr] |
| 810 | + value = type(None) |
| 811 | + if isinstance(value, str): # type: ignore[no-any-expr] |
| 812 | + if sys.version_info < (3, 9): |
| 813 | + value = ForwardRef(value, is_argument=False) |
| 814 | + else: |
| 815 | + value = ForwardRef(value, is_argument=False, is_class=True) |
| 816 | + value = typing._eval_type(value, base_globals, base_locals, recursive_guard=1) # type: ignore[attr-defined, no-any-expr] |
| 817 | + hints[name] = value # type: ignore[no-any-expr] |
| 818 | + |
| 819 | + return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()} # type: ignore[no-any-expr] |
| 820 | + |
| 821 | + if globalns is None: |
| 822 | + if isinstance(obj, types.ModuleType): # type: ignore[no-any-expr] |
| 823 | + globalns = obj.__dict__ |
| 824 | + else: |
| 825 | + nsobj = obj |
| 826 | + # Find globalns for the unwrapped object. |
| 827 | + while hasattr(nsobj, "__wrapped__"): |
| 828 | + nsobj = nsobj.__wrapped__ # type: ignore[no-any-expr] |
| 829 | + globalns = getattr(nsobj, "__globals__", {}) # type: ignore[no-any-expr] |
| 830 | + if localns is None: |
| 831 | + localns = globalns |
| 832 | + elif localns is None: |
| 833 | + localns = globalns |
| 834 | + hints = getattr(obj, "__annotations__", None) # type: ignore[assignment, no-any-expr] |
| 835 | + if hints is None: # type: ignore[no-any-expr, redundant-expr] |
| 836 | + # Return empty annotations for something that _could_ have them. |
| 837 | + if isinstance(obj, typing._allowed_types): # type: ignore[ unreachable] |
| 838 | + return {} |
| 839 | + raise TypeError(f"{obj!r} is not a module, class, method, " "or function.") |
| 840 | + hints = dict(hints) # type: ignore[no-any-expr] |
| 841 | + for name, value in hints.items(): # type: ignore[no-any-expr] |
| 842 | + if value is None: # type: ignore[no-any-expr] |
| 843 | + value = type(None) |
| 844 | + if isinstance(value, str): # type: ignore[no-any-expr] |
| 845 | + # class-level forward refs were handled above, this must be either |
| 846 | + # a module-level annotation or a function argument annotation |
| 847 | + value = ForwardRef( |
| 848 | + value, |
| 849 | + is_argument=not isinstance(cast(object, obj), types.ModuleType), |
| 850 | + is_class=False, |
| 851 | + ) |
| 852 | + hints[name] = typing._eval_type(value, globalns, localns) # type: ignore[no-any-expr, attr-defined] |
| 853 | + return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()} # type: ignore[no-any-expr] |
0 commit comments