Skip to content

Commit ba1927c

Browse files
committed
Support and test generics as annotate callables
1 parent c16083b commit ba1927c

File tree

2 files changed

+33
-1
lines changed

2 files changed

+33
-1
lines changed

Lib/annotationlib.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,9 @@ def _get_annotate_attr(annotate, attr, default):
900900
if call_func := getattr(annotate.__call__, "__func__", None):
901901
return getattr(call_func, attr, default)
902902

903-
if isinstance(annotate, type):
903+
# Classes and generics are callable, usually the __init__ method sets attributes,
904+
# so let's access this method for fake globals and the like.
905+
if isinstance(annotate, type) or isinstance(annotate, types.GenericAlias):
904906
return getattr(annotate.__init__, attr, default)
905907

906908
if (wrapped := getattr(annotate, "__wrapped__", None)) is not None:
@@ -936,6 +938,17 @@ def _direct_call_annotate(func, annotate, format):
936938
func(inst, format)
937939
return inst
938940

941+
# Generic instantiation is slightly different.
942+
if isinstance(annotate, types.GenericAlias):
943+
inst = annotate.__new__(annotate.__origin__)
944+
func(inst, format)
945+
# Try to set the original class on the instance, if possible.
946+
try:
947+
inst.__orig_class__ = annotate
948+
except Exception:
949+
pass
950+
return inst
951+
939952
if functools := sys.modules.get("functools", None):
940953
# If annotate is a partial function, re-create it with the new function object.
941954
# We could call the function directly, but then we'd have to handle placeholders,

Lib/test/test_annotationlib.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,25 @@ def __init__(self, format, /, __Format=Format,
16871687

16881688
self.assertEqual(annotations, {"x": int})
16891689

1690+
def test_callable_generic_class_annotate_forwardref_value_fallback(self):
1691+
# If Format.STRING and Format.VALUE_WITH_FAKE_GLOBALS are not
1692+
# supported fall back to Format.VALUE and convert to strings
1693+
class Annotate[T](dict[T]):
1694+
def __init__(self, format, /, __Format=Format,
1695+
__NotImplementedError=NotImplementedError):
1696+
if format == __Format.VALUE:
1697+
super().__init__({"x": int})
1698+
else:
1699+
raise __NotImplementedError(format)
1700+
1701+
annotations = annotationlib.call_annotate_function(
1702+
Annotate[int],
1703+
Format.FORWARDREF,
1704+
)
1705+
1706+
self.assertEqual(annotations, {"x": int})
1707+
self.assertEqual(annotations.__orig_class__, Annotate[int])
1708+
16901709
def test_callable_partial_annotate_forwardref_value_fallback(self):
16911710
# If Format.STRING and Format.VALUE_WITH_FAKE_GLOBALS are not
16921711
# supported fall back to Format.VALUE and convert to strings

0 commit comments

Comments
 (0)