Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
from keyword import iskeyword
from operator import attrgetter
from collections import namedtuple, OrderedDict
from typing import _rewrite_star_unpack
from weakref import ref as make_weakref

# Create constants for the compiler flags in Include/code.h
Expand Down Expand Up @@ -288,8 +289,9 @@ def get_annotations(obj, *, globals=None, locals=None, eval_str=False):
if type_params := getattr(obj, "__type_params__", ()):
locals = {param.__name__: param for param in type_params} | locals

return_value = {key:
value if not isinstance(value, str) else eval(value, globals, locals)
return_value = {
key: value if not isinstance(value, str)
else eval(_rewrite_star_unpack(value), globals, locals)
for key, value in ann.items() }
return return_value

Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1859,6 +1859,10 @@ def test_get_annotations_with_stringized_annotations(self):
self.assertEqual(inspect.get_annotations(isa2, eval_str=True), {})
self.assertEqual(inspect.get_annotations(isa2, eval_str=False), {})

def f(*args: *tuple[int, ...]): ...
self.assertEqual(inspect.get_annotations(f, eval_str=True),
{'args': (*tuple[int, ...],)[0]})

def times_three(fn):
@functools.wraps(fn)
def wrapper(a, b):
Expand Down
19 changes: 11 additions & 8 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,15 +1024,8 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
if not isinstance(arg, str):
raise TypeError(f"Forward reference must be a string -- got {arg!r}")

# 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:
code = compile(arg_to_compile, '<string>', 'eval')
code = compile(_rewrite_star_unpack(arg), '<string>', 'eval')
except SyntaxError:
raise SyntaxError(f"Forward reference must be an expression -- got {arg!r}")

Expand Down Expand Up @@ -1119,6 +1112,16 @@ def __repr__(self):
return f'ForwardRef({self.__forward_arg__!r}{module_repr})'


def _rewrite_star_unpack(arg):
"""If the given argument annotation expression is a star unpack e.g. `'*Ts'`
rewrite it to a valid expression.
"""
if arg.startswith("*"):
return f"({arg},)[0]" # E.g. (*Ts,)[0] or (*tuple[int, int],)[0]
else:
return arg


def _is_unpacked_typevartuple(x: Any) -> bool:
return ((not isinstance(x, type)) and
getattr(x, '__typing_is_unpacked_typevartuple__', False))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix ``SyntaxError`` when ``inspect.get_annotations(f, eval_str=True)`` is
called on a function annotated with a :pep:`646` ``star_expression``
Loading