Skip to content

Commit 89b502a

Browse files
committed
annotationlib: Use __annotate__ if only it is present
1 parent 7bb1e1a commit 89b502a

File tree

2 files changed

+44
-7
lines changed

2 files changed

+44
-7
lines changed

Lib/annotationlib.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -649,8 +649,12 @@ def get_annotations(
649649
):
650650
"""Compute the annotations dict for an object.
651651
652-
obj may be a callable, class, or module.
653-
Passing in an object of any other type raises TypeError.
652+
obj may be a callable, class, module, or any other object with an
653+
__annotations__ or __annotate__ attribute. For the VALUE and
654+
FORWARDREF formats, __annotations__ is tried first, and __annotate__
655+
is used as a fallback. The STRING format uses __annotate__ if it exists,
656+
but falls back to accessing __annotations__ and using annotations_to_string()
657+
to stringify the annotations.
654658
655659
Returns a dict. get_annotations() returns a new dict every time
656660
it's called; calling it twice on the same object will return two
@@ -696,14 +700,21 @@ def get_annotations(
696700

697701
match format:
698702
case Format.VALUE:
699-
# For VALUE, we only look at __annotations__
703+
# For VALUE, we first look at __annotations__
700704
ann = _get_dunder_annotations(obj)
705+
706+
# If it's not there, try __annotate__ instead
707+
if ann is None:
708+
ann = _get_and_call_annotate(obj, format)
701709
case Format.FORWARDREF:
702710
# For FORWARDREF, we use __annotations__ if it exists
703711
try:
704-
return dict(_get_dunder_annotations(obj))
712+
ann = _get_dunder_annotations(obj)
705713
except NameError:
706714
pass
715+
else:
716+
if ann is not None:
717+
return dict(ann)
707718

708719
# But if __annotations__ threw a NameError, we try calling __annotate__
709720
ann = _get_and_call_annotate(obj, format)
@@ -713,14 +724,19 @@ def get_annotations(
713724
# If that didn't work either, we have a very weird object: evaluating
714725
# __annotations__ threw NameError and there is no __annotate__. In that case,
715726
# we fall back to trying __annotations__ again.
716-
return dict(_get_dunder_annotations(obj))
727+
ann = _get_dunder_annotations(obj)
728+
if ann is None:
729+
return {}
730+
return dict(ann)
717731
case Format.STRING:
718732
# For STRING, we try to call __annotate__
719733
ann = _get_and_call_annotate(obj, format)
720734
if ann is not None:
721735
return ann
722736
# But if we didn't get it, we use __annotations__ instead.
723737
ann = _get_dunder_annotations(obj)
738+
if ann is None:
739+
return {}
724740
return annotations_to_string(ann)
725741
case Format.VALUE_WITH_FAKE_GLOBALS:
726742
raise ValueError("The VALUE_WITH_FAKE_GLOBALS format is for internal use only")
@@ -836,11 +852,11 @@ def _get_dunder_annotations(obj):
836852
ann = _BASE_GET_ANNOTATIONS(obj)
837853
except AttributeError:
838854
# For static types, the descriptor raises AttributeError.
839-
return {}
855+
return None
840856
else:
841857
ann = getattr(obj, "__annotations__", None)
842858
if ann is None:
843-
return {}
859+
return None
844860

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

Lib/test/test_annotationlib.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,27 @@ def __annotate__(self):
885885
annotationlib.get_annotations(hb, format=Format.STRING), {"x": str}
886886
)
887887

888+
def test_only_annotate(self):
889+
def f(x: int):
890+
pass
891+
892+
class OnlyAnnotate:
893+
@property
894+
def __annotate__(self):
895+
return f.__annotate__
896+
897+
oa = OnlyAnnotate()
898+
self.assertEqual(
899+
annotationlib.get_annotations(oa, format=Format.VALUE), {"x": int}
900+
)
901+
self.assertEqual(
902+
annotationlib.get_annotations(oa, format=Format.FORWARDREF), {"x": int}
903+
)
904+
self.assertEqual(
905+
annotationlib.get_annotations(oa, format=Format.STRING),
906+
{"x": "int"},
907+
)
908+
888909
def test_pep695_generic_class_with_future_annotations(self):
889910
ann_module695 = inspect_stringized_annotations_pep695
890911
A_annotations = annotationlib.get_annotations(ann_module695.A, eval_str=True)

0 commit comments

Comments
 (0)