Skip to content

Commit 7f2cedc

Browse files
committed
More factories with takes_self
1 parent 5d480f4 commit 7f2cedc

File tree

5 files changed

+92
-51
lines changed

5 files changed

+92
-51
lines changed

tests/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import os
2+
from typing import Literal
23

34
from hypothesis import HealthCheck, settings
45
from hypothesis.strategies import just, one_of
6+
from typing_extensions import TypeAlias
57

68
from cattrs import UnstructureStrategy
79

@@ -13,3 +15,5 @@
1315
settings.load_profile("CI")
1416

1517
unstructure_strats = one_of(just(s) for s in UnstructureStrategy)
18+
19+
FeatureFlag: TypeAlias = Literal["always", "never", "sometimes"]

tests/test_gen_dict.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .untyped import nested_classes, simple_classes
1717

1818

19-
@given(nested_classes | simple_classes())
19+
@given(nested_classes() | simple_classes())
2020
def test_unmodified_generated_unstructuring(cl_and_vals):
2121
converter = BaseConverter()
2222
cl, vals, kwargs = cl_and_vals
@@ -33,7 +33,7 @@ def test_unmodified_generated_unstructuring(cl_and_vals):
3333
assert res_expected == res_actual
3434

3535

36-
@given(nested_classes | simple_classes())
36+
@given(nested_classes() | simple_classes())
3737
def test_nodefs_generated_unstructuring(cl_and_vals):
3838
"""Test omitting default values on a per-attribute basis."""
3939
converter = BaseConverter()
@@ -61,7 +61,9 @@ def test_nodefs_generated_unstructuring(cl_and_vals):
6161
assert attr.name not in res
6262

6363

