Skip to content

Commit

Permalink
PEP 695 work (#452)
Browse files Browse the repository at this point in the history
* PEP 695 work

* Fixes

* More type aliases

* Fixes
  • Loading branch information
Tinche authored Nov 20, 2023
1 parent 2ac6c03 commit 65a6405
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 121 deletions.
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

## 24.1.0 (UNRELEASED)

- Add support for [PEP 695](https://peps.python.org/pep-0695/) type aliases.
([#452](https://github.com/python-attrs/cattrs/pull/452))
- More robust support for `Annotated` and `NotRequired` in TypedDicts.
([#450](https://github.com/python-attrs/cattrs/pull/450))
- [PEP 695](https://peps.python.org/pep-0695/) generics are now tested.
([#452](https://github.com/python-attrs/cattrs/pull/452))
- _cattrs_ now uses Ruff for sorting imports.

## 23.2.1 (2023-11-18)

Expand Down
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ clean-test: ## remove test and coverage artifacts

lint: ## check style with ruff and black
pdm run ruff src/ tests
pdm run isort -c src/ tests
pdm run black --check src tests docs/conf.py

test: ## run tests quickly with the default Python
Expand Down
25 changes: 10 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ _cattrs_ works well with _attrs_ classes out of the box.
C(a=1, b='a')
```

Here's a much more complex example, involving `attrs` classes with type
metadata.
Here's a much more complex example, involving _attrs_ classes with type metadata.

```python
>>> from enum import unique, Enum
Expand Down Expand Up @@ -99,12 +98,9 @@ metadata.
[Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)), Cat(breed=<CatBreed.MAINE_COON: 'maine_coon'>, names=['Fluffly', 'Fluffer'])]
```

Consider unstructured data a low-level representation that needs to be converted
to structured data to be handled, and use `structure`. When you're done,
`unstructure` the data to its unstructured form and pass it along to another
library or module. 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.
Consider unstructured data a low-level representation that needs to be converted to structured data to be handled, and use `structure`.
When you're done, `unstructure` the data to its unstructured form and pass it along to another library or module.
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
Expand All @@ -116,28 +112,27 @@ destructure them.

- _attrs_ classes and dataclasses are converted into dictionaries in a way similar to `attrs.asdict`, or into tuples in a way similar to `attrs.astuple`.
- Enumeration instances are converted to their values.
- Other types are let through without conversion. This includes types such as
integers, dictionaries, lists and instances of non-_attrs_ classes.
- Other types are let through without conversion. This includes types such as integers, dictionaries, lists and instances of non-_attrs_ classes.
- Custom converters for any type can be registered using `register_unstructure_hook`.

- Converts unstructured data into structured data, recursively, according to
your specification given as a type. The following types are supported:
- Converts unstructured data into structured data, recursively, according to your specification given as a type.
The following types are supported:

- `typing.Optional[T]`.
- `typing.List[T]`, `typing.MutableSequence[T]`, `typing.Sequence[T]` (converts to a list).
- `typing.Tuple` (both variants, `Tuple[T, ...]` and `Tuple[X, Y, Z]`).
- `typing.MutableSet[T]`, `typing.Set[T]` (converts to a set).
- `typing.FrozenSet[T]` (converts to a frozenset).
- `typing.Dict[K, V]`, `typing.MutableMapping[K, V]`, `typing.Mapping[K, V]` (converts to a dict).
- `typing.TypedDict`.
- `typing.TypedDict`, ordinary and generic.
- _attrs_ classes with simple attributes and the usual `__init__`.

- Simple attributes are attributes that can be assigned unstructured data,
like numbers, strings, and collections of unstructured data.

- All _attrs_ classes and dataclasses with the usual `__init__`, if their complex attributes have type metadata.
- `typing.Union` s of supported _attrs_ classes, given that all of the classes have a unique field.
- `typing.Union` s of anything, given that you provide a disambiguation function for it.
- Unions of supported _attrs_ classes, given that all of the classes have a unique field.
- Unions s of anything, given that you provide a disambiguation function for it.
- Custom converters for any type can be registered using `register_structure_hook`.

_cattrs_ comes with preconfigured converters for a number of serialization libraries, including json, msgpack, cbor2, bson, yaml and toml.
Expand Down
43 changes: 22 additions & 21 deletions docs/structuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,11 +467,11 @@ A(a='string', b=2)
Structuring from tuples can also be made the default for specific classes only;
see registering custom structure hooks below.
## Using Attribute Types and Converters
### Using Attribute Types and Converters
By default, {meth}`structure() <cattrs.BaseConverter.structure>` will use hooks registered using {meth}`register_structure_hook() <cattrs.BaseConverter.register_structure_hook>`,
to convert values to the attribute type, and fallback to invoking any converters registered on
attributes with `attrib`.
attributes with `field`.
```{doctest}
Expand All @@ -494,8 +494,6 @@ but this priority can be inverted by setting `prefer_attrib_converters` to `True
>>> converter = cattrs.Converter(prefer_attrib_converters=True)
>>> converter.register_structure_hook(int, lambda v, t: int(v))
>>> @define
... class A:
... a: int = field(converter=lambda v: int(v) + 5)
Expand All @@ -504,28 +502,22 @@ but this priority can be inverted by setting `prefer_attrib_converters` to `True
A(a=15)
```
### Complex `attrs` Classes and Dataclasses
Complex `attrs` classes and dataclasses are classes with type information
available for some or all attributes. These classes support almost arbitrary
nesting.
### Complex _attrs_ Classes and Dataclasses
Type information is supported by attrs directly, and can be set using type
annotations when using Python 3.6+, or by passing the appropriate type to
`attr.ib`.
Complex _attrs_ classes and dataclasses are classes with type information available for some or all attributes.
These classes support almost arbitrary nesting.
```{doctest}
>>> @define
... class A:
... a: int
>>> attr.fields(A).a
>>> attrs.fields(A).a
Attribute(name='a', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=<class 'int'>, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='a')
```
Type information, when provided, can be used for all attribute types, not only
attributes holding `attrs` classes and dataclasses.
Type information can be used for all attribute types, not only attributes holding _attrs_ classes and dataclasses.
```{doctest}
Expand All @@ -541,13 +533,23 @@ attributes holding `attrs` classes and dataclasses.
B(b=A(a=1))
```
Finally, if an `attrs` or `dataclass` class uses inheritance and as such has one or several subclasses, it can be structured automatically to its exact subtype by using the [include subclasses](strategies.md#include-subclasses-strategy) strategy.
Generic _attrs_ classes and dataclasses are fully supported, both using `typing.Generic` and [PEP 695](https://peps.python.org/pep-0695/).
```python
>>> @define
... class A[T]:
... a: T
>>> cattrs.structure({"a": "1"}, A[int])
A(a=1)
```
Finally, if an _attrs_ or dataclass class uses inheritance and as such has one or several subclasses, it can be structured automatically to its exact subtype by using the [include subclasses](strategies.md#include-subclasses-strategy) strategy.
## Registering Custom Structuring Hooks
_cattrs_ doesn't know how to structure non-_attrs_ classes by default,
so it has to be taught. This can be done by registering structuring hooks on
a converter instance (including the global converter).
_cattrs_ doesn't know how to structure non-_attrs_ classes by default, so it has to be taught.
This can be done by registering structuring hooks on a converter instance (including the global converter).
Here's an example involving a simple, classic (i.e. non-_attrs_) Python class.
Expand All @@ -569,8 +571,7 @@ StructureHandlerNotFoundError: Unsupported type: <class '__main__.C'>. Register
C(a=1)
```
The structuring hooks are callables that take two arguments: the object to
convert to the desired class and the type to convert to.
The structuring hooks are callables that take two arguments: the object to convert to the desired class and the type to convert to.
(The type may seem redundant but is useful when dealing with generic types.)
When using {meth}`cattrs.register_structure_hook`, the hook will be registered on the global converter.
Expand Down
Loading

0 comments on commit 65a6405

Please # to comment.