|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -import functools |
4 | | -import sys |
5 | | -import types |
6 | | -from typing import TYPE_CHECKING |
7 | | -from typing import Any |
8 | | -from typing import Callable |
9 | | - |
10 | | -if TYPE_CHECKING: |
11 | | - from collections.abc import Mapping |
12 | | - |
13 | 3 | __all__ = ["get_annotations"] |
14 | 4 |
|
15 | 5 |
|
16 | | -if sys.version_info >= (3, 10): # pragma: no cover |
17 | | - from inspect import get_annotations |
18 | | -else: # pragma: no cover |
19 | | - |
20 | | - def get_annotations( # noqa: C901, PLR0912, PLR0915 |
21 | | - obj: Callable[..., object] | type[Any] | types.ModuleType, |
22 | | - *, |
23 | | - globals: Mapping[str, Any] | None = None, # noqa: A002 |
24 | | - locals: Mapping[str, Any] | None = None, # noqa: A002 |
25 | | - eval_str: bool = False, |
26 | | - ) -> dict[str, Any]: |
27 | | - """Compute the annotations dict for an object. |
28 | | -
|
29 | | - obj may be a callable, class, or module. |
30 | | - Passing in an object of any other type raises TypeError. |
31 | | -
|
32 | | - Returns a dict. get_annotations() returns a new dict every time |
33 | | - it's called; calling it twice on the same object will return two |
34 | | - different but equivalent dicts. |
35 | | -
|
36 | | - This function handles several details for you: |
37 | | -
|
38 | | - * If eval_str is true, values of type str will |
39 | | - be un-stringized using eval(). This is intended |
40 | | - for use with stringized annotations |
41 | | - ("from __future__ import annotations"). |
42 | | - * If obj doesn't have an annotations dict, returns an |
43 | | - empty dict. (Functions and methods always have an |
44 | | - annotations dict; classes, modules, and other types of |
45 | | - callables may not.) |
46 | | - * Ignores inherited annotations on classes. If a class |
47 | | - doesn't have its own annotations dict, returns an empty dict. |
48 | | - * All accesses to object members and dict values are done |
49 | | - using getattr() and dict.get() for safety. |
50 | | - * Always, always, always returns a freshly-created dict. |
51 | | -
|
52 | | - eval_str controls whether or not values of type str are replaced |
53 | | - with the result of calling eval() on those values: |
54 | | -
|
55 | | - * If eval_str is true, eval() is called on values of type str. |
56 | | - * If eval_str is false (the default), values of type str are unchanged. |
57 | | -
|
58 | | - globals and locals are passed in to eval(); see the documentation |
59 | | - for eval() for more information. If either globals or locals is |
60 | | - None, this function may replace that value with a context-specific |
61 | | - default, contingent on type(obj): |
62 | | -
|
63 | | - * If obj is a module, globals defaults to obj.__dict__. |
64 | | - * If obj is a class, globals defaults to |
65 | | - sys.modules[obj.__module__].__dict__ and locals |
66 | | - defaults to the obj class namespace. |
67 | | - * If obj is a callable, globals defaults to obj.__globals__, |
68 | | - although if obj is a wrapped function (using |
69 | | - functools.update_wrapper()) it is first unwrapped. |
70 | | - """ |
71 | | - if isinstance(obj, type): |
72 | | - # class |
73 | | - obj_dict = getattr(obj, "__dict__", None) |
74 | | - if obj_dict and hasattr(obj_dict, "get"): |
75 | | - ann = obj_dict.get("__annotations__", None) |
76 | | - if isinstance(ann, types.GetSetDescriptorType): |
77 | | - ann = None |
78 | | - else: |
79 | | - ann = None |
80 | | - |
81 | | - obj_globals = None |
82 | | - module_name = getattr(obj, "__module__", None) |
83 | | - if module_name: |
84 | | - module = sys.modules.get(module_name, None) |
85 | | - if module: |
86 | | - obj_globals = getattr(module, "__dict__", None) |
87 | | - obj_locals = dict(vars(obj)) |
88 | | - unwrap = obj |
89 | | - elif isinstance(obj, types.ModuleType): |
90 | | - # module |
91 | | - ann = getattr(obj, "__annotations__", None) |
92 | | - obj_globals = obj.__dict__ |
93 | | - obj_locals = None |
94 | | - unwrap = None |
95 | | - elif callable(obj): |
96 | | - # this includes types.Function, types.BuiltinFunctionType, |
97 | | - # types.BuiltinMethodType, functools.partial, functools.singledispatch, |
98 | | - # "class funclike" from Lib/test/test_inspect... on and on it goes. |
99 | | - ann = getattr(obj, "__annotations__", None) |
100 | | - obj_globals = getattr(obj, "__globals__", None) |
101 | | - obj_locals = None |
102 | | - unwrap = obj |
103 | | - else: |
104 | | - msg = f"{obj!r} is not a module, class, or callable." |
105 | | - raise TypeError(msg) |
106 | | - |
107 | | - if ann is None: |
108 | | - return {} |
109 | | - |
110 | | - if not isinstance(ann, dict): |
111 | | - msg = f"{obj!r}.__annotations__ is neither a dict nor None" |
112 | | - raise ValueError(msg) # noqa: TRY004 |
113 | | - |
114 | | - if not ann: |
115 | | - return {} |
116 | | - |
117 | | - if not eval_str: |
118 | | - return dict(ann) |
119 | | - |
120 | | - if unwrap is not None: |
121 | | - while True: |
122 | | - if hasattr(unwrap, "__wrapped__"): |
123 | | - unwrap = unwrap.__wrapped__ |
124 | | - continue |
125 | | - if isinstance(unwrap, functools.partial): |
126 | | - unwrap = unwrap.func |
127 | | - continue |
128 | | - break |
129 | | - if hasattr(unwrap, "__globals__"): |
130 | | - obj_globals = unwrap.__globals__ |
131 | | - |
132 | | - if globals is None: |
133 | | - globals = obj_globals # noqa: A001 |
134 | | - if locals is None: |
135 | | - locals = obj_locals # noqa: A001 |
136 | | - |
137 | | - eval_func = eval |
138 | | - return { |
139 | | - key: value |
140 | | - if not isinstance(value, str) |
141 | | - else eval_func(value, globals, locals) |
142 | | - for key, value in ann.items() |
143 | | - } |
| 6 | +from inspect import get_annotations |
0 commit comments