Skip to content

Commit 89f3587

Browse files
committed
gh-158936: Resolve lazy imports in dataclass string annotations like annotationlib
With string annotations, dataclasses looks up the first name of each annotation to detect ClassVar, InitVar and KW_ONLY. If that name was a lazy import that could not be resolved, the lookup raised and the class could not be created, while the same class works with non-string annotations. This resolves lazy imports the way annotationlib does: try to resolve them, and treat a failure like a missing name.
1 parent 182f323 commit 89f3587

5 files changed

Lines changed: 26 additions & 1 deletion

File tree

‎Lib/dataclasses.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,11 @@ def _get_type_from_annotation(annotation, cls):
806806
path = match[1].split(".")
807807
root = sys.modules.get(cls.__module__)
808808
for path_item in path:
809-
root = getattr(root, path_item.strip(), None)
809+
try:
810+
root = getattr(root, path_item.strip(), None)
811+
except Exception:
812+
# Like annotationlib, ignore lazy imports that cannot be resolved.
813+
return None
810814
if root is None:
811815
return None
812816

‎Lib/test/test_dataclasses/__init__.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4525,6 +4525,11 @@ def test_classvar_module_level_import(self):
45254525
# won't exist on the instance.
45264526
self.assertNotIn('not_iv4', c.__dict__)
45274527

4528+
def test_lazy_import_annotations(self):
4529+
from test.test_dataclasses import dataclass_lazy_str as m
4530+
self.assertEqual([f.name for f in fields(m.C)], ['x', 'y'])
4531+
self.assertNotIsInstance(vars(m)['t'], types.LazyImportType)
4532+
45284533
def test_text_annotations(self):
45294534
from test.test_dataclasses import dataclass_textanno
45304535

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
raise RuntimeError("module fails to import")
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from __future__ import annotations
2+
3+
import dataclasses
4+
lazy import typing as t
5+
lazy from _dataclass_test_missing_module import Missing
6+
lazy from test.test_dataclasses.dataclass_lazy_broken import Broken
7+
8+
@dataclasses.dataclass
9+
class C:
10+
x: Missing | None = None
11+
y: Broken | None = None
12+
cv: t.ClassVar[int] = 0
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`~dataclasses.dataclass` no longer fails when a string annotation names a
2+
:ref:`lazy import <lazy-imports>` that cannot be resolved, matching the
3+
behavior for non-string annotations.

0 commit comments

Comments
 (0)