The Basics#
All cattrs functionality is exposed through a cattrs.Converter
object.
A global converter is provided for convenience as cattrs.global_converter
but more complex customizations should be performed on private instances, any number of which can be made.
Converters and Hooks#
The core functionality of a converter is structuring and unstructuring data by composing provided and custom handling functions, called hooks.
To create a private converter, instantiate a cattrs.Converter
. Converters are relatively cheap; users are encouraged to have as many as they need.
The two main methods, structure
and unstructure
, are used to convert between structured and unstructured data.
>>> from cattrs import structure, unstructure
>>> from attrs import define
>>> @define
... class Model:
... a: int
>>> unstructure(Model(1))
{"a": 1}
>>> structure({"a": 1}, Model)
Model(a=1)
cattrs comes with a rich library of un/structuring hooks by default but it excels at composing custom hooks with built-in ones.
The simplest approach to customization is writing a new hook from scratch.
For example, we can write our own hook for the int
class.
>>> def int_hook(value, type):
... if not isinstance(value, int):
... raise ValueError('not an int!')
... return value
We can then register this hook to a converter and any other hook converting an int
will use it.
>>> from cattrs import Converter
>>> converter = Converter()
>>> converter.register_structure_hook(int, int_hook)
Another approach to customization is wrapping an existing hook with your own function. A base hook can be obtained from a converter and then be subjected to the very rich machinery of function composition that Python offers.
>>> base_hook = converter.get_structure_hook(Model)
>>> def my_model_hook(value, type):
... # Apply any preprocessing to the value.
... result = base_hook(value, type)
... # Apply any postprocessing to the model.
... return result
(cattrs.structure({}, Model)
is equivalent to cattrs.get_structure_hook(Model)({}, Model)
.)
This new hook can be used directly or registered to a converter (the original instance, or a different one):
>>> converter.register_structure_hook(Model, my_model_hook)
Now if we use this hook to structure a Model
, through ✨the magic of function composition✨ that hook will use our old int_hook
.
>>> converter.structure({"a": "1"}, Model)
+ Exception Group Traceback (most recent call last):
| File "...", line 22, in <module>
| base_hook({"a": "1"}, Model)
| File "<cattrs generated structure __main__.Model>", line 9, in structure_Model
| cattrs.errors.ClassValidationError: While structuring Model (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "<cattrs generated structure __main__.Model>", line 5, in structure_Model
| File "...", line 15, in my_int_hook
| raise ValueError("not an int!")
| ValueError: not an int!
| Structuring class Model @ attribute a
+------------------------------------
To continue reading about customizing cattrs, see Customizing Un/structuring. More advanced structuring customizations are commonly called Strategies.
Global Converter#
Global cattrs functions, such as cattrs.structure()
, use a single global converter
.
Changes done to this global converter, such as registering new structure and unstructure hooks, affect all code using the global functions.
The following functions implicitly use this global converter:
Changes made to the global converter will affect the behavior of these functions.
Larger applications are strongly encouraged to create and customize different, private instances of cattrs.Converter
.