Literal discriminator ignores override(rename=...), so union structuring fails with KeyError
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 35/100
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.
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.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 159
- Avg merge
- 12h 21m
- Merged PRs (30d)
- 6
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.
More from python-attrs/cattrs
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
python-attrs/cattrs#761 · 2 comments ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
python-attrs/cattrs#513 · 4 comments ·
-
Difficulty 3/5 1-2 days Newbie friendliness 85/100
python-attrs/cattrs#774 ·
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
python-attrs/cattrs#746 · 2 comments ·
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
python-attrs/cattrs#743 · 2 comments ·
All issues in python-attrs/cattrs
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100