Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-82129: Provide __annotate__ method for dataclasses from make_dataclass #122262

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
16 changes: 14 additions & 2 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1527,10 +1527,11 @@ class C(Base):
seen = set()
annotations = {}
defaults = {}
any_marker = object()
for item in fields:
if isinstance(item, str):
name = item
tp = 'typing.Any'
tp = any_marker
elif len(item) == 2:
name, tp, = item
elif len(item) == 3:
Expand All @@ -1549,11 +1550,22 @@ class C(Base):
seen.add(name)
annotations[name] = tp

def annotate_method(format):
from typing import Any, _convert_to_source
ann_dict = {
ann: Any if t is any_marker else t
for ann, t in annotations.items()
}
if format == 1 or format == 2:
return ann_dict
else:
return _convert_to_source(ann_dict)

# Update 'ns' with the user-supplied namespace plus our calculated values.
def exec_body_callback(ns):
ns['__annotate__'] = annotate_method
ns.update(namespace)
ns.update(defaults)
ns['__annotations__'] = annotations

# We use `types.new_class()` instead of simply `type()` to allow dynamic creation
# of generic dataclasses.
Expand Down
28 changes: 23 additions & 5 deletions Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4159,16 +4159,34 @@ def test_no_types(self):
C = make_dataclass('Point', ['x', 'y', 'z'])
c = C(1, 2, 3)
self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
self.assertEqual(C.__annotations__, {'x': 'typing.Any',
'y': 'typing.Any',
'z': 'typing.Any'})
self.assertEqual(C.__annotations__, {'x': typing.Any,
'y': typing.Any,
'z': typing.Any})

C = make_dataclass('Point', ['x', ('y', int), 'z'])
c = C(1, 2, 3)
self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
self.assertEqual(C.__annotations__, {'x': 'typing.Any',
self.assertEqual(C.__annotations__, {'x': typing.Any,
'y': int,
'z': 'typing.Any'})
'z': typing.Any})

def test_no_types_get_annotations(self):
from annotationlib import Format, get_annotations

C = make_dataclass('C', ['x', ('y', int), 'z'])

self.assertEqual(
get_annotations(C, format=Format.VALUE),
{'x': typing.Any, 'y': int, 'z': typing.Any},
)
self.assertEqual(
get_annotations(C, format=Format.FORWARDREF),
{'x': typing.Any, 'y': int, 'z': typing.Any},
)
self.assertEqual(
get_annotations(C, format=Format.SOURCE),
{'x': 'typing.Any', 'y': 'int', 'z': 'typing.Any'},
)

def test_module_attr(self):
self.assertEqual(ByMakeDataClass.__module__, __name__)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :exc:`NameError` happenning on :func:`dataclasses.dataclass` created by
:func:`dataclasses.make_dataclass` with un-annotated fields when
:func:`typing.get_type_hints` was called on it.
Loading