Skip to content

Commit f553389

Browse files
committed
Tweak tests for coverage
1 parent 2270761 commit f553389

4 files changed

Lines changed: 58 additions & 34 deletions

File tree

src/cattrs/converters.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,11 @@ def __init__(
281281
(is_tuple, self._structure_tuple),
282282
(is_namedtuple, namedtuple_structure_factory, "extended"),
283283
(is_mapping, self._structure_dict),
284-
(is_supported_union, self._gen_attrs_union_structure, True),
284+
*(
285+
[(is_supported_union, self._gen_attrs_union_structure, True)]
286+
if unstruct_strat is UnstructureStrategy.AS_DICT
287+
else []
288+
),
285289
(is_optional, self._structure_optional),
286290
(
287291
lambda t: is_union_type(t) and t in self._union_struct_registry,

tests/test_converter.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -267,24 +267,20 @@ def test_nested_roundtrip_tuple(cls_and_vals, omit_if_default: bool):
267267
assert inst == converter.structure(unstructured, cl)
268268

269269

270-
@settings(suppress_health_check=[HealthCheck.filter_too_much, HealthCheck.too_slow])
270+
@settings(suppress_health_check=[HealthCheck.too_slow])
271271
@given(
272-
simple_typed_classes(defaults=False, newtypes=False, allow_nan=False),
273-
simple_typed_classes(defaults=False, newtypes=False, allow_nan=False),
274-
unstructure_strats,
272+
simple_typed_classes(defaults=False, newtypes=False, allow_nan=False, min_attrs=2),
273+
simple_typed_classes(defaults=False, newtypes=False, allow_nan=False, min_attrs=1),
275274
)
276-
def test_union_field_roundtrip(cl_and_vals_a, cl_and_vals_b, strat):
275+
def test_union_field_roundtrip_dict(cl_and_vals_a, cl_and_vals_b):
277276
"""
278277
Classes with union fields can be unstructured and structured.
279278
"""
280-
converter = Converter(unstruct_strat=strat)
279+
converter = Converter()
281280
cl_a, vals_a, kwargs_a = cl_and_vals_a
282281
cl_b, _, _ = cl_and_vals_b
283-
assume(strat is UnstructureStrategy.AS_DICT or not kwargs_a)
284282
a_field_names = {a.name for a in fields(cl_a)}
285283
b_field_names = {a.name for a in fields(cl_b)}
286-
assume(a_field_names)
287-
assume(b_field_names)
288284

289285
common_names = a_field_names & b_field_names
290286
assume(len(a_field_names) > len(common_names))
@@ -295,20 +291,44 @@ class C:
295291

296292
inst = C(a=cl_a(*vals_a, **kwargs_a))
297293

298-
if strat is UnstructureStrategy.AS_DICT:
299-
unstructured = converter.unstructure(inst)
300-
assert inst == converter.structure(converter.unstructure(unstructured), C)
301-
else:
302-
# Our disambiguation functions only support dictionaries for now.
303-
with pytest.raises(ValueError):
304-
converter.structure(converter.unstructure(inst), C)
294+
unstructured = converter.unstructure(inst)
295+
assert inst == converter.structure(converter.unstructure(unstructured), C)
305296

306-
def handler(obj, _):
307-
return converter.structure(obj, cl_a)
308297

309-
converter.register_structure_hook(Union[cl_a, cl_b], handler)
310-
unstructured = converter.unstructure(inst)
311-
assert inst == converter.structure(unstructured, C)
298+
@settings(suppress_health_check=[HealthCheck.too_slow])
299+
@given(
300+
simple_typed_classes(
301+
defaults=False, newtypes=False, allow_nan=False, kw_only=False, min_attrs=2
302+
),
303+
simple_typed_classes(
304+
defaults=False, newtypes=False, allow_nan=False, kw_only=False, min_attrs=1
305+
),
306+
)
307+
def test_union_field_roundtrip_tuple(cl_and_vals_a, cl_and_vals_b):
308+
"""
309+
Classes with union fields can be unstructured and structured.
310+
"""
311+
converter = Converter(unstruct_strat=UnstructureStrategy.AS_TUPLE)
312+
cl_a, vals_a, _ = cl_and_vals_a
313+
cl_b, _, _ = cl_and_vals_b
314+
315+
@define
316+
class C:
317+
a: Union[cl_a, cl_b]
318+
319+
inst = C(a=cl_a(*vals_a))
320+
321+
# Our disambiguation functions only support dictionaries for now.
322+
raw = converter.unstructure(inst)
323+
with pytest.raises(StructureHandlerNotFoundError):
324+
converter.structure(raw, C)
325+
326+
def handler(obj, _):
327+
return converter.structure(obj, cl_a)
328+
329+
converter.register_structure_hook(Union[cl_a, cl_b], handler)
330+
unstructured = converter.unstructure(inst)
331+
assert inst == converter.structure(unstructured, C)
312332

313333

314334
@pytest.mark.skipif(not is_py310_plus, reason="3.10+ union syntax")

tests/test_gen_dict.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ def test_nodefs_generated_unstructuring_cl(
108108

109109
@given(
110110
one_of(just(BaseConverter), just(Converter)),
111-
nested_classes() | simple_classes() | simple_typed_dataclasses(),
111+
nested_classes()
112+
| simple_classes(min_attrs=1)
113+
| simple_typed_dataclasses(min_attrs=1),
112114
)
113115
def test_individual_overrides(converter_cls, cl_and_vals):
114116
"""
@@ -118,7 +120,9 @@ def test_individual_overrides(converter_cls, cl_and_vals):
118120
converter = converter_cls()
119121
cl, vals, kwargs = cl_and_vals
120122

121-
for attr in adapted_fields(cl):
123+
fields = adapted_fields(cl)
124+
125+
for attr in fields:
122126
if attr.default is not NOTHING:
123127
break
124128
else:
@@ -142,7 +146,7 @@ def test_individual_overrides(converter_cls, cl_and_vals):
142146
assert "Hyp" not in repr(res)
143147
assert "Factory" not in repr(res)
144148

145-
for attr, val in zip(adapted_fields(cl), vals):
149+
for attr, val in zip(fields, vals):
146150
if attr.name == chosen_name:
147151
assert attr.name in res
148152
elif attr.default is not NOTHING:

tests/untyped.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import keyword
44
import string
5+
from collections.abc import Iterable
56
from enum import Enum
67
from typing import (
78
Any,
89
Deque,
910
Dict,
10-
Iterable,
1111
List,
1212
Mapping,
1313
MutableMapping,
@@ -25,8 +25,6 @@
2525
from hypothesis.strategies import SearchStrategy, booleans
2626
from typing_extensions import TypeAlias
2727

28-
from . import FeatureFlag
29-
3028
PosArg = Any
3129
PosArgs = tuple[PosArg]
3230
KwArgs = dict[str, Any]
@@ -206,7 +204,7 @@ def key(t):
206204

207205
def just_class(tup):
208206
nested_cl = tup[1][0]
209-
default = attr.Factory(nested_cl)
207+
default = Factory(nested_cl)
210208
combined_attrs = list(tup[0])
211209
combined_attrs.append((attr.ib(default=default), st.just(nested_cl())))
212210
return _create_hyp_class(combined_attrs)
@@ -288,7 +286,7 @@ def make_with_default(takes_self: bool) -> SearchStrategy[AttrsAndArgs]:
288286

289287
def dict_of_class(tup):
290288
nested_cl = tup[1][0]
291-
default = attr.Factory(lambda: {"cls": nested_cl()})
289+
default = Factory(lambda: {"cls": nested_cl()})
292290
combined_attrs = list(tup[0])
293291
combined_attrs.append((attr.ib(default=default), st.just({"cls": nested_cl()})))
294292
return _create_hyp_class(combined_attrs)
@@ -404,7 +402,7 @@ def dict_attrs(draw, defaults=None, kw_only=None):
404402
val_strat = st.dictionaries(keys=st.text(), values=st.integers())
405403
if defaults is True or (defaults is None and draw(st.booleans())):
406404
default_val = draw(val_strat)
407-
default = attr.Factory(lambda: default_val)
405+
default = Factory(lambda: default_val)
408406
return (
409407
attr.ib(
410408
default=default, kw_only=draw(st.booleans()) if kw_only is None else kw_only
@@ -460,9 +458,7 @@ def simple_classes(defaults=None, min_attrs=0, frozen=None, kw_only=None):
460458
)
461459

462460

463-
def nested_classes(
464-
takes_self: FeatureFlag = "sometimes",
465-
) -> SearchStrategy[AttrsAndArgs]:
461+
def nested_classes() -> SearchStrategy[AttrsAndArgs]:
466462
# Ok, so st.recursive works by taking a base strategy (in this case,
467463
# simple_classes) and a special function. This function receives a strategy,
468464
# and returns another strategy (building on top of the base strategy).

0 commit comments

Comments
 (0)