8000 gh-133684: Fix get_annotations() where PEP 563 is involved (#133795) · faster-cpython/cpython@3e562b3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3e562b3

Browse files
pythongh-133684: Fix get_annotations() where PEP 563 is involved (python#133795)
1 parent 4443110 commit 3e562b3

File tree

3 files changed

+84
-4
lines changed

3 files changed

+84
-4
lines changed

Lib/annotationlib.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,14 +1042,27 @@ def _get_and_call_annotate(obj, format):
10421042
return None
10431043

10441044

1045+
_BASE_GET_ANNOTATIONS = type.__dict__["__annotations__"].__get__
1046+
1047+
10451048
def _get_dunder_annotations(obj):
10461049
"""Return the annotations for an object, checking that it is a dictionary.
10471050
10481051
Does not return a fresh dictionary.
10491052
"""
1050-
ann = getattr(obj, "__annotations__", None)
1051-
if ann is None:
1052-
return None
1053+
# This special case is needed to support types defined under
1054+
# from __future__ import annotations, where accessing the __annotations__
1055+
# attribute directly might return annotations for the wrong class.
1056+
if isinstance(obj, type):
1057+
try:
1058+
ann = _BASE_GET_ANNOTATIONS(obj)
1059+
except AttributeError:
1060+
# For static types, the descriptor raises AttributeError.
1061+
return None
1062+
else:
1063+
ann = getattr(obj, "__annotations__", None)
1064+
if ann is None:
1065+
return None
10531066

10541067
if not isinstance(ann, dict):
10551068
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")

Lib/test/test_annotationlib.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import functools
88
import itertools
99
import pickle
10-
from string.templatelib import Interpolation, Template
10+
from string.templatelib import Template
1111
import typing
1212
import unittest
1313
from annotationlib import (
@@ -815,6 +815,70 @@ def test_stringized_annotations_on_class(self):
815815
{"x": int},
816816
)
817817

818+
def test_stringized_annotation_permutations(self):
819+
def define_class(name, has_future, has_annos, base_text, extra_names=None):
820+
lines = []
821+
if has_future:
822+
lines.append("from __future__ import annotations")
823+
lines.append(f"class {name}({base_text}):")
824+
if has_annos:
825+
lines.append(f" {name}_attr: int")
826+
else:
827+
lines.append(" pass")
828+
code = "\n".join(lines)
829+
ns = support.run_code(code, extra_names=extra_names)
830+
return ns[name]
831+
832+
def check_annotations(cls, has_future, has_annos):
833+
if has_annos:
834+
if has_future:
835+
anno = "int"
836+
else:
837+
anno = int
838+
self.assertEqual(get_annotations(cls), {f"{cls.__name__}_attr": anno})
839+
else:
840+
self.assertEqual(get_annotations(cls), {})
841+
842+
for meta_future, base_future, child_future, meta_has_annos, base_has_annos, child_has_annos in itertools.product(
843+
(False, True),
844+
(False, True),
845+
(False, True),
846+
(False, True),
847+
(False, True),
848+
(False, True),
849+
):
850+
with self.subTest(
851+
meta_future=meta_future,
852+
base_future=base_future,
853+
child_future=child_future,
854+
meta_has_annos=meta_has_annos,
855+
base_has_annos=base_has_annos,
856+
child_has_annos=child_has_annos,
857+
):
858+
meta = define_class(
859+
"Meta",
860+
has_future=meta_future,
861+
has_annos=meta_has_annos,
862+
base_text="type",
863+
)
864+
base = define_class(
865+
"Base",
866+
has_future=base_future,
867+
has_annos=base_has_annos,
868+
base_text="metaclass=Meta",
869+
extra_names={"Meta": meta},
870+
)
871+
child = define_class(
872+
"Child",
873+
has_future=child_future,
874+
has_annos=child_has_annos,
875+
base_text="Base",
876+
extra_names={"Base": base},
877+
)
878+
check_annotations(meta, meta_future, meta_has_annos)
879+
check_annotations(base, base_future, base_has_annos)
880+
check_annotations(child, child_future, child_has_annos)
881+
818882
def test_modify_annotations(self):
819883
def f(x: int):
820884
pass
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix bug where :func:`annotationlib.get_annotations` would return the wrong
2+
result for certain classes that are part of a class hierarchy where ``from
3+
__future__ import annotations`` is used.

0 commit comments

Comments
 (0)
0