64-
@given(one_of(just(BaseConverter), just(Converter)), nested_classes | simple_classes())
64+
@given(
65+
one_of(just(BaseConverter), just(Converter)), nested_classes() | simple_classes()
66+
)
6567
def test_nodefs_generated_unstructuring_cl(
6668
converter_cls: Type[BaseConverter], cl_and_vals
6769
):
@@ -105,7 +107,7 @@ def test_nodefs_generated_unstructuring_cl(
105107

106108
@given(
107109
one_of(just(BaseConverter), just(Converter)),
108-
nested_classes | simple_classes() | simple_typed_dataclasses(),
110+
nested_classes() | simple_classes() | simple_typed_dataclasses(),
109111
)
110112
def test_individual_overrides(converter_cls, cl_and_vals):
111113
"""

tests/test_unstructure.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Tests for dumping."""
22

3-
from attr import asdict, astuple
3+
from attrs import asdict, astuple
44
from hypothesis import given
55
from hypothesis.strategies import data, just, lists, one_of, sampled_from
66

@@ -69,15 +69,15 @@ def test_enum_unstructure(enum, dump_strat, data):
6969
assert converter.unstructure(member) == member.value
7070

7171

72-
@given(nested_classes)
72+
@given(nested_classes())
7373
def test_attrs_asdict_unstructure(nested_class):
7474
"""Our dumping should be identical to `attrs`."""
7575
converter = BaseConverter()
7676
instance = nested_class[0]()
7777
assert converter.unstructure(instance) == asdict(instance)
7878

7979

80-
@given(nested_classes)
80+
@given(nested_classes())
8181
def test_attrs_astuple_unstructure(nested_class):
8282
"""Our dumping should be identical to `attrs`."""
8383
converter = BaseConverter(unstruct_strat=UnstructureStrategy.AS_TUPLE)

tests/typed.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Strategies for attributes with types and classes using them."""
22

3-
from collections import OrderedDict
43
from collections.abc import MutableSequence as AbcMutableSequence
54
from collections.abc import MutableSet as AbcMutableSet
65
from collections.abc import Sequence as AbcSequence
@@ -293,7 +292,7 @@ def key(t):
293292
attr_name = attr_name[1:]
294293
kwarg_strats[attr_name] = attr_and_strat[1]
295294
return tuples(
296-
just(make_class("HypClass", OrderedDict(zip(gen_attr_names(), attrs)))),
295+
just(make_class("HypClass", dict(zip(gen_attr_names(), attrs)))),
297296
just(tuples(*vals)),
298297
just(fixed_dictionaries(kwarg_strats)),
299298
)
@@ -860,7 +859,12 @@ def nested_typed_classes_and_strat(
860859

861860
@composite
862861
def nested_typed_classes(
863-
draw, defaults=None, min_attrs=0, kw_only=None, newtypes=True, allow_nan=True
862+
draw: DrawFn,
863+
defaults=None,
864+
min_attrs=0,
865+
kw_only=None,
866+
newtypes=True,
867+
allow_nan=True,
864868
):
865869
cl, strat, kwarg_strat = draw(
866870
nested_typed_classes_and_strat(

tests/untyped.py

+72-41
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import keyword
44
import string
5-
from collections import OrderedDict
65
from enum import Enum
76
from typing import (
87
Any,
@@ -23,11 +22,15 @@
2322
from attr._make import _CountingAttr
2423
from attrs import NOTHING, AttrsInstance, Factory, make_class
2524
from hypothesis import strategies as st
26-
from hypothesis.strategies import SearchStrategy
25+
from hypothesis.strategies import SearchStrategy, booleans
26+
from typing_extensions import TypeAlias
27+
28+
from . import FeatureFlag
2729

2830
PosArg = Any
2931
PosArgs = tuple[PosArg]
3032
KwArgs = dict[str, Any]
33+
AttrsAndArgs: TypeAlias = tuple[type[AttrsInstance], PosArgs, KwArgs]
3134

3235
primitive_strategies = st.sampled_from(
3336
[
@@ -167,7 +170,7 @@ def gen_attr_names() -> Iterable[str]:
167170
def _create_hyp_class(
168171
attrs_and_strategy: list[tuple[_CountingAttr, st.SearchStrategy[PosArgs]]],
169172
frozen=None,
170-
) -> SearchStrategy[tuple]:
173+
) -> SearchStrategy[AttrsAndArgs]:
171174
"""
172175
A helper function for Hypothesis to generate attrs classes.
173176
@@ -192,7 +195,7 @@ def key(t):
192195
return st.tuples(
193196
st.builds(
194197
lambda f: make_class(
195-
"HypClass", OrderedDict(zip(gen_attr_names(), attrs)), frozen=f
198+
"HypClass", dict(zip(gen_attr_names(), attrs)), frozen=f
196199
),
197200
st.booleans() if frozen is None else st.just(frozen),
198201
),
@@ -209,26 +212,28 @@ def just_class(tup):
209212
return _create_hyp_class(combined_attrs)
210213

211214

212-
def just_class_with_type(tup):
215+
def just_class_with_type(tup: tuple) -> SearchStrategy[AttrsAndArgs]:
213216
nested_cl = tup[1][0]
214-
default = attr.Factory(nested_cl)
215-
combined_attrs = list(tup[0])
216-
combined_attrs.append(
217-
(attr.ib(default=default, type=nested_cl), st.just(nested_cl()))
218-
)
219-
return _create_hyp_class(combined_attrs)
220217

218+
def make_with_default(takes_self: bool) -> SearchStrategy[AttrsAndArgs]:
219+
combined_attrs = list(tup[0])
220+
combined_attrs.append(
221+
(
222+
attr.ib(
223+
default=(
224+
Factory(
225+
nested_cl if not takes_self else lambda _: nested_cl(),
226+
takes_self=takes_self,
227+
)
228+
),
229+
type=nested_cl,
230+
),
231+
st.just(nested_cl()),
232+
)
233+
)
234+
return _create_hyp_class(combined_attrs)
221235

222-
def just_class_with_type_takes_self(
223-
tup: tuple[list[tuple[_CountingAttr, SearchStrategy]], tuple[type[AttrsInstance]]]
224-
) -> SearchStrategy[tuple[type[AttrsInstance]]]:
225-
nested_cl = tup[1][0]
226-
default = Factory(lambda _: nested_cl(), takes_self=True)
227-
combined_attrs = list(tup[0])
228-
combined_attrs.append(
229-
(attr.ib(default=default, type=nested_cl), st.just(nested_cl()))
230-
)
231-
return _create_hyp_class(combined_attrs)
236+
return booleans().flatmap(make_with_default)
232237

233238

234239
def just_frozen_class_with_type(tup):
@@ -240,22 +245,45 @@ def just_frozen_class_with_type(tup):
240245
return _create_hyp_class(combined_attrs)
241246

242247

243-
def list_of_class(tup):
248+
def list_of_class(tup: tuple) -> SearchStrategy[AttrsAndArgs]:
244249
nested_cl = tup[1][0]
245-
default = attr.Factory(lambda: [nested_cl()])
246-
combined_attrs = list(tup[0])
247-
combined_attrs.append((attr.ib(default=default), st.just([nested_cl()])))
248-
return _create_hyp_class(combined_attrs)
249250

251+
def make_with_default(takes_self: bool) -> SearchStrategy[AttrsAndArgs]:
252+
combined_attrs = list(tup[0])
253+
combined_attrs.append(
254+
(
255+
attr.ib(
256+
default=(
257+
Factory(lambda: [nested_cl()])
258+
if not takes_self
259+
else Factory(lambda _: [nested_cl()], takes_self=True)
260+
),
261+
type=list[nested_cl],
262+
),
263+
st.just([nested_cl()]),
264+
)
265+
)
266+
return _create_hyp_class(combined_attrs)
267+
268+
return booleans().flatmap(make_with_default)
250269

251-
def list_of_class_with_type(tup):
270+
271+
def list_of_class_with_type(tup: tuple) -> SearchStrategy[AttrsAndArgs]:
252272
nested_cl = tup[1][0]
253-
default = attr.Factory(lambda: [nested_cl()])
254-
combined_attrs = list(tup[0])
255-
combined_attrs.append(
256-
(attr.ib(default=default, type=List[nested_cl]), st.just([nested_cl()]))
257-
)
258-
return _create_hyp_class(combined_attrs)
273+
274+
def make_with_default(takes_self: bool) -> SearchStrategy[AttrsAndArgs]:
275+
default = (
276+
Factory(lambda: [nested_cl()])
277+
if not takes_self
278+
else Factory(lambda _: [nested_cl()], takes_self=True)
279+
)
280+
combined_attrs = list(tup[0])
281+
combined_attrs.append(
282+
(attr.ib(default=default, type=List[nested_cl]), st.just([nested_cl()]))
283+
)
284+
return _create_hyp_class(combined_attrs)
285+
286+
return booleans().flatmap(make_with_default)
259287

260288

261289
def dict_of_class(tup):
@@ -266,7 +294,9 @@ def dict_of_class(tup):
266294
return _create_hyp_class(combined_attrs)
267295

268296

269-
def _create_hyp_nested_strategy(simple_class_strategy):
297+
def _create_hyp_nested_strategy(
298+
simple_class_strategy: SearchStrategy,
299+
) -> SearchStrategy:
270300
"""
271301
Create a recursive attrs class.
272302
Given a strategy for building (simpler) classes, create and return
@@ -275,6 +305,7 @@ def _create_hyp_nested_strategy(simple_class_strategy):
275305
* a list of simpler classes
276306
* a dict mapping the string "cls" to a simpler class.
277307
"""
308+
278309
# A strategy producing tuples of the form ([list of attributes], <given
279310
# class strategy>).
280311
attrs_and_classes = st.tuples(lists_of_attrs(defaults=True), simple_class_strategy)
@@ -286,7 +317,6 @@ def _create_hyp_nested_strategy(simple_class_strategy):
286317
| attrs_and_classes.flatmap(list_of_class_with_type)
287318
| attrs_and_classes.flatmap(dict_of_class)
288319
| attrs_and_classes.flatmap(just_frozen_class_with_type)
289-
| attrs_and_classes.flatmap(just_class_with_type_takes_self)
290320
)
291321

292322

@@ -430,9 +460,10 @@ def simple_classes(defaults=None, min_attrs=0, frozen=None, kw_only=None):
430460
)
431461

432462

433-
# Ok, so st.recursive works by taking a base strategy (in this case,
434-
# simple_classes) and a special function. This function receives a strategy,
435-
# and returns another strategy (building on top of the base strategy).
436-
nested_classes = st.recursive(
437-
simple_classes(defaults=True), _create_hyp_nested_strategy
438-
)
463+
def nested_classes(
464+
takes_self: FeatureFlag = "sometimes",
465+
) -> SearchStrategy[AttrsAndArgs]:
466+
# Ok, so st.recursive works by taking a base strategy (in this case,
467+
# simple_classes) and a special function. This function receives a strategy,
468+
# and returns another strategy (building on top of the base strategy).
469+
return st.recursive(simple_classes(defaults=True), _create_hyp_nested_strategy)

0 commit comments

Comments
 (0)