Skip to content

Commit e6db507

Browse files
committed
configure_tagged_union: support type aliases
1 parent 58c7ba6 commit e6db507

4 files changed

Lines changed: 28 additions & 4 deletions

File tree

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Our backwards-compatibility policy can be found [here](https://github.com/python
3030
([#519](https://github.com/python-attrs/cattrs/issues/519) [#588](https://github.com/python-attrs/cattrs/pull/588))
3131
- Generic PEP 695 type aliases are now supported.
3232
([#611](https://github.com/python-attrs/cattrs/issues/611) [#618](https://github.com/python-attrs/cattrs/pull/618))
33+
- The [tagged union strategy](https://catt.rs/en/stable/strategies.html#tagged-unions-strategy) now also supports type aliases of unions.
3334
- {meth}`Converter.copy` and {meth}`BaseConverter.copy` are correctly annotated as returning `Self`.
3435
([#644](https://github.com/python-attrs/cattrs/pull/644))
3536
- 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 = {}

tests/strategies/test_tagged_unions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,16 @@ class Top:
177177

178178
data = c.unstructure(Top(u=[B(a="")]), Top)
179179
c.structure(data, Top)
180+
181+
182+
def test_type_alias(converter: BaseConverter):
183+
"""Type aliases to unions also work."""
184+
type AOrB = A | B
185+
186+
configure_tagged_union(AOrB, converter)
187+
188+
assert converter.unstructure(A(1), AOrB) == {"_type": "A", "a": 1}
189+
assert converter.unstructure(B("1"), AOrB) == {"_type": "B", "a": "1"}
190+
191+
assert converter.structure({"_type": "A", "a": 1}, AOrB) == A(1)
192+
assert converter.structure({"_type": "B", "a": 1}, AOrB) == B("1")

0 commit comments

Comments
 (0)