Skip to content

Commit b060863

Browse files
authored
Tin/override-metadata (#472)
* gettable hooks * Rework docs slightly * Make code more consistent * Improve disambiguator * Doc improvements * Fix tests * Doc tweaks * Rename `cache` to `cache_result`
1 parent 0e54e4b commit b060863

23 files changed

+687
-505
lines changed

Diff for: HISTORY.md

+10
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,18 @@
44

55
## 24.1.0 (UNRELEASED)
66

7+
- Introduce {meth}`BaseConverter.get_structure_hook` and {meth}`BaseConverter.get_unstructure_hook` methods.
8+
([#432](https://github.com/python-attrs/cattrs/issues/432) [#472](https://github.com/python-attrs/cattrs/pull/472))
9+
- The default union handler now properly takes renamed fields into account.
10+
([#472](https://github.com/python-attrs/cattrs/pull/472))
711
- Add support for [PEP 695](https://peps.python.org/pep-0695/) type aliases.
812
([#452](https://github.com/python-attrs/cattrs/pull/452))
13+
- The `include_subclasses` strategy now fetches the member hooks from the converter (making use of converter defaults) if overrides are not provided, instead of generating new hooks with no overrides.
14+
([#429](https://github.com/python-attrs/cattrs/issues/429) [#472](https://github.com/python-attrs/cattrs/pull/472))
915
- The {class}`orjson preconf converter <cattrs.preconf.orjson.OrjsonConverter>` now passes through dates and datetimes to orjson while unstructuring, greatly improving speed.
1016
([#463](https://github.com/python-attrs/cattrs/pull/463))
17+
- `cattrs.gen` generators now attach metadata to the generated functions, making them introspectable.
18+
([#472](https://github.com/python-attrs/cattrs/pull/472))
1119
- More robust support for `Annotated` and `NotRequired` in TypedDicts.
1220
([#450](https://github.com/python-attrs/cattrs/pull/450))
1321
- `typing_extensions.Literal` is now automatically structured, just like `typing.Literal`.
@@ -16,6 +24,8 @@
1624
([#452](https://github.com/python-attrs/cattrs/pull/452))
1725
- Imports are now sorted using Ruff.
1826
- Tests are run with the pytest-xdist plugin by default.
27+
- Rework the introductory parts of the documentation, introducing the Basics section.
28+
([#472](https://github.com/python-attrs/cattrs/pull/472))
1929
- The docs now use the Inter font.
2030

2131
## 23.2.3 (2023-11-30)

Diff for: docs/basics.md

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# The Basics
2+
```{currentmodule} cattrs
3+
```
4+
5+
All _cattrs_ functionality is exposed through a {class}`cattrs.Converter` object.
6+
A global converter is provided for convenience as {data}`cattrs.global_converter` but more complex customizations should be performed on private instances.
7+
8+
9+
## Converters
10+
11+
The core functionality of a converter is [structuring](structuring.md) and [unstructuring](unstructuring.md) data by composing provided and [custom handling functions](customizing.md), called _hooks_.
12+
13+
To create a private converter, instantiate a {class}`cattrs.Converter`. Converters are relatively cheap; users are encouraged to have as many as they need.
14+
15+
The two main methods are {meth}`structure <cattrs.BaseConverter.structure>` and {meth}`unstructure <cattrs.BaseConverter.unstructure>`, these are used to convert between _structured_ and _unstructured_ data.
16+
17+
```python
18+
>>> from cattrs import structure, unstructure
19+
>>> from attrs import define
20+
21+
>>> @define
22+
... class Model:
23+
... a: int
24+
25+
>>> unstructure(Model(1))
26+
{"a": 1}
27+
>>> structure({"a": 1}, Model)
28+
Model(a=1)
29+
```
30+
31+
_cattrs_ comes with a rich library of un/structuring rules by default, but it excels at composing custom rules with built-in ones.
32+
33+
The simplest approach to customization is wrapping an existing hook with your own function.
34+
A base hook can be obtained from a converter and be subjected to the very rich mechanisms of Python function composition.
35+
36+
```python
37+
>>> from cattrs import get_structure_hook
38+
39+
>>> base_hook = get_structure_hook(Model)
40+
41+
>>> def my_hook(value, type):
42+
... # Apply any preprocessing to the value.
43+
... result = base_hook(value, type)
44+
... # Apply any postprocessing to the value.
45+
... return result
46+
```
47+
48+
This new hook can be used directly or registered to a converter (the original instance, or a different one).
49+
50+
(`cattrs.structure({}, Model)` is shorthand for `cattrs.get_structure_hook(Model)({}, Model)`.)
51+
52+
Another approach is to write a hook from scratch instead of wrapping an existing one.
53+
For example, we can write our own hook for the `int` class.
54+
55+
```python
56+
>>> def my_int_hook(value, type):
57+
... if not isinstance(value, int):
58+
... raise ValueError('not an int!')
59+
... return value
60+
```
61+
62+
We can then register this hook to a converter, and any other hook converting an `int` will use it.
63+
Since this is an impactful change, we will switch to using a private converter.
64+
65+
```python
66+
>>> from cattrs import Converter
67+
68+
>>> c = Converter()
69+
70+
>>> c.register_structure_hook(int, my_int_hook)
71+
```
72+
73+
Now, if we ask our new converter for a `Model` hook, through the ✨magic of function composition✨ that hook will use our new `my_int_hook`.
74+
75+
```python
76+
>>> base_hook = c.get_structure_hook(Model)
77+
>>> base_hook({"a": "1"}, Model)
78+
+ Exception Group Traceback (most recent call last):
79+
| File "...", line 22, in <module>
80+
| base_hook({"a": "1"}, Model)
81+
| File "<cattrs generated structure __main__.Model>", line 9, in structure_Model
82+
| cattrs.errors.ClassValidationError: While structuring Model (1 sub-exception)
83+
+-+---------------- 1 ----------------
84+
| Traceback (most recent call last):
85+
| File "<cattrs generated structure __main__.Model>", line 5, in structure_Model
86+
| File "...", line 15, in my_int_hook
87+
| raise ValueError("not an int!")
88+
| ValueError: not an int!
89+
| Structuring class Model @ attribute a
90+
+------------------------------------
91+
```
92+
93+
To continue reading about customizing _cattrs_, see [](customizing.md).
94+
More advanced structuring customizations are commonly called [](strategies.md).
95+
96+
## Global Converter
97+
98+
Global _cattrs_ functions, such as {meth}`cattrs.unstructure`, use a single {data}`global converter <cattrs.global_converter>`.
99+
Changes done to this global converter, such as registering new structure and unstructure hooks, affect all code using the global functions.
100+
101+
The following functions implicitly use this global converter:
102+
103+
- {meth}`cattrs.structure`
104+
- {meth}`cattrs.unstructure`
105+
- {meth}`cattrs.get_structure_hook`
106+
- {meth}`cattrs.get_unstructure_hook`
107+
- {meth}`cattrs.structure_attrs_fromtuple`
108+
- {meth}`cattrs.structure_attrs_fromdict`
109+
110+
Changes made to the global converter will affect the behavior of these functions.
111+
112+
Larger applications are strongly encouraged to create and customize different, private instances of {class}`cattrs.Converter`.

Diff for: docs/converters.md

-95
This file was deleted.

0 commit comments

Comments
 (0)