Skip to content

Commit

Permalink
list_from_dict initial try
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinche committed Dec 8, 2024
1 parent 460003e commit d633703
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/cattrs/strategies/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""High level strategies for converters."""

from ._class_methods import use_class_methods
from ._listfromdict import configure_list_from_dict
from ._subclasses import include_subclasses
from ._unions import configure_tagged_union, configure_union_passthrough

__all__ = [
"configure_list_from_dict",
"configure_tagged_union",
"configure_union_passthrough",
"include_subclasses",
Expand Down
40 changes: 40 additions & 0 deletions src/cattrs/strategies/_listfromdict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""The list-from-dict implementation."""

from collections.abc import Mapping
from typing import Any, TypeVar, get_args

from .. import BaseConverter, SimpleStructureHook
from ..dispatch import UnstructureHook

T = TypeVar("T")


def configure_list_from_dict(
seq_type: list[T], field: str, converter: BaseConverter
) -> tuple[SimpleStructureHook[Mapping, T], UnstructureHook]:
"""
Configure a list subtype to be structured and unstructured using a dictionary.
List elements have to be an attrs class or a dataclass. One field of the element
type is extracted into a dictionary key; the rest of the data is stored under that
key.
"""
arg_type = get_args(seq_type)[0]

arg_structure_hook = converter.get_structure_hook(arg_type, cache_result=False)

def structure_hook(
value: Mapping, type: Any = seq_type, _arg_type=arg_type
) -> list[T]:
return [arg_structure_hook(v | {field: k}, _arg_type) for k, v in value.items()]

arg_unstructure_hook = converter.get_unstructure_hook(arg_type, cache_result=False)

def unstructure_hook(val: list[T]) -> dict:
return {
(unstructured := arg_unstructure_hook(v)).pop(field): unstructured
for v in val
}

return structure_hook, unstructure_hook
22 changes: 22 additions & 0 deletions tests/strategies/test_from_from_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Tests for the list-from-dict strategy."""

from attrs import define

from cattrs import BaseConverter
from cattrs.strategies import configure_list_from_dict


@define
class A:
a: int
b: str


def test_simple_roundtrip(converter: BaseConverter):
hook, hook2 = configure_list_from_dict(list[A], "a", converter)

structured = [A(1, "2"), A(3, "4")]
unstructured = hook2(structured)
assert unstructured == {1: {"b": "2"}, 3: {"b": "4"}}

assert hook(unstructured) == structured

0 comments on commit d633703

Please sign in to comment.