Skip to content

Commit 4411382

Browse files
committed
Guess we doin V now
1 parent 54fcf32 commit 4411382

9 files changed

Lines changed: 503 additions & 70 deletions

File tree

README.md

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,21 @@
99

1010
---
1111

12-
**cattrs** is an open source Python library for structuring and unstructuring
13-
data. _cattrs_ works best with _attrs_ classes, dataclasses and the usual
14-
Python collections, but other kinds of classes are supported by manually
15-
registering converters.
16-
17-
Python has a rich set of powerful, easy to use, built-in data types like
18-
dictionaries, lists and tuples. These data types are also the lingua franca
19-
of most data serialization libraries, for formats like json, msgpack, cbor,
20-
yaml or toml.
21-
22-
Data types like this, and mappings like `dict` s in particular, represent
23-
unstructured data. Your data is, in all likelihood, structured: not all
24-
combinations of field names or values are valid inputs to your programs. In
25-
Python, structured data is better represented with classes and enumerations.
26-
_attrs_ is an excellent library for declaratively describing the structure of
27-
your data, and validating it.
28-
29-
When you're handed unstructured data (by your network, file system, database...),
30-
_cattrs_ helps to convert this data into structured data. When you have to
31-
convert your structured data into data types other libraries can handle,
32-
_cattrs_ turns your classes and enumerations into dictionaries, integers and
33-
strings.
34-
35-
Here's a simple taste. The list containing a float, an int and a string
36-
gets converted into a tuple of three ints.
12+
**cattrs** is an open source Python library for structuring and unstructuring data.
13+
_cattrs_ works best with _attrs_ classes, dataclasses and the usual Python collections, but other kinds of classes are supported by manually registering converters.
14+
15+
Python has a rich set of powerful, easy to use, built-in data types like dictionaries, lists and tuples.
16+
These data types are also the lingua franca of most data serialization libraries, for formats like json, msgpack, cbor, yaml or toml.
17+
18+
Data types like this, and mappings like `dict` s in particular, represent unstructured data.
19+
Your data is, in all likelihood, structured: not all combinations of field names or values are valid inputs to your programs.
20+
In Python, structured data is better represented with classes and enumerations.
21+
_attrs_ is an excellent library for declaratively describing the structure of your data and validating it.
22+
23+
When you're handed unstructured data (by your network, file system, database...), _cattrs_ helps to convert this data into structured data.
24+
When you have to convert your structured data into data types other libraries can handle, _cattrs_ turns your classes and enumerations into dictionaries, integers and strings.
25+
26+
Here's a simple taste. The list containing a float, an int and a string gets converted into a tuple of three ints.
3727

3828
```python
3929
>>> import cattrs
@@ -64,7 +54,7 @@ Here's a much more complex example, involving _attrs_ classes with type metadata
6454

6555
```python
6656
>>> from enum import unique, Enum
67-
>>> from typing import Optional, Sequence, Union
57+
>>> from typing import Sequence
6858
>>> from cattrs import structure, unstructure
6959
>>> from attrs import define, field
7060

@@ -87,14 +77,18 @@ Here's a much more complex example, involving _attrs_ classes with type metadata
8777
>>> @define
8878
... class Dog:
8979
... cuteness: int
90-
... chip: Optional[DogMicrochip] = None
80+
... chip: DogMicrochip | None = None
9181

92-
>>> p = unstructure([Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)),
93-
... Cat(breed=CatBreed.MAINE_COON, names=('Fluffly', 'Fluffer'))])
82+
>>> p = unstructure(
83+
... [
84+
... Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)),
85+
... Cat(CatBreed.MAINE_COON, names=('Fluffly', 'Fluffer'))
86+
... ]
87+
... )
9488

9589
>>> print(p)
9690
[{'cuteness': 1, 'chip': {'chip_id': 1, 'time_chipped': 10.0}}, {'breed': 'maine_coon', 'names': ('Fluffly', 'Fluffer')}]
97-
>>> print(structure(p, list[Union[Dog, Cat]]))
91+
>>> print(structure(p, list[Dog | Cat]))
9892
[Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)), Cat(breed=<CatBreed.MAINE_COON: 'maine_coon'>, names=['Fluffly', 'Fluffer'])]
9993
```
10094

@@ -147,6 +141,9 @@ _cattrs_ is based on a few fundamental design decisions.
147141
- Un/structuring rules are separate from the models.
148142
This allows models to have a one-to-many relationship with un/structuring rules, and to create un/structuring rules for models which you do not own and you cannot change.
149143
(_cattrs_ can be configured to use un/structuring rules from models using the [`use_class_methods` strategy](https://catt.rs/en/latest/strategies.html#using-class-specific-structure-and-unstructure-methods).)
144+
- Strongly lean on function composition.
145+
Almost all problems in _cattrs_ can be solved by writing and composing functions (called _hooks_), instead of writing classes and subclassing.
146+
This makes _cattrs_ code elegant, concise, powerful and amenable to all the rich Python ways of working with functions.
150147
- Invent as little as possible; reuse existing ordinary Python instead.
151148
For example, _cattrs_ did not have a custom exception type to group exceptions until the sanctioned Python [`exceptiongroups`](https://docs.python.org/3/library/exceptions.html#ExceptionGroup).
152149
A side-effect of this design decision is that, in a lot of cases, when you're solving _cattrs_ problems you're actually learning Python instead of learning _cattrs_.
Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"""Cattrs validation."""
22
from typing import Callable, List, Union
33

4-
from .errors import (
4+
from ..errors import (
55
ClassValidationError,
66
ForbiddenExtraKeysError,
77
IterableValidationError,
88
)
9+
from ._fluent import V, customize
910

10-
__all__ = ["format_exception", "transform_error"]
11+
__all__ = ["customize", "format_exception", "transform_error", "V"]
1112

1213

1314
def format_exception(exc: BaseException, type: Union[type, None]) -> str:
@@ -27,9 +28,9 @@ def format_exception(exc: BaseException, type: Union[type, None]) -> str:
2728
elif isinstance(exc, ValueError):
2829
if type is not None:
2930
tn = type.__name__ if hasattr(type, "__name__") else repr(type)
30-
res = f"invalid value for type, expected {tn}"
31+
res = f"invalid value for type, expected {tn} ({exc.args[0]})"
3132
else:
32-
res = "invalid value"
33+
res = f"invalid value ({exc.args[0]})"
3334
elif isinstance(exc, TypeError):
3435
if type is None:
3536
if exc.args[0].endswith("object is not iterable"):
@@ -85,7 +86,7 @@ def transform_error(
8586
8687
.. versionadded:: 23.1.0
8788
"""
88-
errors = []
89+
errors: List[str] = []
8990
if isinstance(exc, IterableValidationError):
9091
with_notes, without = exc.group_exceptions()
9192
for exc, note in with_notes:
@@ -102,6 +103,15 @@ def transform_error(
102103
p = f"{path}.{note.name}"
103104
if isinstance(exc, (ClassValidationError, IterableValidationError)):
104105
errors.extend(transform_error(exc, p, format_exception))
106+
elif isinstance(exc, ExceptionGroup):
107+
# A bare ExceptionGroup is now used to group all validator failures.
108+
errors.extend(
109+
[
110+
line
111+
for inner in exc.exceptions
112+
for line in transform_error(inner, p, format_exception)
113+
]
114+
)
105115
else:
106116
errors.append(f"{format_exception(exc, note.type)} @ {p}")
107117
for exc in without:

0 commit comments

Comments
 (0)