Skip to content

Commit 224f4ad

Browse files
committed
gh-142214: Fix two regressions in dataclasses
1 parent 7e5fcae commit 224f4ad

3 files changed

Lines changed: 46 additions & 4 deletions

File tree

Lib/dataclasses.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import re
22
import sys
33
import copy
4+
import trace
45
import types
56
import inspect
67
import keyword
@@ -550,7 +551,10 @@ def __annotate__(format, /):
550551

551552
new_annotations = {}
552553
for k in annotation_fields:
553-
new_annotations[k] = cls_annotations[k]
554+
# gh-142214: The annotation may be missing in unusual dynamic cases.
555+
# If so, just skip it.
556+
if k in cls_annotations:
557+
new_annotations[k] = cls_annotations[k]
554558

555559
if return_type is not MISSING:
556560
if format == Format.STRING:
@@ -1399,9 +1403,11 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
13991403
f.type = ann
14001404

14011405
# Fix the class reference in the __annotate__ method
1402-
init_annotate = newcls.__init__.__annotate__
1403-
if getattr(init_annotate, "__generated_by_dataclasses__", False):
1404-
_update_func_cell_for__class__(init_annotate, cls, newcls)
1406+
init = newcls.__init__
1407+
if hasattr(init, "__annotate__"):
1408+
init_annotate = init.__annotate__
1409+
if getattr(init_annotate, "__generated_by_dataclasses__", False):
1410+
_update_func_cell_for__class__(init_annotate, cls, newcls)
14051411

14061412
return newcls
14071413

Lib/test/test_dataclasses/__init__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,20 @@ class C:
927927

928928
validate_class(C)
929929

930+
def test_incomplete_annotations(self):
931+
# gh-142214
932+
@dataclass
933+
class C:
934+
"doc" # needed because otherwise we fetch the annotations at the wrong time
935+
x: int
936+
937+
C.__annotate__ = lambda _: {}
938+
939+
self.assertEqual(
940+
annotationlib.get_annotations(C.__init__),
941+
{"return": None}
942+
)
943+
930944
def test_missing_default(self):
931945
# Test that MISSING works the same as a default not being
932946
# specified.
@@ -2578,6 +2592,20 @@ def __init__(self, x: int) -> None:
25782592

25792593
self.assertFalse(hasattr(E.__init__.__annotate__, "__generated_by_dataclasses__"))
25802594

2595+
def test_slots_true_init_false(self):
2596+
# Test that slots=True and init=False work together and
2597+
# that __annotate__ is not added to __init__.
2598+
2599+
@dataclass(slots=True, init=False)
2600+
class F:
2601+
x: int
2602+
2603+
f = F()
2604+
f.x = 10
2605+
self.assertEqual(f.x, 10)
2606+
2607+
self.assertFalse(hasattr(F.__init__, "__annotate__"))
2608+
25812609
def test_init_false_forwardref(self):
25822610
# Test forward references in fields not required for __init__ annotations.
25832611

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Fix two regressions in :mod:`dataclasses` related to annotations:
2+
3+
- An exception is no longer raised if ``slots=True`` is used and the
4+
``__init__`` method does not have an ``__annotate__`` attribute (likely
5+
because ``init=False`` was used). - An exception is no longer raised if
6+
annotations are requested on the ``__init__`` method and one of the fields
7+
is not present in the class annotations. This can occur in certain dynamic
8+
scenarios.

0 commit comments

Comments
 (0)