mirror of
https://github.com/python/cpython.git
synced 2025-09-25 01:43:11 +00:00
[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 9836503b48
)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
parent
a962934106
commit
d6cb8fa86e
4 changed files with 41 additions and 4 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -8538,6 +8538,36 @@ class TypedDictTests(BaseTestCase):
|
|||
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"}))
|
||||
|
|
|
@ -3087,14 +3087,16 @@ class _TypedDictMeta(type):
|
|||
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
|
||||
|
@ -3165,7 +3167,7 @@ class _TypedDictMeta(type):
|
|||
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(
|
||||
|
|
|
@ -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.
|
Loading…
Add table
Add a link
Reference in a new issue