mirror of https://github.com/python/cpython
656 lines
24 KiB
Python
656 lines
24 KiB
Python
"""Helpers for introspecting and wrapping annotations."""
|
|
|
|
import ast
|
|
import enum
|
|
import functools
|
|
import sys
|
|
import types
|
|
|
|
__all__ = ["Format", "ForwardRef", "call_annotate_function", "get_annotations"]
|
|
|
|
|
|
class Format(enum.IntEnum):
|
|
VALUE = 1
|
|
FORWARDREF = 2
|
|
SOURCE = 3
|
|
|
|
|
|
_Union = None
|
|
_sentinel = object()
|
|
|
|
# Slots shared by ForwardRef and _Stringifier. The __forward__ names must be
|
|
# preserved for compatibility with the old typing.ForwardRef class. The remaining
|
|
# names are private.
|
|
_SLOTS = (
|
|
"__forward_evaluated__",
|
|
"__forward_value__",
|
|
"__forward_is_argument__",
|
|
"__forward_is_class__",
|
|
"__forward_module__",
|
|
"__weakref__",
|
|
"__arg__",
|
|
"__ast_node__",
|
|
"__code__",
|
|
"__globals__",
|
|
"__owner__",
|
|
"__cell__",
|
|
)
|
|
|
|
|
|
class ForwardRef:
|
|
"""Wrapper that holds a forward reference."""
|
|
|
|
__slots__ = _SLOTS
|
|
|
|
def __init__(
|
|
self,
|
|
arg,
|
|
*,
|
|
module=None,
|
|
owner=None,
|
|
is_argument=True,
|
|
is_class=False,
|
|
_globals=None,
|
|
_cell=None,
|
|
):
|
|
if not isinstance(arg, str):
|
|
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
|
|
|
|
self.__arg__ = arg
|
|
self.__forward_evaluated__ = False
|
|
self.__forward_value__ = None
|
|
self.__forward_is_argument__ = is_argument
|
|
self.__forward_is_class__ = is_class
|
|
self.__forward_module__ = module
|
|
self.__code__ = None
|
|
self.__ast_node__ = None
|
|
self.__globals__ = _globals
|
|
self.__cell__ = _cell
|
|
self.__owner__ = owner
|
|
|
|
def __init_subclass__(cls, /, *args, **kwds):
|
|
raise TypeError("Cannot subclass ForwardRef")
|
|
|
|
def evaluate(self, *, globals=None, locals=None, type_params=None, owner=None):
|
|
"""Evaluate the forward reference and return the value.
|
|
|
|
If the forward reference is not evaluatable, raise an exception.
|
|
"""
|
|
if self.__forward_evaluated__:
|
|
return self.__forward_value__
|
|
if self.__cell__ is not None:
|
|
try:
|
|
value = self.__cell__.cell_contents
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
self.__forward_evaluated__ = True
|
|
self.__forward_value__ = value
|
|
return value
|
|
if owner is None:
|
|
owner = self.__owner__
|
|
if type_params is None and owner is None:
|
|
raise TypeError("Either 'type_params' or 'owner' must be provided")
|
|
|
|
if self.__forward_module__ is not None:
|
|
globals = getattr(
|
|
sys.modules.get(self.__forward_module__, None), "__dict__", globals
|
|
)
|
|
if globals is None:
|
|
globals = self.__globals__
|
|
if globals is None:
|
|
if isinstance(owner, type):
|
|
module_name = getattr(owner, "__module__", None)
|
|
if module_name:
|
|
module = sys.modules.get(module_name, None)
|
|
if module:
|
|
globals = getattr(module, "__dict__", None)
|
|
elif isinstance(owner, types.ModuleType):
|
|
globals = getattr(owner, "__dict__", None)
|
|
elif callable(owner):
|
|
globals = getattr(owner, "__globals__", None)
|
|
|
|
if locals is None:
|
|
locals = {}
|
|
if isinstance(self.__owner__, type):
|
|
locals.update(vars(self.__owner__))
|
|
|
|
if type_params is None and self.__owner__ is not None:
|
|
# "Inject" type parameters into the local namespace
|
|
# (unless they are shadowed by assignments *in* the local namespace),
|
|
# as a way of emulating annotation scopes when calling `eval()`
|
|
type_params = getattr(self.__owner__, "__type_params__", None)
|
|
|
|
# type parameters require some special handling,
|
|
# as they exist in their own scope
|
|
# but `eval()` does not have a dedicated parameter for that scope.
|
|
# For classes, names in type parameter scopes should override
|
|
# names in the global scope (which here are called `localns`!),
|
|
# but should in turn be overridden by names in the class scope
|
|
# (which here are called `globalns`!)
|
|
if type_params is not None:
|
|
globals, locals = dict(globals), dict(locals)
|
|
for param in type_params:
|
|
param_name = param.__name__
|
|
if not self.__forward_is_class__ or param_name not in globals:
|
|
globals[param_name] = param
|
|
locals.pop(param_name, None)
|
|
|
|
code = self.__forward_code__
|
|
value = eval(code, globals=globals, locals=locals)
|
|
self.__forward_evaluated__ = True
|
|
self.__forward_value__ = value
|
|
return value
|
|
|
|
def _evaluate(self, globalns, localns, type_params=_sentinel, *, recursive_guard):
|
|
import typing
|
|
import warnings
|
|
|
|
if type_params is _sentinel:
|
|
typing._deprecation_warning_for_no_type_params_passed(
|
|
"typing.ForwardRef._evaluate"
|
|
)
|
|
type_params = ()
|
|
warnings._deprecated(
|
|
"ForwardRef._evaluate",
|
|
"{name} is a private API and is retained for compatibility, but will be removed"
|
|
" in Python 3.16. Use ForwardRef.evaluate() or typing.evaluate_forward_ref() instead.",
|
|
remove=(3, 16),
|
|
)
|
|
return typing.evaluate_forward_ref(
|
|
self,
|
|
globals=globalns,
|
|
locals=localns,
|
|
type_params=type_params,
|
|
_recursive_guard=recursive_guard,
|
|
)
|
|
|
|
@property
|
|
def __forward_arg__(self):
|
|
if self.__arg__ is not None:
|
|
return self.__arg__
|
|
if self.__ast_node__ is not None:
|
|
self.__arg__ = ast.unparse(self.__ast_node__)
|
|
return self.__arg__
|
|
raise AssertionError(
|
|
"Attempted to access '__forward_arg__' on an uninitialized ForwardRef"
|
|
)
|
|
|
|
@property
|
|
def __forward_code__(self):
|
|
if self.__code__ is not None:
|
|
return self.__code__
|
|
arg = self.__forward_arg__
|
|
# If we do `def f(*args: *Ts)`, then we'll have `arg = '*Ts'`.
|
|
# Unfortunately, this isn't a valid expression on its own, so we
|
|
# do the unpacking manually.
|
|
if arg.startswith("*"):
|
|
arg_to_compile = f"({arg},)[0]" # E.g. (*Ts,)[0] or (*tuple[int, int],)[0]
|
|
else:
|
|
arg_to_compile = arg
|
|
try:
|
|
self.__code__ = compile(arg_to_compile, "<string>", "eval")
|
|
except SyntaxError:
|
|
raise SyntaxError(f"Forward reference must be an expression -- got {arg!r}")
|
|
return self.__code__
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, ForwardRef):
|
|
return NotImplemented
|
|
if self.__forward_evaluated__ and other.__forward_evaluated__:
|
|
return (
|
|
self.__forward_arg__ == other.__forward_arg__
|
|
and self.__forward_value__ == other.__forward_value__
|
|
)
|
|
return (
|
|
self.__forward_arg__ == other.__forward_arg__
|
|
and self.__forward_module__ == other.__forward_module__
|
|
)
|
|
|
|
def __hash__(self):
|
|
return hash((self.__forward_arg__, self.__forward_module__))
|
|
|
|
def __or__(self, other):
|
|
global _Union
|
|
if _Union is None:
|
|
from typing import Union as _Union
|
|
return _Union[self, other]
|
|
|
|
def __ror__(self, other):
|
|
global _Union
|
|
if _Union is None:
|
|
from typing import Union as _Union
|
|
return _Union[other, self]
|
|
|
|
def __repr__(self):
|
|
if self.__forward_module__ is None:
|
|
module_repr = ""
|
|
else:
|
|
module_repr = f", module={self.__forward_module__!r}"
|
|
return f"ForwardRef({self.__forward_arg__!r}{module_repr})"
|
|
|
|
|
|
class _Stringifier:
|
|
# Must match the slots on ForwardRef, so we can turn an instance of one into an
|
|
# instance of the other in place.
|
|
__slots__ = _SLOTS
|
|
|
|
def __init__(self, node, globals=None, owner=None, is_class=False, cell=None):
|
|
assert isinstance(node, ast.AST)
|
|
self.__arg__ = None
|
|
self.__forward_evaluated__ = False
|
|
self.__forward_value__ = None
|
|
self.__forward_is_argument__ = False
|
|
self.__forward_is_class__ = is_class
|
|
self.__forward_module__ = None
|
|
self.__code__ = None
|
|
self.__ast_node__ = node
|
|
self.__globals__ = globals
|
|
self.__cell__ = cell
|
|
self.__owner__ = owner
|
|
|
|
def __convert(self, other):
|
|
if isinstance(other, _Stringifier):
|
|
return other.__ast_node__
|
|
elif isinstance(other, slice):
|
|
return ast.Slice(
|
|
lower=self.__convert(other.start) if other.start is not None else None,
|
|
upper=self.__convert(other.stop) if other.stop is not None else None,
|
|
step=self.__convert(other.step) if other.step is not None else None,
|
|
)
|
|
else:
|
|
return ast.Constant(value=other)
|
|
|
|
def __make_new(self, node):
|
|
return _Stringifier(
|
|
node, self.__globals__, self.__owner__, self.__forward_is_class__
|
|
)
|
|
|
|
# Must implement this since we set __eq__. We hash by identity so that
|
|
# stringifiers in dict keys are kept separate.
|
|
def __hash__(self):
|
|
return id(self)
|
|
|
|
def __getitem__(self, other):
|
|
# Special case, to avoid stringifying references to class-scoped variables
|
|
# as '__classdict__["x"]'.
|
|
if (
|
|
isinstance(self.__ast_node__, ast.Name)
|
|
and self.__ast_node__.id == "__classdict__"
|
|
):
|
|
raise KeyError
|
|
if isinstance(other, tuple):
|
|
elts = [self.__convert(elt) for elt in other]
|
|
other = ast.Tuple(elts)
|
|
else:
|
|
other = self.__convert(other)
|
|
assert isinstance(other, ast.AST), repr(other)
|
|
return self.__make_new(ast.Subscript(self.__ast_node__, other))
|
|
|
|
def __getattr__(self, attr):
|
|
return self.__make_new(ast.Attribute(self.__ast_node__, attr))
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
return self.__make_new(
|
|
ast.Call(
|
|
self.__ast_node__,
|
|
[self.__convert(arg) for arg in args],
|
|
[
|
|
ast.keyword(key, self.__convert(value))
|
|
for key, value in kwargs.items()
|
|
],
|
|
)
|
|
)
|
|
|
|
def __iter__(self):
|
|
yield self.__make_new(ast.Starred(self.__ast_node__))
|
|
|
|
def __repr__(self):
|
|
return ast.unparse(self.__ast_node__)
|
|
|
|
def __format__(self, format_spec):
|
|
raise TypeError("Cannot stringify annotation containing string formatting")
|
|
|
|
def _make_binop(op: ast.AST):
|
|
def binop(self, other):
|
|
return self.__make_new(
|
|
ast.BinOp(self.__ast_node__, op, self.__convert(other))
|
|
)
|
|
|
|
return binop
|
|
|
|
__add__ = _make_binop(ast.Add())
|
|
__sub__ = _make_binop(ast.Sub())
|
|
__mul__ = _make_binop(ast.Mult())
|
|
__matmul__ = _make_binop(ast.MatMult())
|
|
__truediv__ = _make_binop(ast.Div())
|
|
__mod__ = _make_binop(ast.Mod())
|
|
__lshift__ = _make_binop(ast.LShift())
|
|
__rshift__ = _make_binop(ast.RShift())
|
|
__or__ = _make_binop(ast.BitOr())
|
|
__xor__ = _make_binop(ast.BitXor())
|
|
__and__ = _make_binop(ast.BitAnd())
|
|
__floordiv__ = _make_binop(ast.FloorDiv())
|
|
__pow__ = _make_binop(ast.Pow())
|
|
|
|
del _make_binop
|
|
|
|
def _make_rbinop(op: ast.AST):
|
|
def rbinop(self, other):
|
|
return self.__make_new(
|
|
ast.BinOp(self.__convert(other), op, self.__ast_node__)
|
|
)
|
|
|
|
return rbinop
|
|
|
|
__radd__ = _make_rbinop(ast.Add())
|
|
__rsub__ = _make_rbinop(ast.Sub())
|
|
__rmul__ = _make_rbinop(ast.Mult())
|
|
__rmatmul__ = _make_rbinop(ast.MatMult())
|
|
__rtruediv__ = _make_rbinop(ast.Div())
|
|
__rmod__ = _make_rbinop(ast.Mod())
|
|
__rlshift__ = _make_rbinop(ast.LShift())
|
|
__rrshift__ = _make_rbinop(ast.RShift())
|
|
__ror__ = _make_rbinop(ast.BitOr())
|
|
__rxor__ = _make_rbinop(ast.BitXor())
|
|
__rand__ = _make_rbinop(ast.BitAnd())
|
|
__rfloordiv__ = _make_rbinop(ast.FloorDiv())
|
|
__rpow__ = _make_rbinop(ast.Pow())
|
|
|
|
del _make_rbinop
|
|
|
|
def _make_compare(op):
|
|
def compare(self, other):
|
|
return self.__make_new(
|
|
ast.Compare(
|
|
left=self.__ast_node__,
|
|
ops=[op],
|
|
comparators=[self.__convert(other)],
|
|
)
|
|
)
|
|
|
|
return compare
|
|
|
|
__lt__ = _make_compare(ast.Lt())
|
|
__le__ = _make_compare(ast.LtE())
|
|
__eq__ = _make_compare(ast.Eq())
|
|
__ne__ = _make_compare(ast.NotEq())
|
|
__gt__ = _make_compare(ast.Gt())
|
|
__ge__ = _make_compare(ast.GtE())
|
|
|
|
del _make_compare
|
|
|
|
def _make_unary_op(op):
|
|
def unary_op(self):
|
|
return self.__make_new(ast.UnaryOp(op, self.__ast_node__))
|
|
|
|
return unary_op
|
|
|
|
__invert__ = _make_unary_op(ast.Invert())
|
|
__pos__ = _make_unary_op(ast.UAdd())
|
|
__neg__ = _make_unary_op(ast.USub())
|
|
|
|
del _make_unary_op
|
|
|
|
|
|
class _StringifierDict(dict):
|
|
def __init__(self, namespace, globals=None, owner=None, is_class=False):
|
|
super().__init__(namespace)
|
|
self.namespace = namespace
|
|
self.globals = globals
|
|
self.owner = owner
|
|
self.is_class = is_class
|
|
self.stringifiers = []
|
|
|
|
def __missing__(self, key):
|
|
fwdref = _Stringifier(
|
|
ast.Name(id=key),
|
|
globals=self.globals,
|
|
owner=self.owner,
|
|
is_class=self.is_class,
|
|
)
|
|
self.stringifiers.append(fwdref)
|
|
return fwdref
|
|
|
|
|
|
def call_annotate_function(annotate, format, owner=None):
|
|
"""Call an __annotate__ function. __annotate__ functions are normally
|
|
generated by the compiler to defer the evaluation of annotations. They
|
|
can be called with any of the format arguments in the Format enum, but
|
|
compiler-generated __annotate__ functions only support the VALUE format.
|
|
This function provides additional functionality to call __annotate__
|
|
functions with the FORWARDREF and SOURCE formats.
|
|
|
|
*annotate* must be an __annotate__ function, which takes a single argument
|
|
and returns a dict of annotations.
|
|
|
|
*format* must be a member of the Format enum or one of the corresponding
|
|
integer values.
|
|
|
|
*owner* can be the object that owns the annotations (i.e., the module,
|
|
class, or function that the __annotate__ function derives from). With the
|
|
FORWARDREF format, it is used to provide better evaluation capabilities
|
|
on the generated ForwardRef objects.
|
|
|
|
"""
|
|
try:
|
|
return annotate(format)
|
|
except NotImplementedError:
|
|
pass
|
|
if format == Format.SOURCE:
|
|
# SOURCE is implemented by calling the annotate function in a special
|
|
# environment where every name lookup results in an instance of _Stringifier.
|
|
# _Stringifier supports every dunder operation and returns a new _Stringifier.
|
|
# At the end, we get a dictionary that mostly contains _Stringifier objects (or
|
|
# possibly constants if the annotate function uses them directly). We then
|
|
# convert each of those into a string to get an approximation of the
|
|
# original source.
|
|
globals = _StringifierDict({})
|
|
if annotate.__closure__:
|
|
freevars = annotate.__code__.co_freevars
|
|
new_closure = []
|
|
for i, cell in enumerate(annotate.__closure__):
|
|
if i < len(freevars):
|
|
name = freevars[i]
|
|
else:
|
|
name = "__cell__"
|
|
fwdref = _Stringifier(ast.Name(id=name))
|
|
new_closure.append(types.CellType(fwdref))
|
|
closure = tuple(new_closure)
|
|
else:
|
|
closure = None
|
|
func = types.FunctionType(annotate.__code__, globals, closure=closure)
|
|
annos = func(Format.VALUE)
|
|
return {
|
|
key: val if isinstance(val, str) else repr(val)
|
|
for key, val in annos.items()
|
|
}
|
|
elif format == Format.FORWARDREF:
|
|
# FORWARDREF is implemented similarly to SOURCE, but there are two changes,
|
|
# at the beginning and the end of the process.
|
|
# First, while SOURCE uses an empty dictionary as the namespace, so that all
|
|
# name lookups result in _Stringifier objects, FORWARDREF uses the globals
|
|
# and builtins, so that defined names map to their real values.
|
|
# Second, instead of returning strings, we want to return either real values
|
|
# or ForwardRef objects. To do this, we keep track of all _Stringifier objects
|
|
# created while the annotation is being evaluated, and at the end we convert
|
|
# them all to ForwardRef objects by assigning to __class__. To make this
|
|
# technique work, we have to ensure that the _Stringifier and ForwardRef
|
|
# classes share the same attributes.
|
|
# We use this technique because while the annotations are being evaluated,
|
|
# we want to support all operations that the language allows, including even
|
|
# __getattr__ and __eq__, and return new _Stringifier objects so we can accurately
|
|
# reconstruct the source. But in the dictionary that we eventually return, we
|
|
# want to return objects with more user-friendly behavior, such as an __eq__
|
|
# that returns a bool and an defined set of attributes.
|
|
namespace = {**annotate.__builtins__, **annotate.__globals__}
|
|
is_class = isinstance(owner, type)
|
|
globals = _StringifierDict(namespace, annotate.__globals__, owner, is_class)
|
|
if annotate.__closure__:
|
|
freevars = annotate.__code__.co_freevars
|
|
new_closure = []
|
|
for i, cell in enumerate(annotate.__closure__):
|
|
try:
|
|
cell.cell_contents
|
|
except ValueError:
|
|
if i < len(freevars):
|
|
name = freevars[i]
|
|
else:
|
|
name = "__cell__"
|
|
fwdref = _Stringifier(
|
|
ast.Name(id=name),
|
|
cell=cell,
|
|
owner=owner,
|
|
globals=annotate.__globals__,
|
|
is_class=is_class,
|
|
)
|
|
globals.stringifiers.append(fwdref)
|
|
new_closure.append(types.CellType(fwdref))
|
|
else:
|
|
new_closure.append(cell)
|
|
closure = tuple(new_closure)
|
|
else:
|
|
closure = None
|
|
func = types.FunctionType(annotate.__code__, globals, closure=closure)
|
|
result = func(Format.VALUE)
|
|
for obj in globals.stringifiers:
|
|
obj.__class__ = ForwardRef
|
|
return result
|
|
elif format == Format.VALUE:
|
|
# Should be impossible because __annotate__ functions must not raise
|
|
# NotImplementedError for this format.
|
|
raise RuntimeError("annotate function does not support VALUE format")
|
|
else:
|
|
raise ValueError(f"Invalid format: {format!r}")
|
|
|
|
|
|
def get_annotations(
|
|
obj, *, globals=None, locals=None, eval_str=False, format=Format.VALUE
|
|
):
|
|
"""Compute the annotations dict for an object.
|
|
|
|
obj may be a callable, class, or module.
|
|
Passing in an object of any other type raises TypeError.
|
|
|
|
Returns a dict. get_annotations() returns a new dict every time
|
|
it's called; calling it twice on the same object will return two
|
|
different but equivalent dicts.
|
|
|
|
This function handles several details for you:
|
|
|
|
* If eval_str is true, values of type str will
|
|
be un-stringized using eval(). This is intended
|
|
for use with stringized annotations
|
|
("from __future__ import annotations").
|
|
* If obj doesn't have an annotations dict, returns an
|
|
empty dict. (Functions and methods always have an
|
|
annotations dict; classes, modules, and other types of
|
|
callables may not.)
|
|
* Ignores inherited annotations on classes. If a class
|
|
doesn't have its own annotations dict, returns an empty dict.
|
|
* All accesses to object members and dict values are done
|
|
using getattr() and dict.get() for safety.
|
|
* Always, always, always returns a freshly-created dict.
|
|
|
|
eval_str controls whether or not values of type str are replaced
|
|
with the result of calling eval() on those values:
|
|
|
|
* If eval_str is true, eval() is called on values of type str.
|
|
* If eval_str is false (the default), values of type str are unchanged.
|
|
|
|
globals and locals are passed in to eval(); see the documentation
|
|
for eval() for more information. If either globals or locals is
|
|
None, this function may replace that value with a context-specific
|
|
default, contingent on type(obj):
|
|
|
|
* If obj is a module, globals defaults to obj.__dict__.
|
|
* If obj is a class, globals defaults to
|
|
sys.modules[obj.__module__].__dict__ and locals
|
|
defaults to the obj class namespace.
|
|
* If obj is a callable, globals defaults to obj.__globals__,
|
|
although if obj is a wrapped function (using
|
|
functools.update_wrapper()) it is first unwrapped.
|
|
"""
|
|
if eval_str and format != Format.VALUE:
|
|
raise ValueError("eval_str=True is only supported with format=Format.VALUE")
|
|
|
|
# For VALUE format, we look at __annotations__ directly.
|
|
if format != Format.VALUE:
|
|
annotate = getattr(obj, "__annotate__", None)
|
|
if annotate is not None:
|
|
ann = call_annotate_function(annotate, format, owner=obj)
|
|
if not isinstance(ann, dict):
|
|
raise ValueError(f"{obj!r}.__annotate__ returned a non-dict")
|
|
return dict(ann)
|
|
|
|
ann = getattr(obj, "__annotations__", None)
|
|
if ann is None:
|
|
return {}
|
|
|
|
if not isinstance(ann, dict):
|
|
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
|
|
|
|
if not ann:
|
|
return {}
|
|
|
|
if not eval_str:
|
|
return dict(ann)
|
|
|
|
if isinstance(obj, type):
|
|
# class
|
|
obj_globals = None
|
|
module_name = getattr(obj, "__module__", None)
|
|
if module_name:
|
|
module = sys.modules.get(module_name, None)
|
|
if module:
|
|
obj_globals = getattr(module, "__dict__", None)
|
|
obj_locals = dict(vars(obj))
|
|
unwrap = obj
|
|
elif isinstance(obj, types.ModuleType):
|
|
# module
|
|
obj_globals = getattr(obj, "__dict__")
|
|
obj_locals = None
|
|
unwrap = None
|
|
elif callable(obj):
|
|
# this includes types.Function, types.BuiltinFunctionType,
|
|
# types.BuiltinMethodType, functools.partial, functools.singledispatch,
|
|
# "class funclike" from Lib/test/test_inspect... on and on it goes.
|
|
obj_globals = getattr(obj, "__globals__", None)
|
|
obj_locals = None
|
|
unwrap = obj
|
|
elif ann is not None:
|
|
obj_globals = obj_locals = unwrap = None
|
|
else:
|
|
raise TypeError(f"{obj!r} is not a module, class, or callable.")
|
|
|
|
if unwrap is not None:
|
|
while True:
|
|
if hasattr(unwrap, "__wrapped__"):
|
|
unwrap = unwrap.__wrapped__
|
|
continue
|
|
if isinstance(unwrap, functools.partial):
|
|
unwrap = unwrap.func
|
|
continue
|
|
break
|
|
if hasattr(unwrap, "__globals__"):
|
|
obj_globals = unwrap.__globals__
|
|
|
|
if globals is None:
|
|
globals = obj_globals
|
|
if locals is None:
|
|
locals = obj_locals
|
|
|
|
# "Inject" type parameters into the local namespace
|
|
# (unless they are shadowed by assignments *in* the local namespace),
|
|
# as a way of emulating annotation scopes when calling `eval()`
|
|
if type_params := getattr(obj, "__type_params__", ()):
|
|
if locals is None:
|
|
locals = {}
|
|
locals = {param.__name__: param for param in type_params} | locals
|
|
|
|
return_value = {
|
|
key: value if not isinstance(value, str) else eval(value, globals, locals)
|
|
for key, value in ann.items()
|
|
}
|
|
return return_value
|