Skip to content
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
7 changes: 5 additions & 2 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ Our backwards-compatibility policy can be found [here](https://github.com/python
- [`typing.Self`](https://docs.python.org/3/library/typing.html#typing.Self) is now supported in _attrs_ classes, dataclasses, TypedDicts and the dict NamedTuple factories.
See [`typing.Self`](https://catt.rs/en/latest/defaulthooks.html#typing-self) for details.
([#299](https://github.com/python-attrs/cattrs/issues/299) [#627](https://github.com/python-attrs/cattrs/pull/627))
- PEP 695 type aliases can now be used with {meth}`Converter.register_structure_hook` and {meth}`Converter.register_unstructure_hook`.
Previously, they required the use of {meth}`Converter.register_structure_hook_func` (which is still supported).
- PEP 695 type aliases can now be used with {meth}`BaseConverter.register_structure_hook` and {meth}`BaseConverter.register_unstructure_hook`.
Previously, they required the use of {meth}`BaseConverter.register_structure_hook_func` (which is still supported).
([#647](https://github.com/python-attrs/cattrs/pull/647))
- Expose {func}`cattrs.cols.mapping_unstructure_factory` through {mod}`cattrs.cols`.
- Some `defaultdicts` are now [supported by default](https://catt.rs/en/latest/defaulthooks.html#defaultdicts), and
{func}`cattrs.cols.is_defaultdict` and {func}`cattrs.cols.defaultdict_structure_factory` are exposed through {mod}`cattrs.cols`.
([#519](https://github.com/python-attrs/cattrs/issues/519) [#588](https://github.com/python-attrs/cattrs/pull/588))
- Generic PEP 695 type aliases are now supported.
([#611](https://github.com/python-attrs/cattrs/issues/611) [#618](https://github.com/python-attrs/cattrs/pull/618))
- The [tagged union strategy](https://catt.rs/en/stable/strategies.html#tagged-unions-strategy) now also supports type aliases of unions.
([#649](https://github.com/python-attrs/cattrs/pull/649))
- {meth}`Converter.copy` and {meth}`BaseConverter.copy` are correctly annotated as returning `Self`.
([#644](https://github.com/python-attrs/cattrs/pull/644))
- Many preconf converters (_bson_, stdlib JSON, _cbor2_, _msgpack_, _msgspec_, _orjson_, _ujson_) skip unstructuring `int` and `str` enums,
Expand Down
4 changes: 4 additions & 0 deletions docs/strategies.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ This also means union members can be reused in multiple unions easily.
{'a': 1}
```

```{versionchanged} 25.1.0
The strategy can also be called with a type alias of a union.
```

### Real-life Case Study

The Apple App Store supports [server callbacks](https://developer.apple.com/documentation/appstoreservernotifications), by which Apple sends a JSON payload to a URL of your choice.
Expand Down
14 changes: 10 additions & 4 deletions src/cattrs/strategies/_unions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

from attrs import NOTHING, NothingType

from cattrs import BaseConverter
from cattrs._compat import get_newtype_base, is_literal, is_subclass, is_union_type
from .. import BaseConverter
from .._compat import get_newtype_base, is_literal, is_subclass, is_union_type
from ..typealiases import is_type_alias

__all__ = [
"configure_tagged_union",
Expand All @@ -26,8 +27,8 @@ def configure_tagged_union(
default: Union[type, NothingType] = NOTHING,
) -> None:
"""
Configure the converter so that `union` (which should be a union) is
un/structured with the help of an additional piece of data in the
Configure the converter so that `union` (which should be a union, or a type alias
of one) is un/structured with the help of an additional piece of data in the
unstructured payload, the tag.

:param converter: The converter to apply the strategy to.
Expand All @@ -44,7 +45,12 @@ def configure_tagged_union(
un/structuring base strategy.

.. versionadded:: 23.1.0

.. versionchanged:: 25.1
Type aliases of unions are now also supported.
"""
if is_type_alias(union):
union = union.__value__
args = union.__args__
tag_to_hook = {}
exact_cl_unstruct_hooks = {}
Expand Down
21 changes: 21 additions & 0 deletions tests/strategies/test_tagged_unions_695.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pytest

from cattrs import BaseConverter
from cattrs.strategies import configure_tagged_union

from .._compat import is_py312_plus
from .test_tagged_unions import A, B


@pytest.mark.skipif(not is_py312_plus, reason="New type alias syntax")
def test_type_alias(converter: BaseConverter):
"""Type aliases to unions also work."""
type AOrB = A | B

configure_tagged_union(AOrB, converter)

assert converter.unstructure(A(1), AOrB) == {"_type": "A", "a": 1}
assert converter.unstructure(B("1"), AOrB) == {"_type": "B", "a": "1"}

assert converter.structure({"_type": "A", "a": 1}, AOrB) == A(1)
assert converter.structure({"_type": "B", "a": 1}, AOrB) == B("1")