Skip to content

Commit 1d72dcc

Browse files
authored
Restructure readme/index/reasons (#562)
* Restructure readme/index/reasons * cleanup
1 parent c393abc commit 1d72dcc

File tree

5 files changed

+174
-102
lines changed

5 files changed

+174
-102
lines changed

README.md

Lines changed: 12 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# *cattrs*: Flexible Object Serialization and Validation
22

3-
<p>
4-
<em>Because validation belongs to the edges.</em>
5-
</p>
3+
*Because validation belongs to the edges.*
64

75
[![Documentation](https://img.shields.io/badge/Docs-Read%20The%20Docs-black)](https://catt.rs/)
86
[![License: MIT](https://img.shields.io/badge/license-MIT-C06524)](https://github.com/hynek/stamina/blob/main/LICENSE)
@@ -13,27 +11,19 @@
1311

1412
---
1513

14+
<!-- begin-teaser -->
15+
1616
**cattrs** is a Swiss Army knife for (un)structuring and validating data in Python.
1717
In practice, that means it converts **unstructured dictionaries** into **proper classes** and back, while **validating** their contents.
1818

19-
---
20-
21-
Python has a rich set of powerful, easy to use, built-in **unstructured** data types like dictionaries, lists and tuples.
22-
These data types effortlessly convert into common serialization formats like JSON, MessagePack, CBOR, YAML or TOML.
23-
24-
But the data that is used by your **business logic** should be **structured** into well-defined classes, since not all combinations of field names or values are valid inputs to your programs.
25-
The more trust you can have into the structure of your data, the simpler your code can be, and the fewer edge cases you have to worry about.
19+
<!-- end-teaser -->
2620

27-
When you're handed unstructured data (by your network, file system, database, ...), _cattrs_ helps to convert this data into trustworthy structured data.
28-
When you have to convert your structured data into data types that other libraries can handle, _cattrs_ turns your classes and enumerations into dictionaries, integers and strings.
2921

30-
_attrs_ (and to a certain degree dataclasses) are excellent libraries for declaratively describing the structure of your data, but they're purposefully not serialization libraries.
31-
*cattrs* is there for you the moment your `attrs.asdict(your_instance)` and `YourClass(**data)` start failing you because you need more control over the conversion process.
22+
## Example
3223

24+
<!-- begin-example -->
3325

34-
## Examples
35-
36-
_cattrs_ works best with [_attrs_](https://www.attrs.org/) classes, and [dataclasses](https://docs.python.org/3/library/dataclasses.html) where simple (un-)structuring works out of the box, even for nested data:
26+
_cattrs_ works best with [_attrs_](https://www.attrs.org/) classes, and [dataclasses](https://docs.python.org/3/library/dataclasses.html) where simple (un-)structuring works out of the box, even for nested data, without polluting your data model with serialization details:
3727

3828
```python
3929
>>> from attrs import define
@@ -50,74 +40,12 @@ C(a=1, b=['x', 'y'])
5040

5141
```
5242

53-
> [!IMPORTANT]
54-
> Note how the structuring and unstructuring details do **not** pollute your class, meaning: your data model.
55-
> Any needs to configure the conversion are done within *cattrs* itself, not within your data model.
56-
>
57-
> There are popular validation libraries for Python that couple your data model with its validation and serialization rules based on, for example, web APIs.
58-
> We think that's the wrong approach.
59-
> Validation and serializations are concerns of the edges of your program – not the core.
60-
> They should neither apply design pressure on your business code, nor affect the performance of your code through unnecessary validation.
61-
> In bigger real-world code bases it's also common for data coming from multiple sources that need different validation and serialization rules.
62-
>
63-
> 🎶 You gotta keep 'em separated. 🎶
64-
65-
*cattrs* also works with the usual Python collection types like dictionaries, lists, or tuples when you want to **normalize** unstructured data data into a certain (still unstructured) shape.
66-
For example, to convert a list of a float, an int and a string into a tuple of ints:
67-
68-
```python
69-
>>> import cattrs
70-
71-
>>> cattrs.structure([1.0, 2, "3"], tuple[int, int, int])
72-
(1, 2, 3)
73-
74-
```
75-
76-
Finally, here's a much more complex example, involving _attrs_ classes where _cattrs_ interprets the type annotations to structure and unstructure the data correctly, including Enums and nested data structures:
43+
<!-- end-teaser -->
44+
<!-- end-example -->
7745

78-
```python
79-
>>> from enum import unique, Enum
80-
>>> from typing import Optional, Sequence, Union
81-
>>> from cattrs import structure, unstructure
82-
>>> from attrs import define, field
83-
84-
>>> @unique
85-
... class CatBreed(Enum):
86-
... SIAMESE = "siamese"
87-
... MAINE_COON = "maine_coon"
88-
... SACRED_BIRMAN = "birman"
89-
90-
>>> @define
91-
... class Cat:
92-
... breed: CatBreed
93-
... names: Sequence[str]
94-
95-
>>> @define
96-
... class DogMicrochip:
97-
... chip_id = field() # Type annotations are optional, but recommended
98-
... time_chipped: float = field()
99-
100-
>>> @define
101-
... class Dog:
102-
... cuteness: int
103-
... chip: DogMicrochip | None = None
104-
105-
>>> p = unstructure([Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)),
106-
... Cat(breed=CatBreed.MAINE_COON, names=('Fluffly', 'Fluffer'))])
107-
108-
>>> p
109-
[{'cuteness': 1, 'chip': {'chip_id': 1, 'time_chipped': 10.0}}, {'breed': 'maine_coon', 'names': ['Fluffly', 'Fluffer']}]
110-
>>> structure(p, list[Union[Dog, Cat]])
111-
[Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)), Cat(breed=<CatBreed.MAINE_COON: 'maine_coon'>, names=['Fluffly', 'Fluffer'])]
112-
113-
```
114-
115-
> [!TIP]
116-
> Consider unstructured data a low-level representation that needs to be converted to structured data to be handled, and use `structure()`.
117-
> When you're done, `unstructure()` the data to its unstructured form and pass it along to another library or module.
118-
>
119-
> 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.
46+
Have a look at [*Why *cattrs*?*](https://catt.rs/en/latest/why.html) for more examples!
12047

48+
<!-- begin-why -->
12149

12250
## Features
12351

@@ -175,14 +103,7 @@ _cattrs_ is based on a few fundamental design decisions:
175103
A foolish consistency is the hobgoblin of little minds, so these decisions can and are sometimes broken, but they have proven to be a good foundation.
176104

177105

178-
## Additional documentation and talks
179-
180-
- [On structured and unstructured data, or the case for cattrs](https://threeofwands.com/on-structured-and-unstructured-data-or-the-case-for-cattrs/)
181-
- [Why I use attrs instead of pydantic](https://threeofwands.com/why-i-use-attrs-instead-of-pydantic/)
182-
- [cattrs I: un/structuring speed](https://threeofwands.com/why-cattrs-is-so-fast/)
183-
- [Python has a macro language - it's Python (PyCon IT 2022)](https://www.youtube.com/watch?v=UYRSixikUTo)
184-
- [Intro to cattrs 23.1](https://threeofwands.com/intro-to-cattrs-23-1-0/)
185-
106+
<!-- end-why -->
186107

187108
## Credits
188109

docs/conf.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414
import sys
1515
from importlib.metadata import version as v
1616

17+
# Set canonical URL from the Read the Docs Domain
18+
html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "")
19+
20+
# Tell Jinja2 templates the build is running on Read the Docs
21+
if os.environ.get("READTHEDOCS", "") == "True":
22+
html_context = {"READTHEDOCS": True}
23+
24+
1725
# If extensions (or modules to document with autodoc) are in another
1826
# directory, add these directories to sys.path here. If the directory is
1927
# relative to the documentation root, use os.path.abspath to make it
@@ -44,6 +52,8 @@
4452
"myst_parser",
4553
]
4654

55+
myst_enable_extensions = ["colon_fence", "smartquotes", "deflist"]
56+
4757
# Add any paths that contain templates here, relative to this directory.
4858
templates_path = ["_templates"]
4959

docs/customizing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# Customizing Un/structuring
1+
# Customizing (Un-)structuring
22

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

5-
## Custom Un/structuring Hooks
5+
## Custom (Un-)structuring Hooks
66

77
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>`.
88
This approach is the most flexible but also requires the most amount of boilerplate.

docs/index.md

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
1+
# *cattrs*: Flexible Object Serialization and Validation
2+
3+
*Because validation belongs to the edges.*
4+
5+
---
6+
7+
```{include} ../README.md
8+
:start-after: "begin-teaser -->"
9+
:end-before: "<!-- end-teaser"
10+
```
11+
12+
```{include} ../README.md
13+
:start-after: "begin-example -->"
14+
:end-before: "<!-- end-example"
15+
```
16+
17+
---
18+
19+
However, *cattrs* does **much** more with a focus on **functional composition** and **not coupling** your data model to its serialization and validation rules.
20+
21+
To learn more on why to use *cattrs*, have a look at {doc}`why`, and if you're convinced jump right into {doc}`basics`!
22+
23+
124
```{toctree}
225
---
326
maxdepth: 2
427
hidden: true
528
caption: Introduction
629
---
730
8-
self
31+
why
932
basics
1033
defaulthooks
1134
```
@@ -31,20 +54,33 @@ indepth
3154
---
3255
maxdepth: 2
3356
hidden: true
34-
caption: Dev Guide
57+
caption: Reference
3558
---
3659
37-
history
38-
benchmarking
39-
contributing
60+
API <modules>
4061
modindex
62+
genindex
4163
```
4264

43-
```{include} ../README.md
65+
```{toctree}
66+
---
67+
maxdepth: 2
68+
hidden: true
69+
caption: Dev Guide
70+
---
4471
72+
contributing
73+
benchmarking
4574
```
4675

47-
# Indices and tables
76+
```{toctree}
77+
---
78+
caption: Meta
79+
hidden: true
80+
maxdepth: 1
81+
---
4882
49-
- {ref}`genindex`
50-
- {ref}`modindex`
83+
history
84+
PyPI <https://pypi.org/project/cattrs/>
85+
GitHub <https://github.com/python-attrs/cattrs>
86+
```

docs/why.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Why *cattrs*?
2+
3+
Python has a rich set of powerful, easy to use, built-in **unstructured** data types like dictionaries, lists and tuples.
4+
These data types effortlessly convert into common serialization formats like JSON, MessagePack, CBOR, YAML or TOML.
5+
6+
But the data that is used by your **business logic** should be **structured** into well-defined classes, since not all combinations of field names or values are valid inputs to your programs.
7+
The more trust you can have into the structure of your data, the simpler your code can be, and the fewer edge cases you have to worry about.
8+
9+
When you're handed unstructured data (by your network, file system, database, ...), _cattrs_ helps to convert this data into trustworthy structured data.
10+
When you have to convert your structured data into data types that other libraries can handle, _cattrs_ turns your classes and enumerations into dictionaries, integers and strings.
11+
12+
_attrs_ (and to a certain degree dataclasses) are excellent libraries for declaratively describing the structure of your data, but they're purposefully not serialization libraries.
13+
*cattrs* is there for you the moment your `attrs.asdict(your_instance)` and `YourClass(**data)` start failing you because you need more control over the conversion process.
14+
15+
16+
## Examples
17+
18+
```{include} ../README.md
19+
:start-after: "begin-example -->"
20+
:end-before: "<!-- end-example"
21+
```
22+
23+
:::{important}
24+
Note how the structuring and unstructuring details do **not** pollute your class, meaning: your data model.
25+
Any needs to configure the conversion are done within *cattrs* itself, not within your data model.
26+
27+
There are popular validation libraries for Python that couple your data model with its validation and serialization rules based on, for example, web APIs.
28+
We think that's the wrong approach.
29+
Validation and serializations are concerns of the edges of your program – not the core.
30+
They should neither apply design pressure on your business code, nor affect the performance of your code through unnecessary validation.
31+
In bigger real-world code bases it's also common for data coming from multiple sources that need different validation and serialization rules.
32+
33+
🎶 You gotta keep 'em separated. 🎶
34+
:::
35+
36+
37+
*cattrs* also works with the usual Python collection types like dictionaries, lists, or tuples when you want to **normalize** unstructured data data into a certain (still unstructured) shape.
38+
For example, to convert a list of a float, an int and a string into a tuple of ints:
39+
40+
```python
41+
>>> import cattrs
42+
43+
>>> cattrs.structure([1.0, 2, "3"], tuple[int, int, int])
44+
(1, 2, 3)
45+
46+
```
47+
48+
Finally, here's a much more complex example, involving _attrs_ classes where _cattrs_ interprets the type annotations to structure and unstructure the data correctly, including Enums and nested data structures:
49+
50+
```python
51+
>>> from enum import unique, Enum
52+
>>> from typing import Optional, Sequence, Union
53+
>>> from cattrs import structure, unstructure
54+
>>> from attrs import define, field
55+
56+
>>> @unique
57+
... class CatBreed(Enum):
58+
... SIAMESE = "siamese"
59+
... MAINE_COON = "maine_coon"
60+
... SACRED_BIRMAN = "birman"
61+
62+
>>> @define
63+
... class Cat:
64+
... breed: CatBreed
65+
... names: Sequence[str]
66+
67+
>>> @define
68+
... class DogMicrochip:
69+
... chip_id = field() # Type annotations are optional, but recommended
70+
... time_chipped: float = field()
71+
72+
>>> @define
73+
... class Dog:
74+
... cuteness: int
75+
... chip: DogMicrochip | None = None
76+
77+
>>> p = unstructure([Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)),
78+
... Cat(breed=CatBreed.MAINE_COON, names=('Fluffly', 'Fluffer'))])
79+
80+
>>> p
81+
[{'cuteness': 1, 'chip': {'chip_id': 1, 'time_chipped': 10.0}}, {'breed': 'maine_coon', 'names': ['Fluffly', 'Fluffer']}]
82+
>>> structure(p, list[Union[Dog, Cat]])
83+
[Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)), Cat(breed=<CatBreed.MAINE_COON: 'maine_coon'>, names=['Fluffly', 'Fluffer'])]
84+
85+
```
86+
87+
:::{tip}
88+
Consider unstructured data a low-level representation that needs to be converted to structured data to be handled, and use `structure()`.
89+
When you're done, `unstructure()` the data to its unstructured form and pass it along to another library or module.
90+
:::
91+
92+
93+
```{include} ../README.md
94+
:start-after: "begin-why -->"
95+
:end-before: "<!-- end-why"
96+
```
97+
98+
99+
## Additional Documentation and Talks
100+
101+
- [On structured and unstructured data, or the case for cattrs](https://threeofwands.com/on-structured-and-unstructured-data-or-the-case-for-cattrs/)
102+
- [Why I use attrs instead of pydantic](https://threeofwands.com/why-i-use-attrs-instead-of-pydantic/)
103+
- [cattrs I: un/structuring speed](https://threeofwands.com/why-cattrs-is-so-fast/)
104+
- [Python has a macro language - it's Python (PyCon IT 2022)](https://www.youtube.com/watch?v=UYRSixikUTo)
105+
- [Intro to cattrs 23.1](https://threeofwands.com/intro-to-cattrs-23-1-0/)

0 commit comments

Comments
 (0)