mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
[3.14] gh-137530: generate an __annotate__ function for dataclasses __init__ (GH-137711) (#141352)
(cherry picked from commit 12837c6363)
Co-authored-by: David Ellis <ducksual@gmail.com>
This commit is contained in:
parent
9221030909
commit
727cdcba8e
3 changed files with 219 additions and 15 deletions
|
|
@ -2471,6 +2471,135 @@ def __init__(self, a):
|
|||
self.assertEqual(D(5).a, 10)
|
||||
|
||||
|
||||
class TestInitAnnotate(unittest.TestCase):
|
||||
# Tests for the generated __annotate__ function for __init__
|
||||
# See: https://github.com/python/cpython/issues/137530
|
||||
|
||||
def test_annotate_function(self):
|
||||
# No forward references
|
||||
@dataclass
|
||||
class A:
|
||||
a: int
|
||||
|
||||
value_annos = annotationlib.get_annotations(A.__init__, format=annotationlib.Format.VALUE)
|
||||
forwardref_annos = annotationlib.get_annotations(A.__init__, format=annotationlib.Format.FORWARDREF)
|
||||
string_annos = annotationlib.get_annotations(A.__init__, format=annotationlib.Format.STRING)
|
||||
|
||||
self.assertEqual(value_annos, {'a': int, 'return': None})
|
||||
self.assertEqual(forwardref_annos, {'a': int, 'return': None})
|
||||
self.assertEqual(string_annos, {'a': 'int', 'return': 'None'})
|
||||
|
||||
self.assertTrue(getattr(A.__init__.__annotate__, "__generated_by_dataclasses__"))
|
||||
|
||||
def test_annotate_function_forwardref(self):
|
||||
# With forward references
|
||||
@dataclass
|
||||
class B:
|
||||
b: undefined
|
||||
|
||||
# VALUE annotations should raise while unresolvable
|
||||
with self.assertRaises(NameError):
|
||||
_ = annotationlib.get_annotations(B.__init__, format=annotationlib.Format.VALUE)
|
||||
|
||||
forwardref_annos = annotationlib.get_annotations(B.__init__, format=annotationlib.Format.FORWARDREF)
|
||||
string_annos = annotationlib.get_annotations(B.__init__, format=annotationlib.Format.STRING)
|
||||
|
||||
self.assertEqual(forwardref_annos, {'b': support.EqualToForwardRef('undefined', owner=B, is_class=True), 'return': None})
|
||||
self.assertEqual(string_annos, {'b': 'undefined', 'return': 'None'})
|
||||
|
||||
# Now VALUE and FORWARDREF should resolve, STRING should be unchanged
|
||||
undefined = int
|
||||
|
||||
value_annos = annotationlib.get_annotations(B.__init__, format=annotationlib.Format.VALUE)
|
||||
forwardref_annos = annotationlib.get_annotations(B.__init__, format=annotationlib.Format.FORWARDREF)
|
||||
string_annos = annotationlib.get_annotations(B.__init__, format=annotationlib.Format.STRING)
|
||||
|
||||
self.assertEqual(value_annos, {'b': int, 'return': None})
|
||||
self.assertEqual(forwardref_annos, {'b': int, 'return': None})
|
||||
self.assertEqual(string_annos, {'b': 'undefined', 'return': 'None'})
|
||||
|
||||
def test_annotate_function_init_false(self):
|
||||
# Check `init=False` attributes don't get into the annotations of the __init__ function
|
||||
@dataclass
|
||||
class C:
|
||||
c: str = field(init=False)
|
||||
|
||||
self.assertEqual(annotationlib.get_annotations(C.__init__), {'return': None})
|
||||
|
||||
def test_annotate_function_contains_forwardref(self):
|
||||
# Check string annotations on objects containing a ForwardRef
|
||||
@dataclass
|
||||
class D:
|
||||
d: list[undefined]
|
||||
|
||||
with self.assertRaises(NameError):
|
||||
annotationlib.get_annotations(D.__init__)
|
||||
|
||||
self.assertEqual(
|
||||
annotationlib.get_annotations(D.__init__, format=annotationlib.Format.FORWARDREF),
|
||||
{"d": list[support.EqualToForwardRef("undefined", is_class=True, owner=D)], "return": None}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
annotationlib.get_annotations(D.__init__, format=annotationlib.Format.STRING),
|
||||
{"d": "list[undefined]", "return": "None"}
|
||||
)
|
||||
|
||||
# Now test when it is defined
|
||||
undefined = str
|
||||
|
||||
# VALUE should now resolve
|
||||
self.assertEqual(
|
||||
annotationlib.get_annotations(D.__init__),
|
||||
{"d": list[str], "return": None}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
annotationlib.get_annotations(D.__init__, format=annotationlib.Format.FORWARDREF),
|
||||
{"d": list[str], "return": None}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
annotationlib.get_annotations(D.__init__, format=annotationlib.Format.STRING),
|
||||
{"d": "list[undefined]", "return": "None"}
|
||||
)
|
||||
|
||||
def test_annotate_function_not_replaced(self):
|
||||
# Check that __annotate__ is not replaced on non-generated __init__ functions
|
||||
@dataclass(slots=True)
|
||||
class E:
|
||||
x: str
|
||||
def __init__(self, x: int) -> None:
|
||||
self.x = x
|
||||
|
||||
self.assertEqual(
|
||||
annotationlib.get_annotations(E.__init__), {"x": int, "return": None}
|
||||
)
|
||||
|
||||
self.assertFalse(hasattr(E.__init__.__annotate__, "__generated_by_dataclasses__"))
|
||||
|
||||
def test_init_false_forwardref(self):
|
||||
# Test forward references in fields not required for __init__ annotations.
|
||||
|
||||
# At the moment this raises a NameError for VALUE annotations even though the
|
||||
# undefined annotation is not required for the __init__ annotations.
|
||||
# Ideally this will be fixed but currently there is no good way to resolve this
|
||||
|
||||
@dataclass
|
||||
class F:
|
||||
not_in_init: list[undefined] = field(init=False, default=None)
|
||||
in_init: int
|
||||
|
||||
annos = annotationlib.get_annotations(F.__init__, format=annotationlib.Format.FORWARDREF)
|
||||
self.assertEqual(
|
||||
annos,
|
||||
{"in_init": int, "return": None},
|
||||
)
|
||||
|
||||
with self.assertRaises(NameError):
|
||||
annos = annotationlib.get_annotations(F.__init__) # NameError on not_in_init
|
||||
|
||||
|
||||
class TestRepr(unittest.TestCase):
|
||||
def test_repr(self):
|
||||
@dataclass
|
||||
|
|
@ -3831,7 +3960,15 @@ def method(self) -> int:
|
|||
|
||||
return SlotsTest
|
||||
|
||||
for make in (make_simple, make_with_annotations, make_with_annotations_and_method):
|
||||
def make_with_forwardref():
|
||||
@dataclass(slots=True)
|
||||
class SlotsTest:
|
||||
x: undefined
|
||||
y: list[undefined]
|
||||
|
||||
return SlotsTest
|
||||
|
||||
for make in (make_simple, make_with_annotations, make_with_annotations_and_method, make_with_forwardref):
|
||||
with self.subTest(make=make):
|
||||
C = make()
|
||||
support.gc_collect()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue