Skip to content

Commit

Permalink
Improve disambiguators coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinche committed Dec 2, 2024
1 parent 2eedc81 commit 2d101cd
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 8 deletions.
1 change: 0 additions & 1 deletion src/cattrs/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,6 @@ def _get_dis_func(
# logic.
union_types = tuple(e for e in union_types if e is not NoneType)

# TODO: technically both disambiguators could support TypedDicts too
if not all(has(get_origin(e) or e) for e in union_types):
raise StructureHandlerNotFoundError(
"Only unions of attrs classes and dataclasses supported "
Expand Down
2 changes: 1 addition & 1 deletion src/cattrs/disambiguators.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@


def is_supported_union(typ: Any) -> bool:
"""Whether the type is a union of attrs classes."""
"""Whether the type is a union of attrs classes or dataclasses."""
return is_union_type(typ) and all(
e is NoneType or has(get_origin(e) or e) for e in typ.__args__
)
Expand Down
2 changes: 1 addition & 1 deletion src/cattrs/strategies/_subclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def include_subclasses(
def _include_subclasses_without_union_strategy(
cl,
converter: BaseConverter,
parent_subclass_tree: tuple[type],
parent_subclass_tree: tuple[type, ...],
overrides: dict[str, AttributeOverride] | None,
):
# The iteration approach is required if subclasses are more than one level deep:
Expand Down
31 changes: 26 additions & 5 deletions tests/strategies/test_include_subclasses.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import typing
from copy import deepcopy
from functools import partial
from typing import List, Tuple

import pytest
from attrs import define

from cattrs import Converter, override
from cattrs.errors import ClassValidationError
from cattrs.errors import ClassValidationError, StructureHandlerNotFoundError
from cattrs.strategies import configure_tagged_union, include_subclasses


Expand Down Expand Up @@ -148,7 +147,7 @@ def conv_w_subclasses(request):
"struct_unstruct", IDS_TO_STRUCT_UNSTRUCT.values(), ids=IDS_TO_STRUCT_UNSTRUCT
)
def test_structuring_with_inheritance(
conv_w_subclasses: Tuple[Converter, bool], struct_unstruct
conv_w_subclasses: tuple[Converter, bool], struct_unstruct
) -> None:
structured, unstructured = struct_unstruct

Expand Down Expand Up @@ -219,7 +218,7 @@ def test_circular_reference(conv_w_subclasses):
"struct_unstruct", IDS_TO_STRUCT_UNSTRUCT.values(), ids=IDS_TO_STRUCT_UNSTRUCT
)
def test_unstructuring_with_inheritance(
conv_w_subclasses: Tuple[Converter, bool], struct_unstruct
conv_w_subclasses: tuple[Converter, bool], struct_unstruct
):
structured, unstructured = struct_unstruct
converter, included_subclasses_param = conv_w_subclasses
Expand Down Expand Up @@ -389,5 +388,27 @@ class Derived(A):
"_type": "Derived",
}
],
List[A],
list[A],
) == [Derived(9, Derived(99, A(999)))]


def test_unsupported_class(genconverter: Converter):
"""Non-attrs/dataclass classes raise proper errors."""

class NewParent:
"""Not an attrs class."""

a: int

@define
class NewChild(NewParent):
pass

@define
class NewChild2(NewParent):
pass

genconverter.register_structure_hook(NewParent, lambda v, _: NewParent(v))

with pytest.raises(StructureHandlerNotFoundError):
include_subclasses(NewParent, genconverter)

0 comments on commit 2d101cd

Please sign in to comment.