Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEP 695 work #452

Merged
merged 4 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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