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.