Skip to content

Commit c95a0a5

Browse files
authored
configure_tagged_union: support type aliases (#649)
1 parent 58c7ba6 commit c95a0a5

File tree

4 files changed

+40
-6
lines changed

4 files changed

+40
-6
lines changed

HISTORY.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ Our backwards-compatibility policy can be found [here](https://github.com/python
2222
- [`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.
2323
See [`typing.Self`](https://catt.rs/en/latest/defaulthooks.html#typing-self) for details.
2424
([#299](https://github.com/python-attrs/cattrs/issues/299) [#627](https://github.com/python-attrs/cattrs/pull/627))
25-
- PEP 695 type aliases can now be used with {meth}`Converter.register_structure_hook` and {meth}`Converter.register_unstructure_hook`.
26-
Previously, they required the use of {meth}`Converter.register_structure_hook_func` (which is still supported).
25+
- PEP 695 type aliases can now be used with {meth}`BaseConverter.register_structure_hook` and {meth}`BaseConverter.register_unstructure_hook`.
26+
Previously, they required the use of {meth}`BaseConverter.register_structure_hook_func` (which is still supported).
27+
([#647](https://github.com/python-attrs/cattrs/pull/647))
2728
- Expose {func}`cattrs.cols.mapping_unstructure_factory` through {mod}`cattrs.cols`.
2829
- Some `defaultdicts` are now [supported by default](https://catt.rs/en/latest/defaulthooks.html#defaultdicts), and
2930
{func}`cattrs.cols.is_defaultdict` and {func}`cattrs.cols.defaultdict_structure_factory` are exposed through {mod}`cattrs.cols`.
3031
([#519](https://github.com/python-attrs/cattrs/issues/519) [#588](https://github.com/python-attrs/cattrs/pull/588))
3132
- Generic PEP 695 type aliases are now supported.
3233
([#611](https://github.com/python-attrs/cattrs/issues/611) [#618](https://github.com/python-attrs/cattrs/pull/618))
34+
- The [tagged union strategy](https://catt.rs/en/stable/strategies.html#tagged-unions-strategy) now also supports type aliases of unions.
35+
([#649](https://github.com/python-attrs/cattrs/pull/649))
3336
- {meth}`Converter.copy` and {meth}`BaseConverter.copy` are correctly annotated as returning `Self`.
3437
([#644](https://github.com/python-attrs/cattrs/pull/644))
3538
- Many preconf converters (_bson_, stdlib JSON, _cbor2_, _msgpack_, _msgspec_, _orjson_, _ujson_) skip unstructuring `int` and `str` enums,

docs/strategies.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ This also means union members can be reused in multiple unions easily.
6767
{'a': 1}
6868
```
6969

70+
```{versionchanged} 25.1.0
71+
The strategy can also be called with a type alias of a union.
72+
```
73+
7074
### Real-life Case Study
7175

7276
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.

src/cattrs/strategies/_unions.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
from attrs import NOTHING, NothingType
55

6-
from cattrs import BaseConverter
7-
from cattrs._compat import get_newtype_base, is_literal, is_subclass, is_union_type
6+
from .. import BaseConverter
7+
from .._compat import get_newtype_base, is_literal, is_subclass, is_union_type
8+
from ..typealiases import is_type_alias
89

910
__all__ = [
1011
"configure_tagged_union",
@@ -26,8 +27,8 @@ def configure_tagged_union(
2627
default: Union[type, NothingType] = NOTHING,
2728
) -> None:
2829
"""
29-
Configure the converter so that `union` (which should be a union) is
30-
un/structured with the help of an additional piece of data in the
30+
Configure the converter so that `union` (which should be a union, or a type alias
31+
of one) is un/structured with the help of an additional piece of data in the
3132
unstructured payload, the tag.
3233
3334
:param converter: The converter to apply the strategy to.
@@ -44,7 +45,12 @@ def configure_tagged_union(
4445
un/structuring base strategy.
4546
4647
.. versionadded:: 23.1.0
48+
49+
.. versionchanged:: 25.1
50+
Type aliases of unions are now also supported.
4751
"""
52+
if is_type_alias(union):
53+
union = union.__value__
4854
args = union.__args__
4955
tag_to_hook = {}
5056
exact_cl_unstruct_hooks = {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import pytest
2+
3+
from cattrs import BaseConverter
4+
from cattrs.strategies import configure_tagged_union
5+
6+
from .._compat import is_py312_plus
7+
from .test_tagged_unions import A, B
8+
9+
10+
@pytest.mark.skipif(not is_py312_plus, reason="New type alias syntax")
11+
def test_type_alias(converter: BaseConverter):
12+
"""Type aliases to unions also work."""
13+
type AOrB = A | B
14+
15+
configure_tagged_union(AOrB, converter)
16+
17+
assert converter.unstructure(A(1), AOrB) == {"_type": "A", "a": 1}
18+
assert converter.unstructure(B("1"), AOrB) == {"_type": "B", "a": "1"}
19+
20+
assert converter.structure({"_type": "A", "a": 1}, AOrB) == A(1)
21+
assert converter.structure({"_type": "B", "a": 1}, AOrB) == B("1")

0 commit comments

Comments
 (0)