-
-
Notifications
You must be signed in to change notification settings - Fork 133
Description
With cattrs version v24.1.2 the following test fails:
def test__cattrs_kw_only_propagation():
import dataclasses
import cattrs
@dataclasses.dataclass
class PartialKeywords:
a1: str = 'Default'
a2: str = dataclasses.field(kw_only=True)
converter = cattrs.Converter(detailed_validation=False)
assert converter.structure(
{
'a2': 'Value',
},
PartialKeywords,
) == PartialKeywords(
a1='Default',
a2='Value',
)What is the test showing?
A dataclass class is defined, with the first attribute possibly positional, and the second attribute with kw_only=True. The first attribute has a default value, so it is optional.
When structuring the passed dict, cattrs is expected to honor the kw_only attribute setting. It does not.
Suggested root cause
The _compat module has functions to support dataclasses by converting some features to the corresponding attrs concepts. The function adapted_fields() does this for class attribute descriptors by creating cattrs Attribute instances from dataclass fields.
The Attribute creation lacks the kw_only argument and therefore uses the default kw_only=False.
"Unfortunately" the defect hits only in one scenario:
- When using
BaseConverter, this does not hit, asstructure_attrs_fromdictalways uses kw-based creation. - When using
Converterwith detailed validation enabled, the generated instantiation script is also always using kw-based creation. - Only when using
Converterwithout detailed validation support, the generated instantion script depends on thekw_onlysetting of an attribute.
The latter distinction is part of Converter.make_dict_structure_n_from_attrs, where you find code like:
if a.kw_only:
invocation_line = f"{a.alias}={invocation_line}"Options to fix
As only the non-detailed-validation case is currently respecting kw_only at all, this could could be removed to even in that case always use kw-based class creation in the instantiation script.
If that code should be kept, adapted_fields can possibly be fixed easily by propagating the kw_only setting:
Attribute(
attr.name,
...,
# FIX:
kw_only=attr.kw_only,
)
for attr in attrs
]This patch fixes the test above.