diff --git a/src/cattrs/disambiguators.py b/src/cattrs/disambiguators.py index 451d5768..62f5e483 100644 --- a/src/cattrs/disambiguators.py +++ b/src/cattrs/disambiguators.py @@ -14,7 +14,6 @@ from ._compat import ( NoneType, adapted_fields, - fields_dict, get_args, has, is_literal, @@ -150,8 +149,12 @@ def dis_func(data: Mapping[Any, Any]) -> type | None: ) uniq = cl_reqs - other_reqs - # We want a unique attribute with no default. - cl_fields = fields_dict(get_origin(cl) or cl) + # We want a unique attribute with no default. Use adapted_fields so + # dataclass default_factory is treated as a default (via Factory), + # matching attrs Factory and plain dataclass default. + cl_fields = { + a.name: a for a in adapted_fields(get_origin(cl) or cl) + } for maybe_renamed_attr_name in uniq: orig_name = back_map[maybe_renamed_attr_name] if cl_fields[orig_name].default in (NOTHING, MISSING): diff --git a/tests/test_disambiguators.py b/tests/test_disambiguators.py index 66f366d8..e39ae244 100644 --- a/tests/test_disambiguators.py +++ b/tests/test_disambiguators.py @@ -495,3 +495,20 @@ class B: assert converter.structure({"a": "a"}, Union[A, B]) == A() assert converter.structure({"b": "b"}, Union[A, B]) == B("b") + + +def test_dataclass_default_factory_not_required(): + """Dataclass default_factory fields count as defaults for disambiguation.""" + import dataclasses + + @dataclass + class D1: + x: int + tags: list = dataclasses.field(default_factory=list) + + @dataclass + class D2: + x: int + + with pytest.raises(TypeError, match="Could not disambiguate"): + create_default_dis_func(Converter(), D1, D2)