Skip to content

Commit ac5fd65

Browse files
committed
pythongh-114053: Fix bad interaction of PEP 695, PEP 563 and inspect.get_annotations
1 parent 38a25e9 commit ac5fd65

File tree

4 files changed

+67
-1
lines changed

4 files changed

+67
-1
lines changed

Lib/inspect.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,13 @@ def get_annotations(obj, *, globals=None, locals=None, eval_str=False):
280280
if globals is None:
281281
globals = obj_globals
282282
if locals is None:
283-
locals = obj_locals
283+
locals = obj_locals or {}
284+
285+
# "Inject" type parameters into the local namespace
286+
# (unless they are shadowed by assignments *in* the local namespace),
287+
# as a way of emulating annotation scopes when calling `eval()`
288+
if type_params := getattr(obj, "__type_params__", ()):
289+
locals = {param.__name__: param for param in type_params} | locals
284290

285291
return_value = {key:
286292
value if not isinstance(value, str) else eval(value, globals, locals)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
from __future__ import annotations
3+
from typing import Callable, Unpack
4+
5+
6+
class A[T, *Ts, **P]:
7+
x: T
8+
y: tuple[*Ts]
9+
z: Callable[P, str]
10+
11+
12+
class B[T, *Ts, **P]:
13+
T = int
14+
Ts = str
15+
P = bytes
16+
x: T
17+
y: Ts
18+
z: P
19+
20+
21+
def generic_function[T, *Ts, **P](
22+
x: T, *y: Unpack[Ts], z: P.args, zz: P.kwargs
23+
) -> None: ...

Lib/test/test_inspect/test_inspect.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
from test.test_inspect import inspect_stock_annotations
4848
from test.test_inspect import inspect_stringized_annotations
4949
from test.test_inspect import inspect_stringized_annotations_2
50+
from test.test_inspect import inspect_stringized_annotations_pep695
5051

5152

5253
# Functions tested in this suite:
@@ -1692,6 +1693,38 @@ def wrapper(a, b):
16921693
self.assertEqual(inspect.get_annotations(isa.MyClassWithLocalAnnotations), {'x': 'mytype'})
16931694
self.assertEqual(inspect.get_annotations(isa.MyClassWithLocalAnnotations, eval_str=True), {'x': int})
16941695

1696+
def test_get_annotations_with_stringized_pep695_annotations(self):
1697+
from typing import Unpack
1698+
ann_module695 = inspect_stringized_annotations_pep695
1699+
1700+
A_annotations = inspect.get_annotations(ann_module695.A, eval_str=True)
1701+
A_type_params = ann_module695.A.__type_params__
1702+
self.assertIs(A_annotations["x"], A_type_params[0])
1703+
self.assertEqual(A_annotations["y"].__args__[0], Unpack[A_type_params[1]])
1704+
self.assertIs(A_annotations["z"].__args__[0], A_type_params[2])
1705+
1706+
B_annotations = inspect.get_annotations(ann_module695.B, eval_str=True)
1707+
self.assertEqual(B_annotations.keys(), {"x", "y", "z"})
1708+
self.assertEqual(
1709+
set(B_annotations.values()).intersection(ann_module695.B.__type_params__),
1710+
set()
1711+
)
1712+
1713+
generic_function_annotations = inspect.get_annotations(
1714+
ann_module695.generic_function, eval_str=True
1715+
)
1716+
func_t_params = ann_module695.generic_function.__type_params__
1717+
self.assertEqual(
1718+
generic_function_annotations.keys(), {"x", "y", "z", "zz", "return"}
1719+
)
1720+
self.assertIs(generic_function_annotations["x"], func_t_params[0])
1721+
self.assertEqual(
1722+
generic_function_annotations["y"],
1723+
Unpack[func_t_params[1]]
1724+
)
1725+
self.assertIs(generic_function_annotations["z"].__origin__, func_t_params[2])
1726+
self.assertIs(generic_function_annotations["zz"].__origin__, func_t_params[2])
1727+
16951728

16961729
class TestFormatAnnotation(unittest.TestCase):
16971730
def test_typing_replacement(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix erroneous :exc:`NameError` when calling :func:`inspect.get_annotations`
2+
with ``eval_str=True``` on a class that made use of :pep:`695` type
3+
parameters in a module that had ``from __future__ import annotations`` at
4+
the top of the file. Patch by Alex Waygood.

0 commit comments

Comments
 (0)