From 224f4ad60edb7087fd31803de8f41f6f07b45fe6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 3 Dec 2025 06:12:44 -0800 Subject: [PATCH 1/6] gh-142214: Fix two regressions in dataclasses --- Lib/dataclasses.py | 14 +++++++--- Lib/test/test_dataclasses/__init__.py | 28 +++++++++++++++++++ ...-12-03-06-12-39.gh-issue-142214.appYNZ.rst | 8 ++++++ 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-03-06-12-39.gh-issue-142214.appYNZ.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 3ccb72469286eb..be0a64048550d1 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1,6 +1,7 @@ import re import sys import copy +import trace import types import inspect import keyword @@ -550,7 +551,10 @@ def __annotate__(format, /): 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. + if k in cls_annotations: + new_annotations[k] = cls_annotations[k] if return_type is not MISSING: if format == Format.STRING: @@ -1399,9 +1403,11 @@ 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 hasattr(init, "__annotate__"): + init_annotate = init.__annotate__ + if getattr(init_annotate, "__generated_by_dataclasses__", False): + _update_func_cell_for__class__(init_annotate, cls, newcls) return newcls diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 513dd78c4381b4..3b335429b98500 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -927,6 +927,20 @@ class C: 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 @@ def __init__(self, x: int) -> None: 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 index 00000000000000..f190b22d0f817d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-03-06-12-39.gh-issue-142214.appYNZ.rst @@ -0,0 +1,8 @@ +Fix two regressions in :mod:`dataclasses` 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. From 5c3d7895529b259a38892d021cb8f476ce672b63 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 3 Dec 2025 06:14:25 -0800 Subject: [PATCH 2/6] format --- .../2025-12-03-06-12-39.gh-issue-142214.appYNZ.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 index f190b22d0f817d..b1ace6b45ca0be 100644 --- 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 @@ -1,8 +1,8 @@ Fix two regressions in :mod:`dataclasses` 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. + ``__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. From 42a68988b99fb38cd7cbaa38cfc381a7eed20bc9 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 3 Dec 2025 06:22:00 -0800 Subject: [PATCH 3/6] fix --- Lib/dataclasses.py | 1 - .../next/Library/2025-12-03-06-12-39.gh-issue-142214.appYNZ.rst | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index be0a64048550d1..d9dfb2651f1e98 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1,7 +1,6 @@ import re import sys import copy -import trace import types import inspect import keyword 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 index b1ace6b45ca0be..ca12ec84ee84f1 100644 --- 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 @@ -6,3 +6,4 @@ Fix two regressions in :mod:`dataclasses` related to annotations: - 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. + From 9a9ef90c813a3d81e062a027e31854fcb6031718 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 3 Dec 2025 06:31:20 -0800 Subject: [PATCH 4/6] fix NEWS syntax --- .../2025-12-03-06-12-39.gh-issue-142214.appYNZ.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 index ca12ec84ee84f1..b87430ec1a3d65 100644 --- 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 @@ -1,9 +1,12 @@ -Fix two regressions in :mod:`dataclasses` related to annotations: +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 +* 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 + +* 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. From 1051d6ca97b98d06092525d9a2b3c2ef272ed881 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 3 Dec 2025 07:55:07 -0800 Subject: [PATCH 5/6] Update Lib/dataclasses.py Co-authored-by: sobolevn --- Lib/dataclasses.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index d9dfb2651f1e98..f3501de2b6aa23 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1403,8 +1403,7 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields): # Fix the class reference in the __annotate__ method init = newcls.__init__ - if hasattr(init, "__annotate__"): - init_annotate = init.__annotate__ + if init_annotate := getattr(init, "__annotate__", None): if getattr(init_annotate, "__generated_by_dataclasses__", False): _update_func_cell_for__class__(init_annotate, cls, newcls) From 320d804f4db9867606fc7149df75158693a2dcfc Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 4 Dec 2025 11:49:27 -0800 Subject: [PATCH 6/6] try except --- Lib/dataclasses.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index f3501de2b6aa23..730ced7299865e 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -552,8 +552,10 @@ def __annotate__(format, /): for k in annotation_fields: # gh-142214: The annotation may be missing in unusual dynamic cases. # If so, just skip it. - if k in cls_annotations: + try: new_annotations[k] = cls_annotations[k] + except KeyError: + pass if return_type is not MISSING: if format == Format.STRING: