@@ -11,29 +11,28 @@ In certain situations, you might want to deviate from this behavior and use alte
1111For example, consider the following ` Point ` class describing points in 2D space, which offers two ` classmethod ` s for alternative creation:
1212
1313``` {doctest}
14- from __future__ import annotations
15-
16- import math
17-
18- from attrs import define
19-
20-
21- @define
22- class Point:
23- """A point in 2D space."""
24-
25- x: float
26- y: float
27-
28- @classmethod
29- def from_tuple(cls, coordinates: tuple[float, float]) -> Point:
30- """Create a point from a tuple of Cartesian coordinates."""
31- return Point(*coordinates)
32-
33- @classmethod
34- def from_polar(cls, radius: float, angle: float) -> Point:
35- """Create a point from its polar coordinates."""
36- return Point(radius * math.cos(angle), radius * math.sin(angle))
14+ >>> from __future__ import annotations
15+
16+ >>> import math
17+
18+ >>> from attrs import define
19+
20+
21+ >>> @define
22+ ... class Point:
23+ ... """A point in 2D space."""
24+ ... x: float
25+ ... y: float
26+ ...
27+ ... @classmethod
28+ ... def from_tuple(cls, coordinates: tuple[float, float]) -> Point:
29+ ... """Create a point from a tuple of Cartesian coordinates."""
30+ ... return Point(*coordinates)
31+ ...
32+ ... @classmethod
33+ ... def from_polar(cls, radius: float, angle: float) -> Point:
34+ ... """Create a point from its polar coordinates."""
35+ ... return Point(radius * math.cos(angle), radius * math.sin(angle))
3736```
3837
3938
@@ -42,33 +41,33 @@ class Point:
4241A simple way to _ statically_ set one of the ` classmethod ` s as initializer is to register a structuring hook that holds a reference to the respective callable:
4342
4443``` {doctest}
45- from inspect import signature
46- from typing import Callable, TypedDict
47-
48- from cattrs import Converter
49- from cattrs.dispatch import StructureHook
50-
51- def signature_to_typed_dict(fn: Callable) -> type[TypedDict]:
52- """Create a TypedDict reflecting a callable's signature."""
53- params = {p: t.annotation for p, t in signature(fn).parameters.items()}
54- return TypedDict(f"{fn.__name__}_args", params)
55-
56- def make_initializer_from(fn: Callable, conv: Converter) -> StructureHook:
57- """Return a structuring hook from a given callable."""
58- td = signature_to_typed_dict(fn)
59- td_hook = conv.get_structure_hook(td)
60- return lambda v, _: fn(**td_hook(v, td))
44+ >>> from inspect import signature
45+ >>> from typing import Callable, TypedDict
46+
47+ >>> from cattrs import Converter
48+ >>> from cattrs.dispatch import StructureHook
49+
50+ >>> def signature_to_typed_dict(fn: Callable) -> type[TypedDict]:
51+ ... """Create a TypedDict reflecting a callable's signature."""
52+ ... params = {p: t.annotation for p, t in signature(fn).parameters.items()}
53+ ... return TypedDict(f"{fn.__name__}_args", params)
54+
55+ >>> def make_initializer_from(fn: Callable, conv: Converter) -> StructureHook:
56+ ... """Return a structuring hook from a given callable."""
57+ ... td = signature_to_typed_dict(fn)
58+ ... td_hook = conv.get_structure_hook(td)
59+ ... return lambda v, _: fn(**td_hook(v, td))
6160```
6261
6362Now, you can easily structure ` Point ` s from the specified alternative representation:
6463
6564``` {doctest}
66- c = Converter()
67- c.register_structure_hook(Point, make_initializer_from(Point.from_polar, c))
65+ >>> c = Converter()
66+ >>> c.register_structure_hook(Point, make_initializer_from(Point.from_polar, c))
6867
69- p0 = Point(1.0, 0.0)
70- p1 = c.structure({"radius": 1.0, "angle": 0.0}, Point)
71- assert p0 == p1
68+ >>> p0 = Point(1.0, 0.0)
69+ >>> p1 = c.structure({"radius": 1.0, "angle": 0.0}, Point)
70+ >>> assert p0 == p1
7271```
7372
7473
@@ -80,49 +79,49 @@ A typical scenario would be when object structuring happens behind an API and yo
8079In such situations, the following hook factory can help you achieve your goal:
8180
8281``` {doctest}
83- from inspect import signature
84- from typing import Callable, TypedDict
85-
86- from cattrs import Converter
87- from cattrs.dispatch import StructureHook
88-
89- def signature_to_typed_dict(fn: Callable) -> type[TypedDict]:
90- """Create a TypedDict reflecting a callable's signature."""
91- params = {p: t.annotation for p, t in signature(fn).parameters.items()}
92- return TypedDict(f"{fn.__name__}_args", params)
93-
94- def make_initializer_selection_hook(
95- initializer_key: str,
96- converter: Converter,
97- ) -> StructureHook:
98- """Return a structuring hook that dynamically switches between initializers."""
99-
100- def select_initializer_hook(specs: dict, cls: type[T]) -> T:
101- """Deserialization with dynamic initializer selection."""
102-
103- # If no initializer keyword is specified, use regular __init__
104- if initializer_key not in specs:
105- return converter.structure_attrs_fromdict(specs, cls)
106-
107- # Otherwise, call the specified initializer with deserialized arguments
108- specs = specs.copy()
109- initializer_name = specs.pop(initializer_key)
110- initializer = getattr(cls, initializer_name)
111- td = signature_to_typed_dict(initializer)
112- td_hook = converter.get_structure_hook(td)
113- return initializer(**td_hook(specs, td))
114-
115- return select_initializer_hook
82+ >>> from inspect import signature
83+ >>> from typing import Callable, TypedDict
84+
85+ >>> from cattrs import Converter
86+ >>> from cattrs.dispatch import StructureHook
87+
88+ >>> def signature_to_typed_dict(fn: Callable) -> type[TypedDict]:
89+ ... """Create a TypedDict reflecting a callable's signature."""
90+ ... params = {p: t.annotation for p, t in signature(fn).parameters.items()}
91+ ... return TypedDict(f"{fn.__name__}_args", params)
92+
93+ >>> def make_initializer_selection_hook(
94+ ... initializer_key: str,
95+ ... converter: Converter,
96+ ... ) -> StructureHook:
97+ ... """Return a structuring hook that dynamically switches between initializers."""
98+ ...
99+ ... def select_initializer_hook(specs: dict, cls: type[T]) -> T:
100+ ... """Deserialization with dynamic initializer selection."""
101+ ...
102+ ... # If no initializer keyword is specified, use regular __init__
103+ ... if initializer_key not in specs:
104+ ... return converter.structure_attrs_fromdict(specs, cls)
105+ ...
106+ ... # Otherwise, call the specified initializer with deserialized arguments
107+ ... specs = specs.copy()
108+ ... initializer_name = specs.pop(initializer_key)
109+ ... initializer = getattr(cls, initializer_name)
110+ ... td = signature_to_typed_dict(initializer)
111+ ... td_hook = converter.get_structure_hook(td)
112+ ... return initializer(**td_hook(specs, td))
113+ ...
114+ ... return select_initializer_hook
116115```
117116
118117Specifying the key that determines the initializer to be used now lets you dynamically select the ` classmethod ` as part of the object specification itself:
119118
120119``` {doctest}
121- c = Converter()
122- c.register_structure_hook(Point, make_initializer_selection_hook("initializer", c))
120+ >>> c = Converter()
121+ >>> c.register_structure_hook(Point, make_initializer_selection_hook("initializer", c))
123122
124- p0 = Point(1.0, 0.0)
125- p1 = c.structure({"initializer": "from_polar", "radius": 1.0, "angle": 0.0}, Point)
126- p2 = c.structure({"initializer": "from_tuple", "coordinates": (1.0, 0.0)}, Point)
127- assert p0 == p1 == p2
123+ >>> p0 = Point(1.0, 0.0)
124+ >>> p1 = c.structure({"initializer": "from_polar", "radius": 1.0, "angle": 0.0}, Point)
125+ >>> p2 = c.structure({"initializer": "from_tuple", "coordinates": (1.0, 0.0)}, Point)
126+ >>> assert p0 == p1 == p2
128127```
0 commit comments