Skip to content

Use unique defaulted fields as a fallback disambiguation signal - #830

Closed
Aschenbath wants to merge 1 commit into
python-attrs:mainfrom
Aschenbath:fix/521-disambiguate-defaulted-unique-fields
Closed

Aschenbath wants to merge 1 commit into
python-attrs:mainfrom
Aschenbath:fix/521-disambiguate-defaulted-unique-fields

Conversation

@Aschenbath

Copy link
Copy Markdown

Summary

Fixes #521.

Structuring a union of classes whose fields all have defaults failed while generating the disambiguation function:

@dataclass(kw_only=True)
class A:
    a: str = ""

@dataclass(kw_only=True)
class B:
    b: str = ""

@dataclass(kw_only=True)
class C:
    a_or_b: A | B | None = None

converter.loads(converter.dumps(C(a_or_b=B())), C)
# TypeError: Could not disambiguate between B, A: no unique non-default attributes

create_default_dis_func only pinned classes by unique fields without defaults, since a defaulted field may be absent from the payload. But a defaulted unique field is still a reliable signal whenever it is present, which is exactly the case that matters here: the unstructured form of B() is {"b": ""}, containing the signal.

The pinning loop (extracted unchanged into a helper) now runs a second time with the opposite default criterion, but only when the first pass leaves more than one class unpinned — that is, only in cases that previously raised TypeError. Unions that disambiguate today produce exactly the same disambiguation functions as before. If the second pass still leaves more than one class unpinned, the TypeError is raised as before, and a payload matching no signal raises ValueError instead of silently picking a class.

Verification

  • The scenario from the issue round-trips (C(a_or_b=B()) → JSON → C).
  • New tests in tests/test_disambiguators.py: disambiguation by defaulted unique fields, a chain where a defaulted field becomes unique only after another class is pinned, and a check that identically-shaped classes still raise TypeError.
  • tests/test_disambiguators.py (61 passed), tests/test_unions.py, tests/strategies/ and tests/test_structure.py (292 passed, 15 xfailed).
  • ruff check and ruff format clean on the touched files.

@Aschenbath

Copy link
Copy Markdown
Author

Closing this one myself: I opened too many PRs here in a short time, which isn't fair to your review queue — sorry about that. I'm leaving #827 and #822 open. If this fix is something you'd want, I'm happy to reopen or rebase it later.

@Aschenbath Aschenbath closed this Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dataclass structuring fails with misleading error message.

1 participant