Skip to content

Commit b3c6ba7

Browse files
authored
Fix docs, update furo (#517)
* Update Black * Fix docs, update furo
1 parent 39e698f commit b3c6ba7

61 files changed

Lines changed: 291 additions & 213 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/recipes.md

Lines changed: 82 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,28 @@ In certain situations, you might want to deviate from this behavior and use alte
1111
For 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:
4241
A 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

6362
Now, 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
8079
In 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

118117
Specifying 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
```

pdm.lock

Lines changed: 28 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ skip-magic-trailing-comma = true
33

44
[tool.pdm.dev-dependencies]
55
lint = [
6-
"black>=23.3.0",
6+
"black>=24.2.0",
77
"ruff>=0.0.277",
88
]
99
test = [
@@ -17,7 +17,7 @@ test = [
1717
]
1818
docs = [
1919
"sphinx>=5.3.0",
20-
"furo>=2023.3.27",
20+
"furo>=2024.1.29",
2121
"sphinx-copybutton>=0.5.2",
2222
"myst-parser>=1.0.0",
2323
"pendulum>=2.1.2",

src/cattr/preconf/bson.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Preconfigured converters for bson."""
2+
23
from cattrs.preconf.bson import BsonConverter, configure_converter, make_converter
34

45
__all__ = ["BsonConverter", "configure_converter", "make_converter"]

src/cattr/preconf/json.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Preconfigured converters for the stdlib json."""
2+
23
from cattrs.preconf.json import JsonConverter, configure_converter, make_converter
34

45
__all__ = ["configure_converter", "JsonConverter", "make_converter"]

src/cattr/preconf/msgpack.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Preconfigured converters for msgpack."""
2+
23
from cattrs.preconf.msgpack import MsgpackConverter, configure_converter, make_converter
34

45
__all__ = ["configure_converter", "make_converter", "MsgpackConverter"]

src/cattr/preconf/orjson.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Preconfigured converters for orjson."""
2+
23
from cattrs.preconf.orjson import OrjsonConverter, configure_converter, make_converter
34

45
__all__ = ["configure_converter", "make_converter", "OrjsonConverter"]

src/cattr/preconf/pyyaml.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Preconfigured converters for pyyaml."""
2+
23
from cattrs.preconf.pyyaml import PyyamlConverter, configure_converter, make_converter
34

45
__all__ = ["configure_converter", "make_converter", "PyyamlConverter"]

src/cattr/preconf/tomlkit.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Preconfigured converters for tomlkit."""
2+
23
from cattrs.preconf.tomlkit import TomlkitConverter, configure_converter, make_converter
34

45
__all__ = ["configure_converter", "make_converter", "TomlkitConverter"]

src/cattr/preconf/ujson.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Preconfigured converters for ujson."""
2+
23
from cattrs.preconf.ujson import UjsonConverter, configure_converter, make_converter
34

45
__all__ = ["configure_converter", "make_converter", "UjsonConverter"]

0 commit comments

Comments
 (0)