Skip to content

Commit

Permalink
Doc rework & Any (#473)
Browse files Browse the repository at this point in the history
* More docs rework

* Tweak docs some more

* Unstructuring `Any`

* Tweak changelog

* Doc improvements

* Tweak cattrs.gen __all__

* Prune docs
  • Loading branch information
Tinche authored Dec 26, 2023
1 parent 8166add commit 9792a43
Show file tree
Hide file tree
Showing 24 changed files with 777 additions and 1,083 deletions.
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

## 24.1.0 (UNRELEASED)

- **Potentially breaking**: Unstructuring hooks for `typing.Any` are consistent now: values are unstructured using their runtime type.
Previously this behavior was underspecified and inconsistent, but followed this rule in the majority of cases.
([#473](https://github.com/python-attrs/cattrs/pull/473))
- Introduce {meth}`BaseConverter.get_structure_hook` and {meth}`BaseConverter.get_unstructure_hook` methods.
([#432](https://github.com/python-attrs/cattrs/issues/432) [#472](https://github.com/python-attrs/cattrs/pull/472))
- The default union handler now properly takes renamed fields into account.
Expand All @@ -26,6 +29,8 @@
- Tests are run with the pytest-xdist plugin by default.
- Rework the introductory parts of the documentation, introducing the Basics section.
([#472](https://github.com/python-attrs/cattrs/pull/472))
- The documentation has been significantly reworked.
([#473](https://github.com/python-attrs/cattrs/pull/473))
- The docs now use the Inter font.

## 23.2.3 (2023-11-30)
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# cattrs

<p>
<em>Great software needs great data structures.</em>
</p>

<a href="https://pypi.python.org/pypi/cattrs"><img src="https://img.shields.io/pypi/v/cattrs.svg"/></a>
<a href="https://github.com/python-attrs/cattrs/actions?workflow=CI"><img src="https://github.com/python-attrs/cattrs/workflows/CI/badge.svg"/></a>
<a href="https://catt.rs/en/latest/?badge=latest"><img src="https://readthedocs.org/projects/cattrs/badge/?version=latest" alt="Documentation Status"/></a>
Expand Down Expand Up @@ -103,7 +107,7 @@ When you're done, `unstructure` the data to its unstructured form and pass it al
Use [attrs type metadata](http://attrs.readthedocs.io/en/stable/examples.html#types) to add type metadata to attributes, so _cattrs_ will know how to structure and destructure them.

- Free software: MIT license
- Documentation: https://catt.rs
- Documentation: [https://catt.rs](https://catt.rs)
- Python versions supported: 3.8 and up. (Older Python versions are supported by older versions; see the changelog.)

## Features
Expand Down
3 changes: 2 additions & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,9 @@ pseudoxml:
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

.PHONY: apidoc
apidoc:
pdm run sphinx-apidoc -o . ../src/cattrs/ -f
pdm run sphinx-apidoc -o . ../src/cattrs/ '../**/converters.py' -f -M

## htmlview to open the index page built by the html target in your browser
.PHONY: htmlview
Expand Down
67 changes: 34 additions & 33 deletions docs/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
```

All _cattrs_ functionality is exposed through a {class}`cattrs.Converter` object.
A global converter is provided for convenience as {data}`cattrs.global_converter` but more complex customizations should be performed on private instances.
A global converter is provided for convenience as {data}`cattrs.global_converter` but more complex customizations should be performed on private instances, any number of which can be made.


## Converters
## Converters and Hooks

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_.
The core functionality of a converter is structuring and unstructuring data by composing [provided](defaulthooks.md) and [custom handling functions](customizing.md), called _hooks_.

To create a private converter, instantiate a {class}`cattrs.Converter`. Converters are relatively cheap; users are encouraged to have as many as they need.

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.
The two main methods, {meth}`structure <cattrs.BaseConverter.structure>` and {meth}`unstructure <cattrs.BaseConverter.unstructure>`, are used to convert between _structured_ and _unstructured_ data.

```python
>>> from cattrs import structure, unstructure
Expand All @@ -28,53 +28,54 @@ The two main methods are {meth}`structure <cattrs.BaseConverter.structure>` and
Model(a=1)
```

_cattrs_ comes with a rich library of un/structuring rules by default, but it excels at composing custom rules with built-in ones.
_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 wrapping an existing hook with your own function.
A base hook can be obtained from a converter and be subjected to the very rich machinery of function composition in Python.
The simplest approach to customization is writing a new hook from scratch.
For example, we can write our own hook for the `int` class.

```python
>>> from cattrs import get_structure_hook
>>> def int_hook(value, type):
... if not isinstance(value, int):
... raise ValueError('not an int!')
... return value
```

>>> base_hook = get_structure_hook(Model)
We can then register this hook to a converter and any other hook converting an `int` will use it.

>>> def my_hook(value, type):
```python
>>> 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.


```python
>>> 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 value.
... # Apply any postprocessing to the model.
... return result
```

This new hook can be used directly or registered to a converter (the original instance, or a different one).

(`cattrs.structure({}, Model)` is shorthand for `cattrs.get_structure_hook(Model)({}, Model)`.)

Another approach is to write a hook from scratch instead of wrapping an existing one.
For example, we can write our own hook for the `int` class.
This new hook can be used directly or registered to a converter (the original instance, or a different one):

```python
>>> def my_int_hook(value, type):
... if not isinstance(value, int):
... raise ValueError('not an int!')
... return value
>>> converter.register_structure_hook(Model, my_model_hook)
```

We can then register this hook to a converter, and any other hook converting an `int` will use it.
Since this is an impactful change, we will switch to using a private converter.

```python
>>> from cattrs import Converter

>>> c = Converter()

>>> c.register_structure_hook(int, my_int_hook)
```

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`.
Now if we use this hook to structure a `Model`, through the ✨magic of function composition✨ that hook will use our old `int_hook`.

```python
>>> base_hook = c.get_structure_hook(Model)
>>> base_hook({"a": "1"}, Model)
>>> converter.structure({"a": "1"}, Model)
+ Exception Group Traceback (most recent call last):
| File "...", line 22, in <module>
| base_hook({"a": "1"}, Model)
Expand All @@ -95,7 +96,7 @@ More advanced structuring customizations are commonly called [](strategies.md).

## Global Converter

Global _cattrs_ functions, such as {meth}`cattrs.unstructure`, use a single {data}`global converter <cattrs.global_converter>`.
Global _cattrs_ functions, such as {meth}`cattrs.structure`, use a single {data}`global converter <cattrs.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:
Expand Down
13 changes: 5 additions & 8 deletions docs/cattrs.gen.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
cattrs.gen package
==================

.. automodule:: cattrs.gen
:members:
:undoc-members:
:show-inheritance:

Submodules
----------

Expand All @@ -11,11 +16,3 @@ cattrs.gen.typeddicts module
:members:
:undoc-members:
:show-inheritance:

Module contents
---------------

.. automodule:: cattrs.gen
:members:
:undoc-members:
:show-inheritance:
13 changes: 5 additions & 8 deletions docs/cattrs.preconf.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
cattrs.preconf package
======================

.. automodule:: cattrs.preconf
:members:
:undoc-members:
:show-inheritance:

Submodules
----------

Expand Down Expand Up @@ -67,11 +72,3 @@ cattrs.preconf.ujson module
:members:
:undoc-members:
:show-inheritance:

Module contents
---------------

.. automodule:: cattrs.preconf
:members:
:undoc-members:
:show-inheritance:
21 changes: 5 additions & 16 deletions docs/cattrs.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
cattrs package
==============

.. automodule:: cattrs
:members:
:undoc-members:
:show-inheritance:

Subpackages
-----------

Expand All @@ -14,14 +19,6 @@ Subpackages
Submodules
----------

cattrs.converters module
------------------------

.. automodule:: cattrs.converters
:members:
:undoc-members:
:show-inheritance:

cattrs.disambiguators module
----------------------------

Expand Down Expand Up @@ -61,11 +58,3 @@ cattrs.v module
:members:
:undoc-members:
:show-inheritance:

Module contents
---------------

.. automodule:: cattrs
:members:
:undoc-members:
:show-inheritance:
3 changes: 0 additions & 3 deletions docs/cattrs.strategies.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
cattrs.strategies package
=========================

Module contents
---------------

.. automodule:: cattrs.strategies
:members:
:undoc-members:
Expand Down
3 changes: 1 addition & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# cattrs documentation build configuration file, created by
# sphinx-quickstart on Tue Jul 9 22:26:36 2013.
#
Expand Down Expand Up @@ -289,6 +287,7 @@
"from typing import *;"
"from enum import Enum, unique"
)
autodoc_member_order = "bysource"
autodoc_typehints = "description"
autosectionlabel_prefix_document = True
copybutton_prompt_text = r">>> |\.\.\. "
Expand Down
5 changes: 2 additions & 3 deletions docs/customizing.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ Hook factories are registered using {meth}`Converter.register_unstructure_hook_f

Here's an example showing how to use hook factories to apply the `forbid_extra_keys` to all attrs classes:

```{doctest}
```python
>>> from attrs import define, has
>>> from cattrs.gen import make_dict_structure_fn

Expand Down Expand Up @@ -135,7 +134,7 @@ So we apply the `omit_if_default` rule to the class, but not to the `dateTime` f
>>> @define
... class TestClass:
... a: Optional[int] = None
... b: dateTime = Factory(datetime.utcnow)
... b: datetime = Factory(datetime.utcnow)

>>> c = cattrs.Converter()
>>> hook = make_dict_unstructure_fn(TestClass, c, _cattrs_omit_if_default=True, b=override(omit_if_default=False))
Expand Down
Loading

0 comments on commit 9792a43

Please sign in to comment.