python-attrs / python-attrs/cattrs
Literal discriminator ignores override(rename=...), so union structuring fails with KeyError
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 159
- Avg merge
- 12h 21m
- Merged PRs (30d)
- 6
Description
When a union's members are discriminated by a Literal field, create_default_dis_func looks that field up in the payload under its original attribute name, ignoring any override(rename=...) registered for it. Structuring the union then fails with a bare KeyError, even though each member structures fine on its own.
The unique-field path of the same function does honour renames (via _usable_attribute_names), so this is an inconsistency between the two disambiguation strategies rather than a general limitation.
Reproduction
from typing import Literal, Union
from attrs import define
from cattrs import Converter
from cattrs.gen import make_dict_structure_fn, override
@define
class A:
kind: Literal["a"]
a_val: int
@define
class B:
kind: Literal["b"]
b_val: int
c = Converter()
for cl in (A, B):
c.register_structure_hook(
cl, make_dict_structure_fn(cl, c, kind=override(rename="type"))
)
c.structure({"type": "a", "a_val": 1}, A) # A(kind='a', a_val=1) -- fine
c.structure({"type": "a", "a_val": 1}, Union[A, B]) # KeyError: 'kind'
Expected
A(kind='a', a_val=1), the same as structuring A directly.
Actual
KeyError: 'kind'
Notes
For contrast, the unique-field strategy handles the equivalent rename correctly — this is what test_field_renaming in tests/test_disambiguators.py already covers:
@define
class C:
c_val: int
@define
class D:
d_val: int
c3 = Converter()
c3.register_structure_hook(C, make_dict_structure_fn(C, c3, c_val=override(rename="cv")))
c3.register_structure_hook(D, make_dict_structure_fn(D, c3, d_val=override(rename="dv")))
c3.structure({"cv": 1}, Union[C, D]) # C(c_val=1) -- correct
In create_default_dis_func, overrides is computed up front but the use_literals branch builds its candidate field names from the raw at.name and looks up data[best_discriminator] with that same raw name; only the later unique-key branch passes overrides through _usable_attribute_names.
Tested on main (5bf7c97). I have a fix and will open a PR shortly.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at create_default_dis_func and read tests/test_disambiguators.py, especially test_field_renaming. Reproduce the Literal-discriminator case with the renamed field, then add coverage showing union structuring matches direct structuring and no longer raises KeyError.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, testing
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100