Feature/better simple classes (#119)

This commit is contained in:
Tin Tvrtković 2016-12-05 09:50:50 +01:00 committed by Hynek Schlawack
parent 6771640853
commit 0c8211d922
2 changed files with 40 additions and 11 deletions

View File

@ -132,10 +132,13 @@ class TestAsDict(object):
} == res
assert isinstance(res, dict_factory)
@given(simple_classes(), st.sampled_from(MAPPING_TYPES))
@given(simple_classes(private_attrs=False), st.sampled_from(MAPPING_TYPES))
def test_roundtrip(self, cls, dict_class):
"""
Test dumping to dicts and back for Hypothesis-generated classes.
Private attributes don't round-trip (the attribute name is different
than the initializer argument).
"""
instance = cls()
dict_instance = asdict(instance, dict_factory=dict_class)
@ -359,22 +362,27 @@ class TestAssoc(object):
assert i1 is not i2
assert i1 == i2
@given(simple_classes(), st.integers())
def test_change(self, C, val):
@given(simple_classes(), st.data())
def test_change(self, C, data):
"""
Changes work.
"""
# Take the first attribute, and change it.
assume(fields(C)) # Skip classes with no attributes.
field_names = [a.name for a in fields(C)]
original = C()
attribute = fields(C)[0]
changed = assoc(original, **{attribute.name: val})
assert getattr(changed, attribute.name) == val
chosen_names = data.draw(st.sets(st.sampled_from(field_names)))
change_dict = {name: data.draw(st.integers())
for name in chosen_names}
changed = assoc(original, **change_dict)
for k, v in change_dict.items():
assert getattr(changed, k) == v
@given(simple_classes())
def test_unknown(self, C):
"""
Wanting to change an unknown attribute raises a ValueError.
Wanting to change an unknown attribute raises an
AttrsAttributeNotFoundError.
"""
# No generated class will have a four letter attribute.
with pytest.raises(AttrsAttributeNotFoundError) as e:

View File

@ -74,6 +74,16 @@ def gen_attr_names():
yield outer + inner
def maybe_underscore_prefix(source):
"""
A generator to sometimes prepend an underscore.
"""
to_underscore = False
for val in source:
yield val if not to_underscore else '_' + val
to_underscore = not to_underscore
def _create_hyp_class(attrs):
"""
A helper function for Hypothesis to generate attrs classes.
@ -164,7 +174,7 @@ list_of_attrs = st.lists(simple_attrs, average_size=3, max_size=9)
@st.composite
def simple_classes(draw, slots=None, frozen=None):
def simple_classes(draw, slots=None, frozen=None, private_attrs=None):
"""
A strategy that generates classes with default non-attr attributes.
@ -173,21 +183,32 @@ def simple_classes(draw, slots=None, frozen=None):
@attr.s(slots=True, frozen=True)
class HypClass:
a = attr.ib(default=1)
b = attr.ib(default=None)
_b = attr.ib(default=None)
c = attr.ib(default='text')
d = attr.ib(default=1.0)
_d = attr.ib(default=1.0)
c = attr.ib(default={'t': 1})
By default, all combinations of slots and frozen classes will be generated.
If `slots=True` is passed in, only slots classes will be generated, and
if `slots=False` is passed in, no slot classes will be generated. The same
applies to `frozen`.
By default, some attributes will be private (i.e. prefixed with an
underscore). If `private_attrs=True` is passed in, all attributes will be
private, and if `private_attrs=False`, no attributes will be private.
"""
attrs = draw(list_of_attrs)
frozen_flag = draw(st.booleans()) if frozen is None else frozen
slots_flag = draw(st.booleans()) if slots is None else slots
cls_dict = dict(zip(gen_attr_names(), attrs))
if private_attrs is None:
attr_names = maybe_underscore_prefix(gen_attr_names())
elif private_attrs is True:
attr_names = ('_' + n for n in gen_attr_names())
elif private_attrs is False:
attr_names = gen_attr_names()
cls_dict = dict(zip(attr_names, attrs))
post_init_flag = draw(st.booleans())
if post_init_flag:
def post_init(self):