Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Our backwards-compatibility policy can be found [here](https://github.com/python

## NEXT (UNRELEASED)

- Fix `Counter` keys not being unstructured with the key type's own hook; the single-type-arg branch passed the whole type-args tuple to the key hook lookup instead of the key type.
([#768](https://github.com/python-attrs/cattrs/pull/768))
- Fix `create_default_dis_func <cattrs.disambiguators.create_default_dis_func>` (aka `create_uniq_field_dis_func`) failing to disambiguate valid unions depending on the order of the member classes; unique fields are now resolved iteratively to a fixpoint.
([#230](https://github.com/python-attrs/cattrs/issues/230) [#765](https://github.com/python-attrs/cattrs/pull/765))
- Support more recursive types on 3.14+ with specialized factories for [`annotationlib.ForwardRef`](https://docs.python.org/3/library/annotationlib.html#annotationlib.ForwardRef).
Expand Down
2 changes: 1 addition & 1 deletion src/cattrs/gen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,7 @@ def mapping_unstructure_factory(
key_arg, val_arg = args
else:
# Probably a Counter
key_arg, val_arg = args, Any
key_arg, val_arg = args[0], Any
# We can do the dispatch here and now.
kh = key_handler or converter.get_unstructure_hook(key_arg, cache_result=False)
if kh == identity:
Expand Down
20 changes: 20 additions & 0 deletions tests/test_unstructure_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,23 @@ def test_collection_unstructure_override_mapping():
assert c.unstructure({1: 2}) == Map({1: 2})
assert c.unstructure({1: 2}, unstructure_as=MutableMapping[int, int]) == Map({1: 2})
assert c.unstructure({1: 2}, unstructure_as=Mapping[int, int]) == Map({1: 2})


def test_counter_unstructure_applies_key_hook():
"""Counter keys must be unstructured with the key type's own hook.

Regression: the single-type-arg ("Probably a Counter") branch assigned the
whole ``args`` tuple to ``key_arg``, so the key hook was resolved for
``(KeyType,)`` (falling back to identity) instead of ``KeyType``, leaving
keys un-unstructured.
"""

@define(frozen=True)
class Key:
v: int

c = Converter()
c.register_unstructure_hook(Key, lambda k: k.v)

result = c.unstructure(Counter({Key(1): 5, Key(2): 3}), unstructure_as=Counter[Key])
assert result == {1: 5, 2: 3}
Loading