https://github.com/python/cpython/commit/9836503b48e047db117b3bef3a812c40ed3e988a
commit: 9836503b48e047db117b3bef3a812c40ed3e988a
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2025-05-14T06:24:33-07:00
summary:
gh-133701: Fix incorrect `__annotations__` on TypedDict defined under PEP 563
(#133772)
files:
A Misc/NEWS.d/next/Library/2025-05-09-08-49-03.gh-issue-133701.KI8tGz.rst
M Lib/test/support/__init__.py
M Lib/test/test_typing.py
M Lib/typing.py
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index c74c3a3190947b..9b6e80fdad9747 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -696,9 +696,11 @@ def sortdict(dict):
return "{%s}" % withcommas
-def run_code(code: str) -> dict[str, object]:
+def run_code(code: str, extra_names: dict[str, object] | None = None) ->
dict[str, object]:
"""Run a piece of code after dedenting it, and return its global
namespace."""
ns = {}
+ if extra_names:
+ ns.update(extra_names)
exec(textwrap.dedent(code), ns)
return ns
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 6ef633e4545aef..246be22a0d8ec4 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -8487,6 +8487,36 @@ class Child(Base1, Base2):
self.assertEqual(Child.__required_keys__, frozenset(['a']))
self.assertEqual(Child.__optional_keys__, frozenset())
+ def test_inheritance_pep563(self):
+ def _make_td(future, class_name, annos, base, extra_names=None):
+ lines = []
+ if future:
+ lines.append('from __future__ import annotations')
+ lines.append('from typing import TypedDict')
+ lines.append(f'class {class_name}({base}):')
+ for name, anno in annos.items():
+ lines.append(f' {name}: {anno}')
+ code = '\n'.join(lines)
+ ns = run_code(code, extra_names)
+ return ns[class_name]
+
+ for base_future in (True, False):
+ for child_future in (True, False):
+ with self.subTest(base_future=base_future,
child_future=child_future):
+ base = _make_td(
+ base_future, "Base", {"base": "int"}, "TypedDict"
+ )
+ self.assertIsNotNone(base.__annotate__)
+ child = _make_td(
+ child_future, "Child", {"child": "int"}, "Base",
{"Base": base}
+ )
+ base_anno = ForwardRef("int", module="builtins") if
base_future else int
+ child_anno = ForwardRef("int", module="builtins") if
child_future else int
+ self.assertEqual(base.__annotations__, {'base': base_anno})
+ self.assertEqual(
+ child.__annotations__, {'child': child_anno, 'base':
base_anno}
+ )
+
def test_required_notrequired_keys(self):
self.assertEqual(NontotalMovie.__required_keys__,
frozenset({"title"}))
diff --git a/Lib/typing.py b/Lib/typing.py
index 3d64480e1431c1..98af61be8b0716 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -3048,14 +3048,16 @@ def __new__(cls, name, bases, ns, total=True):
else:
generic_base = ()
+ ns_annotations = ns.pop('__annotations__', None)
+
tp_dict = type.__new__(_TypedDictMeta, name, (*generic_base, dict), ns)
if not hasattr(tp_dict, '__orig_bases__'):
tp_dict.__orig_bases__ = bases
- if "__annotations__" in ns:
+ if ns_annotations is not None:
own_annotate = None
- own_annotations = ns["__annotations__"]
+ own_annotations = ns_annotations
elif (own_annotate :=
_lazy_annotationlib.get_annotate_from_class_namespace(ns)) is not None:
own_annotations = _lazy_annotationlib.call_annotate_function(
own_annotate, _lazy_annotationlib.Format.FORWARDREF,
owner=tp_dict
@@ -3126,7 +3128,7 @@ def __annotate__(format):
if base_annotate is None:
continue
base_annos = _lazy_annotationlib.call_annotate_function(
- base.__annotate__, format, owner=base)
+ base_annotate, format, owner=base)
annos.update(base_annos)
if own_annotate is not None:
own = _lazy_annotationlib.call_annotate_function(
diff --git
a/Misc/NEWS.d/next/Library/2025-05-09-08-49-03.gh-issue-133701.KI8tGz.rst
b/Misc/NEWS.d/next/Library/2025-05-09-08-49-03.gh-issue-133701.KI8tGz.rst
new file mode 100644
index 00000000000000..163d9b331d1f8a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-09-08-49-03.gh-issue-133701.KI8tGz.rst
@@ -0,0 +1,3 @@
+Fix bug where :class:`typing.TypedDict` classes defined under ``from
+__future__ import annotations`` and inheriting from another ``TypedDict``
+had an incorrect ``__annotations__`` attribute.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]