2015-01-27 16:53:17 +00:00
|
|
|
from __future__ import absolute_import, division, print_function
|
2017-02-19 08:51:43 +00:00
|
|
|
|
2016-09-10 05:58:55 +00:00
|
|
|
import pickle
|
2015-01-27 16:53:17 +00:00
|
|
|
|
2015-01-29 11:20:17 +00:00
|
|
|
import pytest
|
2017-03-07 12:57:03 +00:00
|
|
|
import six
|
2017-02-19 08:51:43 +00:00
|
|
|
|
2016-03-22 23:15:14 +00:00
|
|
|
from hypothesis import given
|
|
|
|
from hypothesis.strategies import booleans
|
2015-01-27 16:53:17 +00:00
|
|
|
|
|
|
|
import attr
|
|
|
|
|
2015-01-29 15:24:49 +00:00
|
|
|
from attr._compat import TYPE
|
2015-01-30 07:57:33 +00:00
|
|
|
from attr._make import Attribute, NOTHING
|
2016-08-20 16:45:15 +00:00
|
|
|
from attr.exceptions import FrozenInstanceError
|
2015-01-27 16:53:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
@attr.s
|
|
|
|
class C1(object):
|
2015-01-29 11:20:17 +00:00
|
|
|
x = attr.ib(validator=attr.validators.instance_of(int))
|
2015-01-27 22:03:42 +00:00
|
|
|
y = attr.ib()
|
2015-01-27 16:53:17 +00:00
|
|
|
|
|
|
|
|
2016-03-14 02:02:13 +00:00
|
|
|
@attr.s(slots=True)
|
|
|
|
class C1Slots(object):
|
|
|
|
x = attr.ib(validator=attr.validators.instance_of(int))
|
|
|
|
y = attr.ib()
|
|
|
|
|
2016-11-19 08:47:03 +00:00
|
|
|
|
2015-01-27 16:53:17 +00:00
|
|
|
foo = None
|
|
|
|
|
|
|
|
|
2016-03-17 18:40:11 +00:00
|
|
|
@attr.s()
|
2015-01-27 16:53:17 +00:00
|
|
|
class C2(object):
|
2015-01-29 21:32:41 +00:00
|
|
|
x = attr.ib(default=foo)
|
2015-01-29 22:10:56 +00:00
|
|
|
y = attr.ib(default=attr.Factory(list))
|
2015-01-27 16:53:17 +00:00
|
|
|
|
|
|
|
|
2016-03-14 02:02:13 +00:00
|
|
|
@attr.s(slots=True)
|
|
|
|
class C2Slots(object):
|
|
|
|
x = attr.ib(default=foo)
|
|
|
|
y = attr.ib(default=attr.Factory(list))
|
|
|
|
|
|
|
|
|
2015-02-18 20:31:32 +00:00
|
|
|
@attr.s
|
|
|
|
class Super(object):
|
|
|
|
x = attr.ib()
|
|
|
|
|
|
|
|
def meth(self):
|
|
|
|
return self.x
|
|
|
|
|
|
|
|
|
2016-03-14 02:02:13 +00:00
|
|
|
@attr.s(slots=True)
|
|
|
|
class SuperSlots(object):
|
|
|
|
x = attr.ib()
|
|
|
|
|
|
|
|
def meth(self):
|
|
|
|
return self.x
|
|
|
|
|
|
|
|
|
2015-02-18 20:31:32 +00:00
|
|
|
@attr.s
|
|
|
|
class Sub(Super):
|
|
|
|
y = attr.ib()
|
|
|
|
|
|
|
|
|
2016-03-14 02:02:13 +00:00
|
|
|
@attr.s(slots=True)
|
|
|
|
class SubSlots(SuperSlots):
|
|
|
|
y = attr.ib()
|
|
|
|
|
|
|
|
|
2016-08-20 16:45:15 +00:00
|
|
|
@attr.s(frozen=True, slots=True)
|
|
|
|
class Frozen(object):
|
|
|
|
x = attr.ib()
|
|
|
|
|
|
|
|
|
2017-02-12 07:33:20 +00:00
|
|
|
@attr.s
|
|
|
|
class SubFrozen(Frozen):
|
|
|
|
y = attr.ib()
|
|
|
|
|
|
|
|
|
2016-09-10 05:58:55 +00:00
|
|
|
@attr.s(frozen=True, slots=False)
|
|
|
|
class FrozenNoSlots(object):
|
|
|
|
x = attr.ib()
|
|
|
|
|
|
|
|
|
2017-03-07 12:57:03 +00:00
|
|
|
class Meta(type):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@attr.s
|
|
|
|
@six.add_metaclass(Meta)
|
|
|
|
class WithMeta(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@attr.s(slots=True)
|
|
|
|
@six.add_metaclass(Meta)
|
|
|
|
class WithMetaSlots(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-01-27 16:53:17 +00:00
|
|
|
class TestDarkMagic(object):
|
|
|
|
"""
|
|
|
|
Integration tests.
|
|
|
|
"""
|
2016-03-17 18:40:11 +00:00
|
|
|
@pytest.mark.parametrize("cls", [C2, C2Slots])
|
|
|
|
def test_fields(self, cls):
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
2015-01-29 20:55:25 +00:00
|
|
|
`attr.fields` works.
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
2015-03-23 08:16:43 +00:00
|
|
|
assert (
|
2017-05-16 07:36:39 +00:00
|
|
|
Attribute(name="x", _default=foo, _validator=None,
|
2017-02-19 08:51:43 +00:00
|
|
|
repr=True, cmp=True, hash=None, init=True),
|
2017-05-16 07:36:39 +00:00
|
|
|
Attribute(name="y", _default=attr.Factory(list), _validator=None,
|
2017-02-19 08:51:43 +00:00
|
|
|
repr=True, cmp=True, hash=None, init=True),
|
2016-03-17 18:40:11 +00:00
|
|
|
) == attr.fields(cls)
|
2015-01-27 16:53:17 +00:00
|
|
|
|
2016-03-17 18:40:11 +00:00
|
|
|
@pytest.mark.parametrize("cls", [C1, C1Slots])
|
|
|
|
def test_asdict(self, cls):
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
2015-01-29 20:50:07 +00:00
|
|
|
`attr.asdict` works.
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
|
|
|
assert {
|
|
|
|
"x": 1,
|
|
|
|
"y": 2,
|
2016-03-17 18:40:11 +00:00
|
|
|
} == attr.asdict(cls(x=1, y=2))
|
2015-01-29 11:20:17 +00:00
|
|
|
|
2016-03-17 18:40:11 +00:00
|
|
|
@pytest.mark.parametrize("cls", [C1, C1Slots])
|
|
|
|
def test_validator(self, cls):
|
2015-01-29 11:20:17 +00:00
|
|
|
"""
|
|
|
|
`instance_of` raises `TypeError` on type mismatch.
|
|
|
|
"""
|
|
|
|
with pytest.raises(TypeError) as e:
|
2016-03-17 18:40:11 +00:00
|
|
|
cls("1", 2)
|
2016-03-14 02:02:13 +00:00
|
|
|
|
|
|
|
# Using C1 explicitly, since slot classes don't support this.
|
2015-01-29 11:20:17 +00:00
|
|
|
assert (
|
|
|
|
"'x' must be <{type} 'int'> (got '1' that is a <{type} "
|
|
|
|
"'str'>).".format(type=TYPE),
|
|
|
|
C1.x, int, "1",
|
|
|
|
) == e.value.args
|
2015-01-29 12:05:04 +00:00
|
|
|
|
2016-03-22 23:15:14 +00:00
|
|
|
@given(booleans())
|
2016-03-14 02:02:13 +00:00
|
|
|
def test_renaming(self, slots):
|
2015-01-29 12:05:04 +00:00
|
|
|
"""
|
|
|
|
Private members are renamed but only in `__init__`.
|
|
|
|
"""
|
2016-03-14 02:02:13 +00:00
|
|
|
@attr.s(slots=slots)
|
2015-01-29 12:05:04 +00:00
|
|
|
class C3(object):
|
|
|
|
_x = attr.ib()
|
|
|
|
|
|
|
|
assert "C3(_x=1)" == repr(C3(x=1))
|
2015-01-30 07:57:33 +00:00
|
|
|
|
2016-08-20 16:45:15 +00:00
|
|
|
@given(booleans(), booleans())
|
|
|
|
def test_programmatic(self, slots, frozen):
|
2015-01-30 07:57:33 +00:00
|
|
|
"""
|
|
|
|
`attr.make_class` works.
|
|
|
|
"""
|
2016-08-20 16:45:15 +00:00
|
|
|
PC = attr.make_class("PC", ["a", "b"], slots=slots, frozen=frozen)
|
2015-03-23 08:16:43 +00:00
|
|
|
assert (
|
2017-05-16 07:36:39 +00:00
|
|
|
Attribute(name="a", _default=NOTHING, _validator=None,
|
2017-02-19 08:51:43 +00:00
|
|
|
repr=True, cmp=True, hash=None, init=True),
|
2017-05-16 07:36:39 +00:00
|
|
|
Attribute(name="b", _default=NOTHING, _validator=None,
|
2017-02-19 08:51:43 +00:00
|
|
|
repr=True, cmp=True, hash=None, init=True),
|
2015-03-23 08:16:43 +00:00
|
|
|
) == attr.fields(PC)
|
2015-02-18 20:31:32 +00:00
|
|
|
|
2016-03-17 18:40:11 +00:00
|
|
|
@pytest.mark.parametrize("cls", [Sub, SubSlots])
|
|
|
|
def test_subclassing_with_extra_attrs(self, cls):
|
2015-02-18 20:31:32 +00:00
|
|
|
"""
|
2015-07-21 23:25:28 +00:00
|
|
|
Sub-classing (where the subclass has extra attrs) does what you'd hope
|
|
|
|
for.
|
2015-02-18 20:31:32 +00:00
|
|
|
"""
|
|
|
|
obj = object()
|
2016-03-17 18:40:11 +00:00
|
|
|
i = cls(x=obj, y=2)
|
2015-02-18 20:31:32 +00:00
|
|
|
assert i.x is i.meth() is obj
|
|
|
|
assert i.y == 2
|
2016-03-17 18:40:11 +00:00
|
|
|
if cls is Sub:
|
2016-03-14 02:02:13 +00:00
|
|
|
assert "Sub(x={obj}, y=2)".format(obj=obj) == repr(i)
|
|
|
|
else:
|
|
|
|
assert "SubSlots(x={obj}, y=2)".format(obj=obj) == repr(i)
|
2015-07-21 23:25:28 +00:00
|
|
|
|
2016-03-14 02:02:13 +00:00
|
|
|
@pytest.mark.parametrize("base", [Super, SuperSlots])
|
|
|
|
def test_subclass_without_extra_attrs(self, base):
|
2015-07-21 23:25:28 +00:00
|
|
|
"""
|
|
|
|
Sub-classing (where the subclass does not have extra attrs) still
|
|
|
|
behaves the same as a subclss with extra attrs.
|
|
|
|
"""
|
2016-03-14 02:02:13 +00:00
|
|
|
class Sub2(base):
|
2015-07-21 23:25:28 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
obj = object()
|
|
|
|
i = Sub2(x=obj)
|
|
|
|
assert i.x is i.meth() is obj
|
|
|
|
assert "Sub2(x={obj})".format(obj=obj) == repr(i)
|
2016-08-20 16:45:15 +00:00
|
|
|
|
|
|
|
@pytest.mark.parametrize("frozen_class", [
|
|
|
|
Frozen, # has slots=True
|
|
|
|
attr.make_class("FrozenToo", ["x"], slots=False, frozen=True),
|
|
|
|
])
|
|
|
|
def test_frozen_instance(self, frozen_class):
|
|
|
|
"""
|
|
|
|
Frozen instances can't be modified (easily).
|
|
|
|
"""
|
|
|
|
frozen = frozen_class(1)
|
|
|
|
|
|
|
|
with pytest.raises(FrozenInstanceError) as e:
|
|
|
|
frozen.x = 2
|
|
|
|
|
2016-12-14 09:55:43 +00:00
|
|
|
with pytest.raises(FrozenInstanceError) as e:
|
|
|
|
del frozen.x
|
|
|
|
|
2016-08-20 16:45:15 +00:00
|
|
|
assert e.value.args[0] == "can't set attribute"
|
|
|
|
assert 1 == frozen.x
|
2016-09-10 05:58:55 +00:00
|
|
|
|
|
|
|
@pytest.mark.parametrize("cls",
|
|
|
|
[C1, C1Slots, C2, C2Slots, Super, SuperSlots,
|
|
|
|
Sub, SubSlots, Frozen, FrozenNoSlots])
|
|
|
|
@pytest.mark.parametrize("protocol",
|
|
|
|
range(2, pickle.HIGHEST_PROTOCOL + 1))
|
|
|
|
def test_pickle_attributes(self, cls, protocol):
|
|
|
|
"""
|
|
|
|
Pickling/un-pickling of Attribute instances works.
|
|
|
|
"""
|
|
|
|
for attribute in attr.fields(cls):
|
|
|
|
assert attribute == pickle.loads(pickle.dumps(attribute, protocol))
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("cls",
|
|
|
|
[C1, C1Slots, C2, C2Slots, Super, SuperSlots,
|
|
|
|
Sub, SubSlots, Frozen, FrozenNoSlots])
|
|
|
|
@pytest.mark.parametrize("protocol",
|
|
|
|
range(2, pickle.HIGHEST_PROTOCOL + 1))
|
|
|
|
def test_pickle_object(self, cls, protocol):
|
|
|
|
"""
|
|
|
|
Pickle object serialization works on all kinds of attrs classes.
|
|
|
|
"""
|
|
|
|
if len(attr.fields(cls)) == 2:
|
|
|
|
obj = cls(123, 456)
|
|
|
|
else:
|
|
|
|
obj = cls(123)
|
|
|
|
assert repr(obj) == repr(pickle.loads(pickle.dumps(obj, protocol)))
|
2017-02-12 07:33:20 +00:00
|
|
|
|
|
|
|
def test_subclassing_frozen_gives_frozen(self):
|
|
|
|
"""
|
|
|
|
The frozen-ness of classes is inherited. Subclasses of frozen classes
|
|
|
|
are also frozen and can be instantiated.
|
|
|
|
"""
|
|
|
|
i = SubFrozen("foo", "bar")
|
|
|
|
|
|
|
|
assert i.x == "foo"
|
|
|
|
assert i.y == "bar"
|
2017-03-07 12:57:03 +00:00
|
|
|
|
|
|
|
@pytest.mark.parametrize("cls", [WithMeta, WithMetaSlots])
|
|
|
|
def test_metaclass_preserved(self, cls):
|
2017-05-16 07:36:39 +00:00
|
|
|
"""
|
|
|
|
Metaclass data is preserved.
|
|
|
|
"""
|
2017-03-07 12:57:03 +00:00
|
|
|
assert Meta == type(cls)
|
2017-05-16 07:36:39 +00:00
|
|
|
|
|
|
|
def test_default_decorator(self):
|
|
|
|
"""
|
|
|
|
Default decorator sets the default and the respective method gets
|
|
|
|
called.
|
|
|
|
"""
|
|
|
|
@attr.s
|
|
|
|
class C(object):
|
|
|
|
x = attr.ib(default=1)
|
|
|
|
y = attr.ib()
|
|
|
|
|
|
|
|
@y.default
|
|
|
|
def compute(self):
|
|
|
|
return self.x + 1
|
|
|
|
|
|
|
|
assert C(1, 2) == C()
|