Skip to content

Commit d7cd7dd

Browse files
committed
list_from_dict initial try
1 parent 01e0fb0 commit d7cd7dd

File tree

3 files changed

+64
-0
lines changed

3 files changed

+64
-0
lines changed

src/cattrs/strategies/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""High level strategies for converters."""
22

33
from ._class_methods import use_class_methods
4+
from ._listfromdict import configure_list_from_dict
45
from ._subclasses import include_subclasses
56
from ._unions import configure_tagged_union, configure_union_passthrough
67

78
__all__ = [
9+
"configure_list_from_dict",
810
"configure_tagged_union",
911
"configure_union_passthrough",
1012
"include_subclasses",
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""The list-from-dict implementation."""
2+
3+
from collections.abc import Mapping
4+
from typing import Any, TypeVar, get_args
5+
6+
from .. import BaseConverter, SimpleStructureHook
7+
from ..dispatch import UnstructureHook
8+
9+
T = TypeVar("T")
10+
11+
12+
def configure_list_from_dict(
13+
seq_type: list[T], field: str, converter: BaseConverter
14+
) -> tuple[SimpleStructureHook[Mapping, T], UnstructureHook]:
15+
"""
16+
Configure a list subtype to be structured and unstructured using a dictionary.
17+
18+
List elements have to be an attrs class or a dataclass. One field of the element
19+
type is extracted into a dictionary key; the rest of the data is stored under that
20+
key.
21+
22+
"""
23+
arg_type = get_args(seq_type)[0]
24+
25+
arg_structure_hook = converter.get_structure_hook(arg_type, cache_result=False)
26+
27+
def structure_hook(
28+
value: Mapping, type: Any = seq_type, _arg_type=arg_type
29+
) -> list[T]:
30+
return [arg_structure_hook(v | {field: k}, _arg_type) for k, v in value.items()]
31+
32+
arg_unstructure_hook = converter.get_unstructure_hook(arg_type, cache_result=False)
33+
34+
def unstructure_hook(val: list[T]) -> dict:
35+
return {
36+
(unstructured := arg_unstructure_hook(v)).pop(field): unstructured
37+
for v in val
38+
}
39+
40+
return structure_hook, unstructure_hook
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Tests for the list-from-dict strategy."""
2+
3+
from attrs import define
4+
5+
from cattrs import BaseConverter
6+
from cattrs.strategies import configure_list_from_dict
7+
8+
9+
@define
10+
class A:
11+
a: int
12+
b: str
13+
14+
15+
def test_simple_roundtrip(converter: BaseConverter):
16+
hook, hook2 = configure_list_from_dict(list[A], "a", converter)
17+
18+
structured = [A(1, "2"), A(3, "4")]
19+
unstructured = hook2(structured)
20+
assert unstructured == {1: {"b": "2"}, 3: {"b": "4"}}
21+
22+
assert hook(unstructured) == structured

0 commit comments

Comments
 (0)