Skip to content

Dataclass kw_only attribute incorrectly structured without detailed validation enabled #637

@moltob

Description

@moltob

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, as structure_attrs_fromdict always uses kw-based creation.
  • When using Converter with detailed validation enabled, the generated instantiation script is also always using kw-based creation.
  • Only when using Converter without detailed validation support, the generated instantion script depends on the kw_only setting 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions