Skip to content

Commit

Permalink
Tweak tagged unions (#443)
Browse files Browse the repository at this point in the history
* Tweak tagged unions

* Tagged unions: Fix to not change input dict

* Old style unions
  • Loading branch information
Tinche committed Nov 14, 2023
1 parent 1352676 commit 4df0c9e
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 9 deletions.
15 changes: 9 additions & 6 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
([#405](https://github.com/python-attrs/cattrs/pull/405))
- The `omit` parameter of {py:func}`cattrs.override` is now of type `bool | None` (from `bool`).
`None` is the new default and means to apply default _cattrs_ handling to the attribute, which is to omit the attribute if it's marked as `init=False`, and keep it otherwise.
- Converters can now be initialized with custom fallback hook factories for un/structuring.
- Converters can now be initialized with [custom fallback hook factories](https://catt.rs/en/latest/converters.html#fallback-hook-factories) for un/structuring.
([#331](https://github.com/python-attrs/cattrs/issues/311) [#441](https://github.com/python-attrs/cattrs/pull/441))
- Add support for `date` to preconfigured converters.
([#420](https://github.com/python-attrs/cattrs/pull/420))
- Add support for `datetime.date`s to the PyYAML preconfigured converter.
([#393](https://github.com/python-attrs/cattrs/issues/393))
- Fix {py:func}`format_exception() <cattrs.v.format_exception>` parameter working for recursive calls to {py:func}`transform_error <cattrs.transform_error>`.
([#389](https://github.com/python-attrs/cattrs/issues/389))
- [_attrs_ aliases](https://www.attrs.org/en/stable/init.html#private-attributes-and-aliases) are now supported, although aliased fields still map to their attribute name instead of their alias by default when un/structuring.
Expand All @@ -44,10 +48,8 @@
([#412](https://github.com/python-attrs/cattrs/issues/412))
- Fix certain cases of structuring `Annotated` types.
([#418](https://github.com/python-attrs/cattrs/issues/418))
- Add support for `date` to preconfigured converters.
([#420](https://github.com/python-attrs/cattrs/pull/420))
- Add support for `datetime.date`s to the PyYAML preconfigured converter.
([#393](https://github.com/python-attrs/cattrs/issues/393))
- Fix the [tagged union strategy](https://catt.rs/en/stable/strategies.html#tagged-unions-strategy) to work with `forbid_extra_keys`.
([#402](https://github.com/python-attrs/cattrs/issues/402) [#443](https://github.com/python-attrs/cattrs/pull/443))
- Use [PDM](https://pdm.fming.dev/latest/) instead of Poetry.
- _cattrs_ is now linted with [Ruff](https://beta.ruff.rs/docs/).
- Remove some unused lines in the unstructuring code.
Expand All @@ -68,7 +70,8 @@

## 23.1.0 (2023-05-30)

- Introduce the `tagged_union` strategy. ([#318](https://github.com/python-attrs/cattrs/pull/318) [#317](https://github.com/python-attrs/cattrs/issues/317))
- Introduce the [`tagged_union` strategy](https://catt.rs/en/stable/strategies.html#tagged-unions-strategy).
([#318](https://github.com/python-attrs/cattrs/pull/318) [#317](https://github.com/python-attrs/cattrs/issues/317))
- Introduce the `cattrs.transform_error` helper function for formatting validation exceptions. ([258](https://github.com/python-attrs/cattrs/issues/258) [342](https://github.com/python-attrs/cattrs/pull/342))
- Add support for [`typing.TypedDict` and `typing_extensions.TypedDict`](https://peps.python.org/pep-0589/).
([#296](https://github.com/python-attrs/cattrs/issues/296) [#364](https://github.com/python-attrs/cattrs/pull/364))
Expand Down
6 changes: 4 additions & 2 deletions src/cattrs/strategies/_unions.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ def unstructure_tagged_union(
def structure_tagged_union(
val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name
) -> union:
return _tag_to_cl[val[_tag_name]](val)
val = val.copy()
return _tag_to_cl[val.pop(_tag_name)](val)

else:

Expand All @@ -101,7 +102,8 @@ def structure_tagged_union(
_default=default,
) -> union:
if _tag_name in val:
return _tag_to_hook[val[_tag_name]](val)
val = val.copy()
return _tag_to_hook[val.pop(_tag_name)](val)
return _dh(val, _default)

converter.register_unstructure_hook(union, unstructure_tagged_union)
Expand Down
38 changes: 37 additions & 1 deletion tests/strategies/test_tagged_unions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from attrs import define

from cattrs import BaseConverter
from cattrs import BaseConverter, Converter
from cattrs.strategies import configure_tagged_union


Expand Down Expand Up @@ -102,3 +102,39 @@ def test_default_member_validation(converter: BaseConverter) -> None:

# A.a should be coerced to an int.
assert converter.structure({"_type": "A", "a": "1"}, union) == A(1)


def test_forbid_extra_keys():
"""The strategy works when converters forbid extra keys."""

@define
class A:
pass

@define
class B:
pass

c = Converter(forbid_extra_keys=True)
configure_tagged_union(Union[A, B], c)

data = c.unstructure(A(), Union[A, B])
c.structure(data, Union[A, B])


def test_forbid_extra_keys_default():
"""The strategy works when converters forbid extra keys."""

@define
class A:
pass

@define
class B:
pass

c = Converter(forbid_extra_keys=True)
configure_tagged_union(Union[A, B], c, default=A)

data = c.unstructure(A(), Union[A, B])
c.structure(data, Union[A, B])

0 comments on commit 4df0c9e

Please sign in to comment.