gh-119180: Make FORWARDREF format look at __annotations__ first (#124479)

From discussion with Larry Hastings and Carl Meyer, this is the desired
behavior.
This commit is contained in:
Jelle Zijlstra 2024-09-25 15:32:45 -07:00 committed by GitHub
parent 4e2fb7bdf5
commit bc543936ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 141 additions and 24 deletions

View file

@ -740,17 +740,97 @@ class TestGetAnnotations(unittest.TestCase):
self.assertEqual(annotationlib.get_annotations(f), {"x": int})
self.assertEqual(
annotationlib.get_annotations(f, format=annotationlib.Format.FORWARDREF),
annotationlib.get_annotations(f, format=Format.FORWARDREF),
{"x": int},
)
f.__annotations__["x"] = str
# The modification is reflected in VALUE (the default)
self.assertEqual(annotationlib.get_annotations(f), {"x": str})
# ... but not in FORWARDREF, which uses __annotate__
# ... and also in FORWARDREF, which tries __annotations__ if available
self.assertEqual(
annotationlib.get_annotations(f, format=annotationlib.Format.FORWARDREF),
{"x": int},
annotationlib.get_annotations(f, format=Format.FORWARDREF),
{"x": str},
)
# ... but not in SOURCE which always uses __annotate__
self.assertEqual(
annotationlib.get_annotations(f, format=Format.SOURCE),
{"x": "int"},
)
def test_non_dict_annotations(self):
class WeirdAnnotations:
@property
def __annotations__(self):
return "not a dict"
wa = WeirdAnnotations()
for format in Format:
with (
self.subTest(format=format),
self.assertRaisesRegex(
ValueError, r".*__annotations__ is neither a dict nor None"
),
):
annotationlib.get_annotations(wa, format=format)
def test_annotations_on_custom_object(self):
class HasAnnotations:
@property
def __annotations__(self):
return {"x": int}
ha = HasAnnotations()
self.assertEqual(
annotationlib.get_annotations(ha, format=Format.VALUE), {"x": int}
)
self.assertEqual(
annotationlib.get_annotations(ha, format=Format.FORWARDREF), {"x": int}
)
# TODO(gh-124412): This should return {'x': 'int'} instead.
self.assertEqual(
annotationlib.get_annotations(ha, format=Format.SOURCE), {"x": int}
)
def test_raising_annotations_on_custom_object(self):
class HasRaisingAnnotations:
@property
def __annotations__(self):
return {"x": undefined}
hra = HasRaisingAnnotations()
with self.assertRaises(NameError):
annotationlib.get_annotations(hra, format=Format.VALUE)
with self.assertRaises(NameError):
annotationlib.get_annotations(hra, format=Format.FORWARDREF)
undefined = float
self.assertEqual(
annotationlib.get_annotations(hra, format=Format.VALUE), {"x": float}
)
def test_forwardref_prefers_annotations(self):
class HasBoth:
@property
def __annotations__(self):
return {"x": int}
@property
def __annotate__(self):
return lambda format: {"x": str}
hb = HasBoth()
self.assertEqual(
annotationlib.get_annotations(hb, format=Format.VALUE), {"x": int}
)
self.assertEqual(
annotationlib.get_annotations(hb, format=Format.FORWARDREF), {"x": int}
)
self.assertEqual(
annotationlib.get_annotations(hb, format=Format.SOURCE), {"x": str}
)
def test_pep695_generic_class_with_future_annotations(self):