Skip to content

Commit 1d35db0

Browse files
committed
Test that GenericAlias objects which cannot have __orig_class__ set don't raise an error when used as annotate functions
1 parent 6a48bbe commit 1d35db0

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

Lib/annotationlib.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,7 @@ def _direct_call_annotate(func, annotate, *args):
959959
inst = annotate.__new__(annotate.__origin__)
960960
func(inst, *args)
961961
# Try to set the original class on the instance, if possible.
962+
# This is the same logic used in typing for custom generics.
962963
try:
963964
inst.__orig_class__ = annotate
964965
except Exception:

Lib/test/test_annotationlib.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,6 +1533,28 @@ def __init__(self, format, /, __Format=Format, __NotImplementedError=NotImplemen
15331533

15341534
self.assertEqual(annotations, {"x": int})
15351535

1536+
def test_callable_generic_class_annotate_forwardref_fakeglobals(self):
1537+
# If Format.FORWARDREF is not supported, use Format.VALUE_WITH_FAKE_GLOBALS
1538+
# before falling back to Format.VALUE
1539+
class Annotate[K, V](dict[K, V]):
1540+
def __init__(self, format, /, __Format=Format, __NotImplementedError=NotImplementedError):
1541+
if format == __Format.VALUE:
1542+
super().__init__({'x': str})
1543+
elif format == __Format.VALUE_WITH_FAKE_GLOBALS:
1544+
super().__init__({'x': int})
1545+
else:
1546+
raise __NotImplementedError(format)
1547+
1548+
annotations = annotationlib.call_annotate_function(
1549+
Annotate[str, type],
1550+
Format.FORWARDREF
1551+
)
1552+
1553+
self.assertEqual(annotations, {"x": int})
1554+
1555+
# We will have to manually set the __orig_class__, ensure it is correct.
1556+
self.assertEqual(annotations.__orig_class__, Annotate[str, type])
1557+
15361558
def test_user_annotate_forwardref_value_fallback(self):
15371559
# If Format.FORWARDREF and Format.VALUE_WITH_FAKE_GLOBALS are not supported
15381560
# use Format.VALUE
@@ -1586,6 +1608,48 @@ def annotate(format, /, __Format=Format, __NotImplementedError=NotImplementedErr
15861608

15871609
self.assertEqual(annotations, {"x": "int"})
15881610

1611+
def test_callable_generic_class_annotate_string_fakeglobals(self):
1612+
# If Format.STRING is not supported but Format.VALUE_WITH_FAKE_GLOBALS is
1613+
# prefer that over Format.VALUE
1614+
class Annotate[T]:
1615+
__slots__ = "data",
1616+
1617+
def __init__(self, format, /, __Format=Format,
1618+
__NotImplementedError=NotImplementedError):
1619+
if format == __Format.VALUE:
1620+
self.data = {"x": str}
1621+
elif format == __Format.VALUE_WITH_FAKE_GLOBALS:
1622+
self.data = {"x": int}
1623+
else:
1624+
raise __NotImplementedError(format)
1625+
def __getitem__(self, item):
1626+
return self.data[item]
1627+
def __iter__(self):
1628+
return iter(self.data)
1629+
def __len__(self):
1630+
return len(self.data)
1631+
def __getattr__(self, attr):
1632+
val = getattr(collections.abc.Mapping, attr)
1633+
if isinstance(val, types.FunctionType):
1634+
return types.MethodType(val, self)
1635+
return val
1636+
def __eq__(self, other):
1637+
return dict(self.items()) == dict(other.items())
1638+
1639+
# Subscripting a user-created class will return a typing._GenericAlias.
1640+
# We want to check that types.GenericAlias objects are created properly,
1641+
# so manually create it with the documented constructor.
1642+
annotations = annotationlib.call_annotate_function(
1643+
types.GenericAlias(Annotate, (int,)),
1644+
Format.STRING,
1645+
)
1646+
1647+
self.assertEqual(annotations, {"x": "int"})
1648+
1649+
# A __slots__ class can't have __orig_class__ set unless already specified.
1650+
# Ensure that the error passes silently.
1651+
self.assertNotHasAttr(annotations, "__orig_class__")
1652+
15891653
def test_user_annotate_string_value_fallback(self):
15901654
# If Format.STRING and Format.VALUE_WITH_FAKE_GLOBALS are not
15911655
# supported fall back to Format.VALUE and convert to strings

0 commit comments

Comments
 (0)