def test_multiple_empty(self): """ Empty list/tuple for validator is the same as None. """ C1 = make_class("C", {"x": attr.ib(validator=[])}) C2 = make_class("C", {"x": attr.ib(validator=None)}) assert inspect.getsource(C1.__init__) == inspect.getsource(C2.__init__)
def test_no_init_order(self): """ If an attribute is `init=False`, it's legal to come after a mandatory attribute. """ make_class("C", { "a": attr(default=Factory(list)), "b": attr(init=False), })
def test_no_init_order(self, slots, frozen): """ If an attribute is `init=False`, it's legal to come after a mandatory attribute. """ make_class("C", { "a": attr.ib(default=Factory(list)), "b": attr.ib(init=False), }, slots=slots, frozen=frozen)
def test_catches_wrong_attrs_type(self): """ Raise `TypeError` if an invalid type for attrs is passed. """ with pytest.raises(TypeError) as e: make_class("C", object()) assert ( "attrs argument must be a dict or a list.", ) == e.value.args
def test_repr_str(self): """ Trying to add a `__str__` without having a `__repr__` raises a ValueError. """ with pytest.raises(ValueError) as ei: make_class("C", {}, repr=False, str=True) assert ( "__str__ can only be generated if a __repr__ exists.", ) == ei.value.args
def test_bases(self): """ Parameter bases default to (object,) and subclasses correctly """ class D(object): pass cls = make_class("C", {}) assert cls.__mro__[-1] == object cls = make_class("C", {}, bases=(D,)) assert D in cls.__mro__ assert isinstance(cls(), D)
def test_multiple_validators(self): """ If a list is passed as a validator, all of its items are treated as one and must pass. """ def v1(_, __, value): if value == 23: raise TypeError("omg") def v2(_, __, value): if value == 42: raise ValueError("omg") C = make_class("C", {"x": attr.ib(validator=[v1, v2])}) validate(C(1)) with pytest.raises(TypeError) as e: C(23) assert "omg" == e.value.args[0] with pytest.raises(ValueError) as e: C(42) assert "omg" == e.value.args[0]
def test_success(self): """ If the validator suceeds, nothing gets raised. """ C = make_class("C", {"x": attr(validator=lambda *a: None), "y": attr()}) validate(C(1, 2))
def test_hash(self): """ If `hash` is False, ignore that attribute. """ C = make_class("C", {"a": attr(hash=False), "b": attr()}) assert hash(C(1, 2)) == hash(C(2, 2))
def _create_hyp_class(attrs): """ A helper function for Hypothesis to generate attrs classes. """ return make_class( "HypClass", dict(zip(gen_attr_names(), attrs)) )
def test_enforces_type(self): """ The `hash` argument to both attrs and attrib must be None, True, or False. """ exc_args = ("Invalid value for hash. Must be True, False, or None.",) with pytest.raises(TypeError) as e: make_class("C", {}, hash=1), assert exc_args == e.value.args with pytest.raises(TypeError) as e: make_class("C", {"a": attr(hash=1)}), assert exc_args == e.value.args
def test_empty_metadata_singleton(self, list_of_attrs): """ All empty metadata attributes share the same empty metadata dict. """ C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) for a in fields(C)[1:]: assert a.metadata is fields(C)[0].metadata
def test_cmp(self): """ If `cmp` is False, ignore that attribute. """ C = make_class("C", {"a": attr(cmp=False), "b": attr()}) assert C(1, 2) == C(2, 2)
def test_attr_args(self): """ attributes_arguments are passed to attributes """ C = make_class("C", ["x"], repr=False) assert repr(C(1)).startswith("<tests.test_make.C object at 0x")
def test_repr(self): """ If `repr` is False, ignore that attribute. """ C = make_class("C", {"a": attr(repr=False), "b": attr()}) assert "C(b=2)" == repr(C(1, 2))
def simple_class(cmp=False, repr=False, hash=False, slots=False): """ Return a new simple class. """ return make_class( "C", ["a", "b"], cmp=cmp, repr=repr, hash=hash, init=True, slots=slots )
def test_frozen(self): """ Converters circumvent immutability. """ C = make_class("C", { "x": attr.ib(converter=lambda v: int(v)), }, frozen=True) C("1")
def test_missing_sys_getframe(self, monkeypatch): """ `make_class()` does not fail when `sys._getframe()` is not available. """ monkeypatch.delattr(sys, '_getframe') C = make_class("C", ["x"]) assert 1 == len(C.__attrs_attrs__)
def test_hash_attribute(self, slots): """ If `hash` is False on an attribute, ignore that attribute. """ C = make_class("C", {"a": attr(hash=False), "b": attr()}, slots=slots, hash=True) assert hash(C(1, 2)) == hash(C(2, 2))
def test_clean_class(self, slots): """ Attribute definitions do not appear on the class body. """ C = make_class("C", ["x"], slots=slots) x = getattr(C, "x", None) assert not isinstance(x, _CountingAttr)
def test_repr_uninitialized_member(self): """ repr signals unset attributes """ C = make_class("C", { "a": attr.ib(init=False), }) assert "C(a=NOTHING)" == repr(C())
def test_convert(self): """ Return value of convert is used as the attribute's value. """ C = make_class("C", {"x": attr(convert=lambda v: v + 1), "y": attr()}) c = C(1, 2) assert c.x == 2 assert c.y == 2
def test_validator_others(self): """ Does not interfere when setting non-attrs attributes. """ C = make_class("C", {"a": attr("a", validator=instance_of(int))}) i = C(1) i.b = "foo" assert 1 == i.a assert "foo" == i.b
def test_init(self): """ If `init` is False, ignore that attribute. """ C = make_class("C", {"a": attr(init=False), "b": attr()}) with pytest.raises(TypeError) as e: C(a=1, b=2) msg = e.value if PY26 else e.value.args[0] assert "__init__() got an unexpected keyword argument 'a'" == msg
def test_cmp(self, slots): """ If `cmp` is False, ignore that attribute. """ C = make_class("C", { "a": attr.ib(cmp=False), "b": attr.ib() }, slots=slots) assert C(1, 2) == C(2, 2)
def test_convert_property(self, val, init): """ Property tests for attributes with convert. """ C = make_class("C", {"y": attr(), "x": attr(init=init, default=val, convert=lambda v: v + 1), }) c = C(2) assert c.x == val + 1 assert c.y == 2
def test_factory_takes_self(self): """ If takes_self on factories is True, self is passed. """ C = make_class("C", {"x": attr(default=Factory( (lambda self: self), takes_self=True ))}) i = C() assert i is i.x
def test_not_none_metadata(self, list_of_attrs): """ Non-empty metadata attributes exist as fields after ``@attr.s``. """ C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) assert len(fields(C)) > 0 for cls_a, raw_a in zip(fields(C), list_of_attrs): assert cls_a.metadata != {} assert cls_a.metadata == raw_a.metadata
def test_simple(self, ls): """ Passing a list of strings creates attributes with default args. """ C1 = make_class("C1", ls(["a", "b"])) @attr.s class C2(object): a = attr.ib() b = attr.ib() assert C1.__attrs_attrs__ == C2.__attrs_attrs__
def test_dict(self): """ Passing a dict of name: _CountingAttr creates an equivalent class. """ C1 = make_class("C1", {"a": attr(default=42), "b": attr(default=None)}) @attributes class C2(object): a = attr(default=42) b = attr(default=None) assert C1.__attrs_attrs__ == C2.__attrs_attrs__
def test_factory_takes_self(self): """ If takes_self on factories is True, self is passed. """ C = make_class( "C", { "x": attr.ib(default=Factory((lambda self: self), takes_self=True)) }, ) i = C() assert i is i.x
def test_hash_attribute(self, slots): """ If `hash` is False on an attribute, ignore that attribute. """ C = make_class( "C", { "a": attr.ib(hash=False), "b": attr.ib() }, slots=slots, hash=True, ) assert hash(C(1, 2)) == hash(C(2, 2))
def test_propagates(self): """ The exception of the validator is handed through. """ def raiser(_, __, value): if value == 42: raise FloatingPointError C = make_class("C", {"x": attr.ib(validator=raiser)}) i = C(1) i.x = 42 with pytest.raises(FloatingPointError): validate(i)
def test_init(self, slots, frozen): """ If `init` is False, ignore that attribute. """ C = make_class("C", { "a": attr.ib(init=False), "b": attr.ib() }, slots=slots, frozen=frozen) with pytest.raises(TypeError) as e: C(a=1, b=2) assert ("__init__() got an unexpected keyword argument 'a'" == e.value.args[0])
def test_dict(self): """ Passing a dict of name: _CountingAttr creates an equivalent class. """ C1 = make_class("C1", { "a": attr.ib(default=42), "b": attr.ib(default=None), }) @attr.s class C2(object): a = attr.ib(default=42) b = attr.ib(default=None) assert C1.__attrs_attrs__ == C2.__attrs_attrs__
def test_validator_others(self, slots): """ Does not interfere when setting non-attrs attributes. """ C = make_class("C", {"a": attr.ib("a", validator=instance_of(int))}, slots=slots) i = C(1) assert 1 == i.a if not slots: i.b = "foo" assert "foo" == i.b else: with pytest.raises(AttributeError): i.b = "foo"
def test_convert_factory_property(self, val, init): """ Property tests for attributes with convert, and a factory default. """ C = make_class("C", ordered_dict([ ("y", attr.ib()), ("x", attr.ib( init=init, default=Factory(lambda: val), converter=lambda v: v + 1 )), ])) c = C(2) assert c.x == val + 1 assert c.y == 2
def test_convert_before_validate(self): """ Validation happens after conversion. """ def validator(inst, attr, val): raise RuntimeError("foo") C = make_class( "C", { "x": attr.ib(validator=validator, converter=lambda v: 1 / 0), "y": attr.ib(), }, ) with pytest.raises(ZeroDivisionError): C(1, 2)
def test_convert_property(self, val, init): """ Property tests for attributes using converter. """ C = make_class( "C", { "y": attr.ib(), "x": attr.ib(init=init, default=val, converter=lambda v: v + 1), }, ) c = C(2) assert c.x == val + 1 assert c.y == 2
def test_convert_factory_property(self, val, init): """ Property tests for attributes with convert, and a factory default. """ C = make_class( "C", { "y": attr(), "x": attr(init=init, default=Factory(lambda: val), convert=lambda v: v + 1), }) c = C(2) assert c.x == val + 1 assert c.y == 2
def test_validator_slots(self): """ If a validator is passed, call it with the preliminary instance, the Attribute, and the argument. """ class VException(Exception): pass def raiser(*args): raise VException(*args) C = make_class("C", {"a": attr("a", validator=raiser)}, slots=True) with pytest.raises(VException) as e: C(42) assert (fields(C)[0], 42,) == e.value.args[1:] assert isinstance(e.value.args[0], C)
def simple_classes(draw, slots=None, frozen=None, private_attrs=None): """ A strategy that generates classes with default non-attr attributes. For example, this strategy might generate a class such as: @attr.s(slots=True, frozen=True) class HypClass: a = attr.ib(default=1) _b = attr.ib(default=None) c = attr.ib(default='text') _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 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): pass cls_dict["__attrs_post_init__"] = post_init return make_class( "HypClass", cls_dict, slots=slots_flag, frozen=frozen_flag, )
def test_hash_mirrors_eq(self, eq): """ If `hash` is None, the hash generation mirrors `eq`. """ C = make_class("C", {"a": attr.ib()}, eq=eq, frozen=True) i = C(1) assert i == i assert hash(i) == hash(i) if eq: assert C(1) == C(1) assert hash(C(1)) == hash(C(1)) else: assert C(1) != C(1) assert hash(C(1)) != hash(C(1))
def test_validator(self): """ If a validator is passed, call it with the Attribute and the argument. """ class VException(Exception): pass def raiser(*args): raise VException(args) C = make_class("C", {"a": attr("a", validator=raiser)}) with pytest.raises(VException) as e: C(42) assert (( C.a, 42, ), ) == e.value.args
def test_no_init_default(self, slots, frozen): """ If `init` is False but a Factory is specified, don't allow passing that argument but initialize it anyway. """ C = make_class("C", { "_a": attr(init=False, default=42), "_b": attr(init=False, default=Factory(list)), "c": attr() }, slots=slots, frozen=frozen) with pytest.raises(TypeError): C(a=1, c=2) with pytest.raises(TypeError): C(b=1, c=2) i = C(23) assert (42, [], 23) == (i._a, i._b, i.c)
def test_hash_mirrors_cmp(self, cmp): """ If `hash` is None, the hash generation mirrors `cmp`. """ C = make_class("C", {"a": attr.ib()}, cmp=cmp, frozen=True) i = C(1) assert i == i assert hash(i) == hash(i) if cmp: assert C(1) == C(1) assert hash(C(1)) == hash(C(1)) else: assert C(1) != C(1) assert hash(C(1)) != hash(C(1))
def test_run_validators(self): """ Setting `_run_validators` to False prevents validators from running. """ _config._run_validators = False obj = object() def raiser(_, __, ___): raise Exception(obj) C = make_class("C", {"x": attr(validator=raiser)}) assert 1 == C(1).x _config._run_validators = True with pytest.raises(Exception) as e: C(1) assert (obj,) == e.value.args
def test_metadata_present(self, list_of_attrs): """ Assert dictionaries are copied and present. """ C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) for hyp_attr, class_attr in zip(list_of_attrs, fields(C)): if hyp_attr.metadata is None: # The default is a singleton empty dict. assert class_attr.metadata is not None assert len(class_attr.metadata) == 0 else: assert hyp_attr.metadata == class_attr.metadata # Once more, just to assert getting items and iteration. for k in class_attr.metadata: assert hyp_attr.metadata[k] == class_attr.metadata[k] assert ( hyp_attr.metadata.get(k) == class_attr.metadata.get(k))
def simple_class(cmp=False, repr=False, hash=False, str=False, slots=False, frozen=False): """ Return a new simple class. """ return make_class( "C", ["a", "b"], cmp=cmp, repr=repr, hash=hash, init=True, slots=slots, str=str, frozen=frozen, )
def simple_classes(draw, slots=None, frozen=None): """A strategy that generates classes with default non-attr attributes. For example, this strategy might generate a class such as: @attr.s(slots=True, frozen=True) class HypClass: a = attr.ib(default=1) b = attr.ib(default=None) c = attr.ib(default='text') 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`. """ 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 return make_class('HypClass', dict(zip(_gen_attr_names(), attrs)), slots=slots_flag, frozen=frozen_flag)
def test_attr_args(self): """ attributes_arguments are passed to attributes """ C = make_class("C", ["x"], repr=False) assert repr(C(1)).startswith("<attr._make.C object at 0x")
def test_frozen(self): """ Converters circumvent immutability. """ C = make_class("C", {"x": attr(convert=lambda v: int(v))}, frozen=True) C("1")
def test_success(self): """ If the validator suceeds, nothing gets raised. """ C = make_class("C", {"x": attr(validator=lambda _, __: None)}) validate(C(1))
def _create_hyp_class(attrs): """ A helper function for Hypothesis to generate attrs classes. """ return make_class("HypClass", dict(zip(gen_attr_names(), attrs)))