Skip to content

Commit 31f8b0e

Browse files
authored
Flesh out docs for hook registration order (#631)
1 parent f78d9e8 commit 31f8b0e

File tree

1 file changed

+46
-30
lines changed

1 file changed

+46
-30
lines changed

docs/customizing.md

+46-30
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
This section describes customizing the unstructuring and structuring processes in _cattrs_.
44

5+
As you go about customizing converters by registering hooks and hook factories,
6+
keep in mind that **the order of hook registration matters**.
7+
8+
Technically speaking, whether the order matters or not depends on the actual implementation of hook factories used.
9+
In practice, the built-in _cattrs_ hooks are optimized to perform early resolution of hooks.
10+
You will likely compose with these hook factories.
11+
12+
This means that **hooks for simpler types should be registered first**.
13+
For example, to override hooks for structuring `int` and `list[int]`, the hook for `int`
14+
must be registered first.
15+
When the {meth}`list_structure_factory() <cattrs.cols.list_structure_factory>`
16+
is applied to the `list[int]` type to produce a hook, it will retrieve and store
17+
the hook for `int`, which should be already present.
18+
519
## Custom (Un-)structuring Hooks
620

721
You can write your own structuring and unstructuring functions and register them for types using {meth}`Converter.register_structure_hook() <cattrs.BaseConverter.register_structure_hook>` and {meth}`Converter.register_unstructure_hook() <cattrs.BaseConverter.register_unstructure_hook>`.
@@ -11,13 +25,13 @@ This approach is the most flexible but also requires the most amount of boilerpl
1125
_singledispatch_ is powerful and fast but comes with some limitations; namely that it performs checks using `issubclass()` which doesn't work with many Python types.
1226
Some examples of this are:
1327

14-
* various generic collections (`list[int]` is not a _subclass_ of `list`)
15-
* literals (`Literal[1]` is not a _subclass_ of `Literal[1]`)
16-
* generics (`MyClass[int]` is not a _subclass_ of `MyClass`)
17-
* protocols, unless they are `runtime_checkable`
18-
* various modifiers, such as `Final` and `NotRequired`
19-
* newtypes and 3.12 type aliases
20-
* `typing.Annotated`
28+
- various generic collections (`list[int]` is not a _subclass_ of `list`)
29+
- literals (`Literal[1]` is not a _subclass_ of `Literal[1]`)
30+
- generics (`MyClass[int]` is not a _subclass_ of `MyClass`)
31+
- protocols, unless they are `runtime_checkable`
32+
- various modifiers, such as `Final` and `NotRequired`
33+
- newtypes and 3.12 type aliases
34+
- `typing.Annotated`
2135

2236
... and many others. In these cases, predicate functions should be used instead.
2337

@@ -49,6 +63,7 @@ def my_datetime_hook(val: datetime) -> str:
4963
The non-decorator approach is still recommended when dealing with lambdas, hooks produced elsewhere, unannotated hooks and situations where type introspection doesn't work.
5064

5165
```{versionadded} 24.1.0
66+
5267
```
5368

5469
### Predicate Hooks
@@ -87,7 +102,7 @@ D(a=2)
87102

88103
### Hook Factories
89104

90-
Hook factories are higher-order predicate hooks: they are functions that *produce* hooks.
105+
Hook factories are higher-order predicate hooks: they are functions that _produce_ hooks.
91106
Hook factories are commonly used to create very optimized hooks by offloading part of the work into a separate, earlier step.
92107

93108
Hook factories are registered using {meth}`Converter.register_unstructure_hook_factory() <cattrs.BaseConverter.register_unstructure_hook_factory>` and {meth}`Converter.register_structure_hook_factory() <cattrs.BaseConverter.register_structure_hook_factory>`.
@@ -251,7 +266,6 @@ This behavior can only be applied to classes or to the default for the {class}`C
251266
The value for the `make_dict_structure_fn._cattrs_forbid_extra_keys` parameter is now taken from the given converter by default.
252267
```
253268

254-
255269
### `rename`
256270

257271
Using the rename override makes `cattrs` use the provided name instead of the real attribute name.
@@ -383,18 +397,22 @@ ClassWithInitFalse(number=2)
383397

384398
## Customizing Collections
385399

400+
```{currentmodule} cattrs.cols
401+
402+
```
403+
386404
The {mod}`cattrs.cols` module contains predicates and hook factories useful for customizing collection handling.
387405
These hook factories can be wrapped to apply complex customizations.
388406

389407
Available predicates are:
390408

391-
* {meth}`is_any_set <cattrs.cols.is_any_set>`
392-
* {meth}`is_frozenset <cattrs.cols.is_frozenset>`
393-
* {meth}`is_set <cattrs.cols.is_set>`
394-
* {meth}`is_sequence <cattrs.cols.is_sequence>`
395-
* {meth}`is_mapping <cattrs.cols.is_mapping>`
396-
* {meth}`is_namedtuple <cattrs.cols.is_namedtuple>`
397-
* {meth}`is_defaultdict <cattrs.cols.is_defaultdict>`
409+
- {meth}`is_any_set`
410+
- {meth}`is_frozenset`
411+
- {meth}`is_set`
412+
- {meth}`is_sequence`
413+
- {meth}`is_mapping`
414+
- {meth}`is_namedtuple`
415+
- {meth}`is_defaultdict`
398416

399417
````{tip}
400418
These predicates aren't _cattrs_-specific and may be useful in other contexts.
@@ -406,18 +424,17 @@ True
406424
```
407425
````
408426

409-
410427
Available hook factories are:
411428

412-
* {meth}`iterable_unstructure_factory <cattrs.cols.iterable_unstructure_factory>`
413-
* {meth}`list_structure_factory <cattrs.cols.list_structure_factory>`
414-
* {meth}`namedtuple_structure_factory <cattrs.cols.namedtuple_structure_factory>`
415-
* {meth}`namedtuple_unstructure_factory <cattrs.cols.namedtuple_unstructure_factory>`
416-
* {meth}`namedtuple_dict_structure_factory <cattrs.cols.namedtuple_dict_structure_factory>`
417-
* {meth}`namedtuple_dict_unstructure_factory <cattrs.cols.namedtuple_dict_unstructure_factory>`
418-
* {meth}`mapping_structure_factory <cattrs.cols.mapping_structure_factory>`
419-
* {meth}`mapping_unstructure_factory <cattrs.cols.mapping_unstructure_factory>`
420-
* {meth}`defaultdict_structure_factory <cattrs.cols.defaultdict_structure_factory>`
429+
- {meth}`iterable_unstructure_factory`
430+
- {meth}`list_structure_factory`
431+
- {meth}`namedtuple_structure_factory`
432+
- {meth}`namedtuple_unstructure_factory`
433+
- {meth}`namedtuple_dict_structure_factory`
434+
- {meth}`namedtuple_dict_unstructure_factory`
435+
- {meth}`mapping_structure_factory`
436+
- {meth}`mapping_unstructure_factory`
437+
- {meth}`defaultdict_structure_factory`
421438

422439
Additional predicates and hook factories will be added as requested.
423440

@@ -460,9 +477,8 @@ ValueError: Not a list!
460477

461478
### Customizing Named Tuples
462479

463-
Named tuples can be un/structured using dictionaries using the {meth}`namedtuple_dict_structure_factory <cattrs.cols.namedtuple_dict_structure_factory>`
464-
and {meth}`namedtuple_dict_unstructure_factory <cattrs.cols.namedtuple_dict_unstructure_factory>`
465-
hook factories.
480+
Named tuples can be un/structured using dictionaries using the {meth}`namedtuple_dict_structure_factory`
481+
and {meth}`namedtuple_dict_unstructure_factory` hook factories.
466482

467483
To unstructure _all_ named tuples into dictionaries:
468484

@@ -497,4 +513,4 @@ change the predicate function when registering the hook factory:
497513

498514
```{versionadded} 24.1.0
499515

500-
```
516+
```

0 commit comments

Comments
 (0)