Skip to content

Commit d6cb8fa

Browse files
[3.14] gh-133701: Fix incorrect __annotations__ on TypedDict defined under PEP 563 (GH-133772) (#134003)
gh-133701: Fix incorrect `__annotations__` on TypedDict defined under PEP 563 (GH-133772) (cherry picked from commit 9836503) Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent a962934 commit d6cb8fa

File tree

4 files changed

+41
-4
lines changed

4 files changed

+41
-4
lines changed

Lib/test/support/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,9 +696,11 @@ def sortdict(dict):
696696
return "{%s}" % withcommas
697697

698698

699-
def run_code(code: str) -> dict[str, object]:
699+
def run_code(code: str, extra_names: dict[str, object] | None = None) -> dict[str, object]:
700700
"""Run a piece of code after dedenting it, and return its global namespace."""
701701
ns = {}
702+
if extra_names:
703+
ns.update(extra_names)
702704
exec(textwrap.dedent(code), ns)
703705
return ns
704706

Lib/test/test_typing.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8538,6 +8538,36 @@ class Child(Base1, Base2):
85388538
self.assertEqual(Child.__required_keys__, frozenset(['a']))
85398539
self.assertEqual(Child.__optional_keys__, frozenset())
85408540

8541+
def test_inheritance_pep563(self):
8542+
def _make_td(future, class_name, annos, base, extra_names=None):
8543+
lines = []
8544+
if future:
8545+
lines.append('from __future__ import annotations')
8546+
lines.append('from typing import TypedDict')
8547+
lines.append(f'class {class_name}({base}):')
8548+
for name, anno in annos.items():
8549+
lines.append(f' {name}: {anno}')
8550+
code = '\n'.join(lines)
8551+
ns = run_code(code, extra_names)
8552+
return ns[class_name]
8553+
8554+
for base_future in (True, False):
8555+
for child_future in (True, False):
8556+
with self.subTest(base_future=base_future, child_future=child_future):
8557+
base = _make_td(
8558+
base_future, "Base", {"base": "int"}, "TypedDict"
8559+
)
8560+
self.assertIsNotNone(base.__annotate__)
8561+
child = _make_td(
8562+
child_future, "Child", {"child": "int"}, "Base", {"Base": base}
8563+
)
8564+
base_anno = ForwardRef("int", module="builtins") if base_future else int
8565+
child_anno = ForwardRef("int", module="builtins") if child_future else int
8566+
self.assertEqual(base.__annotations__, {'base': base_anno})
8567+
self.assertEqual(
8568+
child.__annotations__, {'child': child_anno, 'base': base_anno}
8569+
)
8570+
85418571
def test_required_notrequired_keys(self):
85428572
self.assertEqual(NontotalMovie.__required_keys__,
85438573
frozenset({"title"}))

Lib/typing.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3087,14 +3087,16 @@ def __new__(cls, name, bases, ns, total=True):
30873087
else:
30883088
generic_base = ()
30893089

3090+
ns_annotations = ns.pop('__annotations__', None)
3091+
30903092
tp_dict = type.__new__(_TypedDictMeta, name, (*generic_base, dict), ns)
30913093

30923094
if not hasattr(tp_dict, '__orig_bases__'):
30933095
tp_dict.__orig_bases__ = bases
30943096

3095-
if "__annotations__" in ns:
3097+
if ns_annotations is not None:
30963098
own_annotate = None
3097-
own_annotations = ns["__annotations__"]
3099+
own_annotations = ns_annotations
30983100
elif (own_annotate := _lazy_annotationlib.get_annotate_from_class_namespace(ns)) is not None:
30993101
own_annotations = _lazy_annotationlib.call_annotate_function(
31003102
own_annotate, _lazy_annotationlib.Format.FORWARDREF, owner=tp_dict
@@ -3165,7 +3167,7 @@ def __annotate__(format):
31653167
if base_annotate is None:
31663168
continue
31673169
base_annos = _lazy_annotationlib.call_annotate_function(
3168-
base.__annotate__, format, owner=base)
3170+
base_annotate, format, owner=base)
31693171
annos.update(base_annos)
31703172
if own_annotate is not None:
31713173
own = _lazy_annotationlib.call_annotate_function(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix bug where :class:`typing.TypedDict` classes defined under ``from
2+
__future__ import annotations`` and inheriting from another ``TypedDict``
3+
had an incorrect ``__annotations__`` attribute.

0 commit comments

Comments
 (0)