diff --git a/src/cattrs/strategies/_unions.py b/src/cattrs/strategies/_unions.py index 14add983..f75a641a 100644 --- a/src/cattrs/strategies/_unions.py +++ b/src/cattrs/strategies/_unions.py @@ -5,7 +5,7 @@ from .. import BaseConverter from .._compat import get_newtype_base, is_literal, is_subclass, is_union_type -from ..typealiases import is_type_alias +from ..typealiases import get_type_alias_base, is_type_alias __all__ = [ "configure_tagged_union", @@ -174,7 +174,11 @@ def configure_union_passthrough( .. versionchanged:: 25.2.0 Introduced the `accept_ints_as_floats` parameter. """ - args = set(union.__args__) + + def get_passthrough_base(type: Any) -> Any: + return get_type_alias_base(type) if is_type_alias(type) else type + + args = {get_passthrough_base(type) for type in union.__args__} def make_structure_native_union(exact_type: Any) -> Callable: # `exact_type` is likely to be a subset of the entire configured union (`args`). @@ -194,9 +198,10 @@ def make_structure_native_union(exact_type: Any) -> Callable: } non_literal_classes = { - get_newtype_base(t) or t + get_newtype_base(t) or get_passthrough_base(t) for t in exact_type.__args__ - if not is_literal(t) and ((get_newtype_base(t) or t) in args) + if not is_literal(t) + and ((get_newtype_base(t) or get_passthrough_base(t)) in args) } # We augment the set of allowed classes with any configured subclasses of @@ -211,7 +216,8 @@ def make_structure_native_union(exact_type: Any) -> Callable: spillover = { a for a in exact_type.__args__ - if (get_newtype_base(a) or a) not in non_literal_classes + if (get_newtype_base(a) or get_passthrough_base(a)) + not in non_literal_classes and not is_literal(a) } @@ -273,7 +279,9 @@ def contains_native_union(exact_type: Any) -> bool: for lit_arg in t.__args__ } non_literal_types = { - get_newtype_base(t) or t for t in type_args if not is_literal(t) + get_newtype_base(t) or get_passthrough_base(t) + for t in type_args + if not is_literal(t) } return (literal_classes | non_literal_types) & args diff --git a/tests/strategies/test_union_passthrough_695.py b/tests/strategies/test_union_passthrough_695.py new file mode 100644 index 00000000..7e430ed2 --- /dev/null +++ b/tests/strategies/test_union_passthrough_695.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass + +from cattrs import BaseConverter +from cattrs.strategies import configure_union_passthrough + + +@dataclass +class DataClass: + field: str + + +def test_type_alias_union_member(converter: BaseConverter) -> None: + """Native union passthrough handles PEP 695 aliases in the exact union.""" + type NewScalar = str + + configure_union_passthrough(str | int, converter) + + assert converter.structure("value", NewScalar | DataClass) == "value" + assert converter.structure({"field": "value"}, NewScalar | DataClass) == DataClass( + "value" + )