Skip to content

Commit 177f463

Browse files
authored
Improve coverage (#505)
* Improve coverage * Fix type hint * Fix lint
1 parent c241614 commit 177f463

File tree

6 files changed

+62
-11
lines changed

6 files changed

+62
-11
lines changed

src/cattrs/_compat.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,7 @@ def fields(type):
128128
try:
129129
return type.__attrs_attrs__
130130
except AttributeError:
131-
try:
132-
return dataclass_fields(type)
133-
except AttributeError:
134-
raise Exception("Not an attrs or dataclass class.") from None
131+
return dataclass_fields(type)
135132

136133

137134
def fields_dict(type) -> Dict[str, Union[Attribute, Field]]:

src/cattrs/strategies/_unions.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections import defaultdict
2-
from typing import Any, Callable, Dict, Optional, Type, Union
2+
from typing import Any, Callable, Dict, Literal, Type, Union
33

44
from attrs import NOTHING
55

@@ -23,7 +23,7 @@ def configure_tagged_union(
2323
converter: BaseConverter,
2424
tag_generator: Callable[[Type], str] = default_tag_generator,
2525
tag_name: str = "_type",
26-
default: Optional[Type] = NOTHING,
26+
default: Union[Type, Literal[NOTHING]] = NOTHING,
2727
) -> None:
2828
"""
2929
Configure the converter so that `union` (which should be a union) is

tests/strategies/test_include_subclasses.py

+15
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,18 @@ def test_overrides(with_union_strategy: bool, struct_unstruct: str):
328328
assert c.unstructure(structured) == unstructured
329329
assert c.structure(unstructured, Parent) == structured
330330
assert c.structure(unstructured, structured.__class__) == structured
331+
332+
333+
def test_no_parent_classes(genconverter: Converter):
334+
"""Test an edge condition when a union strategy is used.
335+
336+
The class being registered has no subclasses.
337+
"""
338+
339+
@define
340+
class A:
341+
a: int
342+
343+
include_subclasses(A, genconverter, union_strategy=configure_tagged_union)
344+
345+
assert genconverter.structure({"a": 1}, A) == A(1)

tests/strategies/test_tagged_unions.py

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ def test_default_member(converter: BaseConverter) -> None:
9090

9191
# No tag, so should structure as A.
9292
assert converter.structure({"a": 1}, union) == A(1)
93+
# Wrong tag, so should again structure as A.
94+
assert converter.structure({"_type": "C", "a": 1}, union) == A(1)
9395

9496
assert converter.structure({"_type": "A", "a": 1}, union) == A(1)
9597
assert converter.structure({"_type": "B", "a": 1}, union) == B("1")

tests/test_generics.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def test_structure_generics_with_cols(t, result, detailed_validation):
8181
@pytest.mark.parametrize(
8282
("t", "result"), ((int, (1, [2], {"3": 3})), (str, ("1", ["2"], {"3": "3"})))
8383
)
84-
def test_39_structure_generics_with_cols(t, result):
84+
def test_39_structure_generics_with_cols(t, result, genconverter: Converter):
8585
@define
8686
class GenericCols(Generic[T]):
8787
a: T
@@ -90,21 +90,21 @@ class GenericCols(Generic[T]):
9090

9191
expected = GenericCols(*result)
9292

93-
res = Converter().structure(asdict(expected), GenericCols[t])
93+
res = genconverter.structure(asdict(expected), GenericCols[t])
9494

9595
assert res == expected
9696

9797

9898
@pytest.mark.parametrize(("t", "result"), ((int, (1, [1, 2, 3])), (int, (1, None))))
99-
def test_structure_nested_generics_with_cols(t, result):
99+
def test_structure_nested_generics_with_cols(t, result, genconverter: Converter):
100100
@define
101101
class GenericCols(Generic[T]):
102102
a: T
103103
b: Optional[List[T]]
104104

105105
expected = GenericCols(*result)
106106

107-
res = Converter().structure(asdict(expected), GenericCols[t])
107+
res = genconverter.structure(asdict(expected), GenericCols[t])
108108

109109
assert res == expected
110110

@@ -296,6 +296,7 @@ def test_generate_typeddict_mapping() -> None:
296296
from typing import Generic, TypedDict, TypeVar
297297

298298
T = TypeVar("T")
299+
U = TypeVar("U")
299300

300301
class A(TypedDict):
301302
pass
@@ -312,6 +313,12 @@ class B(A[int]):
312313

313314
assert generate_mapping(B, {}) == {T.__name__: int}
314315

316+
class C(Generic[T, U]):
317+
a: T
318+
c: U
319+
320+
assert generate_mapping(C[int, U], {}) == {T.__name__: int}
321+
315322

316323
def test_nongeneric_protocols(converter):
317324
"""Non-generic protocols work."""

tests/test_v.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
Tuple,
1010
)
1111

12-
from attrs import Factory, define
12+
from attrs import Factory, define, field
1313
from pytest import fixture, raises
1414

1515
from cattrs import Converter, transform_error
@@ -103,6 +103,31 @@ class C:
103103
]
104104

105105

106+
def test_untyped_class_errors(c: Converter) -> None:
107+
"""Errors on untyped attrs classes transform correctly."""
108+
109+
@define
110+
class C:
111+
a = field()
112+
113+
def struct_hook(v, __):
114+
if v == 0:
115+
raise ValueError()
116+
raise TypeError("wrong type")
117+
118+
c.register_structure_hook_func(lambda t: t is None, struct_hook)
119+
120+
with raises(Exception) as exc_info:
121+
c.structure({"a": 0}, C)
122+
123+
assert transform_error(exc_info.value) == ["invalid value @ $.a"]
124+
125+
with raises(Exception) as exc_info:
126+
c.structure({"a": 1}, C)
127+
128+
assert transform_error(exc_info.value) == ["invalid type (wrong type) @ $.a"]
129+
130+
106131
def test_sequence_errors(c: Converter) -> None:
107132
try:
108133
c.structure(["str", 1, "str"], List[int])
@@ -315,3 +340,8 @@ class E(TypedDict):
315340
assert transform_error(exc.value) == [
316341
f"invalid value for type, expected {tn} @ $.a"
317342
]
343+
344+
345+
def test_other_errors():
346+
"""Errors without explicit support transform predictably."""
347+
assert format_exception(IndexError("Test"), List[int]) == "unknown error (Test)"

0 commit comments

Comments
 (0)