]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142214: Fix two regressions in dataclasses (#142223)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Fri, 5 Dec 2025 04:04:42 +0000 (20:04 -0800)
committerGitHub <noreply@github.com>
Fri, 5 Dec 2025 04:04:42 +0000 (20:04 -0800)
Lib/dataclasses.py
Lib/test/test_dataclasses/__init__.py
Misc/NEWS.d/next/Library/2025-12-03-06-12-39.gh-issue-142214.appYNZ.rst [new file with mode: 0644]

index 3ccb72469286eb814618fb127489560a0ac60da8..730ced7299865ecc71d30d9552bf9597dd2d41a1 100644 (file)
@@ -550,7 +550,12 @@ def _make_annotate_function(__class__, method_name, annotation_fields, return_ty
 
                 new_annotations = {}
                 for k in annotation_fields:
-                    new_annotations[k] = cls_annotations[k]
+                    # gh-142214: The annotation may be missing in unusual dynamic cases.
+                    # If so, just skip it.
+                    try:
+                        new_annotations[k] = cls_annotations[k]
+                    except KeyError:
+                        pass
 
                 if return_type is not MISSING:
                     if format == Format.STRING:
@@ -1399,9 +1404,10 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
             f.type = ann
 
     # Fix the class reference in the __annotate__ method
-    init_annotate = newcls.__init__.__annotate__
-    if getattr(init_annotate, "__generated_by_dataclasses__", False):
-        _update_func_cell_for__class__(init_annotate, cls, newcls)
+    init = newcls.__init__
+    if init_annotate := getattr(init, "__annotate__", None):
+        if getattr(init_annotate, "__generated_by_dataclasses__", False):
+            _update_func_cell_for__class__(init_annotate, cls, newcls)
 
     return newcls
 
index 513dd78c4381b42762f8d89ff0e0cb26ae4159c9..3b335429b9850062fe54f80339932335dbebbc4a 100644 (file)
@@ -927,6 +927,20 @@ class TestCase(unittest.TestCase):
 
         validate_class(C)
 
+    def test_incomplete_annotations(self):
+        # gh-142214
+        @dataclass
+        class C:
+            "doc"  # needed because otherwise we fetch the annotations at the wrong time
+            x: int
+
+        C.__annotate__ = lambda _: {}
+
+        self.assertEqual(
+            annotationlib.get_annotations(C.__init__),
+            {"return": None}
+        )
+
     def test_missing_default(self):
         # Test that MISSING works the same as a default not being
         #  specified.
@@ -2578,6 +2592,20 @@ class TestInitAnnotate(unittest.TestCase):
 
         self.assertFalse(hasattr(E.__init__.__annotate__, "__generated_by_dataclasses__"))
 
+    def test_slots_true_init_false(self):
+        # Test that slots=True and init=False work together and
+        #  that __annotate__ is not added to __init__.
+
+        @dataclass(slots=True, init=False)
+        class F:
+            x: int
+
+        f = F()
+        f.x = 10
+        self.assertEqual(f.x, 10)
+
+        self.assertFalse(hasattr(F.__init__, "__annotate__"))
+
     def test_init_false_forwardref(self):
         # Test forward references in fields not required for __init__ annotations.
 
diff --git a/Misc/NEWS.d/next/Library/2025-12-03-06-12-39.gh-issue-142214.appYNZ.rst b/Misc/NEWS.d/next/Library/2025-12-03-06-12-39.gh-issue-142214.appYNZ.rst
new file mode 100644 (file)
index 0000000..b87430e
--- /dev/null
@@ -0,0 +1,12 @@
+Fix two regressions in :mod:`dataclasses` in Python 3.14.1 related to
+annotations.
+
+* An exception is no longer raised if ``slots=True`` is used and the
+  ``__init__`` method does not have an ``__annotate__`` attribute
+  (likely because ``init=False`` was used).
+
+* An exception is no longer raised if annotations are requested on the
+  ``__init__`` method and one of the fields is not present in the class
+  annotations. This can occur in certain dynamic scenarios.
+
+Patch by Jelle Zijlstra.