2015-01-27 16:53:17 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2015-01-29 19:50:42 +00:00
|
|
|
"""
|
|
|
|
Tests for `attr._make`.
|
|
|
|
"""
|
|
|
|
|
2015-01-27 16:53:17 +00:00
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
2015-02-08 11:32:32 +00:00
|
|
|
from . import simple_attr
|
2015-01-27 16:53:17 +00:00
|
|
|
from attr._make import (
|
|
|
|
Attribute,
|
2015-01-29 22:10:56 +00:00
|
|
|
NOTHING,
|
2015-01-27 16:53:17 +00:00
|
|
|
_CountingAttr,
|
2015-01-29 15:24:49 +00:00
|
|
|
_transform_attrs,
|
2015-01-30 07:57:33 +00:00
|
|
|
attr,
|
|
|
|
attributes,
|
2015-02-09 12:16:56 +00:00
|
|
|
fields,
|
2015-01-30 07:57:33 +00:00
|
|
|
make_class,
|
2015-02-09 12:16:56 +00:00
|
|
|
validate,
|
2015-01-27 16:53:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2015-01-30 12:25:59 +00:00
|
|
|
class TestCountingAttr(object):
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
2015-01-29 18:39:49 +00:00
|
|
|
Tests for `attr`.
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
|
|
|
def test_returns_Attr(self):
|
|
|
|
"""
|
2015-01-29 19:50:42 +00:00
|
|
|
Returns an instance of _CountingAttr.
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
2015-01-29 18:39:49 +00:00
|
|
|
a = attr()
|
2015-01-27 16:53:17 +00:00
|
|
|
assert isinstance(a, _CountingAttr)
|
|
|
|
|
|
|
|
|
2015-01-29 15:24:49 +00:00
|
|
|
def make_tc():
|
|
|
|
class TransformC(object):
|
2015-01-29 18:39:49 +00:00
|
|
|
z = attr()
|
|
|
|
y = attr()
|
|
|
|
x = attr()
|
2015-01-29 15:24:49 +00:00
|
|
|
a = 42
|
|
|
|
return TransformC
|
|
|
|
|
|
|
|
|
|
|
|
class TestTransformAttrs(object):
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
2015-01-29 15:24:49 +00:00
|
|
|
Tests for `_transform_attrs`.
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
|
|
|
def test_normal(self):
|
|
|
|
"""
|
2015-01-29 15:24:49 +00:00
|
|
|
Transforms every `_CountingAttr` and leaves others (a) be.
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
2015-01-29 15:24:49 +00:00
|
|
|
C = make_tc()
|
2015-02-08 11:32:32 +00:00
|
|
|
_transform_attrs(C, None)
|
2015-01-29 15:24:49 +00:00
|
|
|
assert ["z", "y", "x"] == [a.name for a in C.__attrs_attrs__]
|
2015-01-27 16:53:17 +00:00
|
|
|
|
|
|
|
def test_empty(self):
|
|
|
|
"""
|
2015-01-29 15:24:49 +00:00
|
|
|
No attributes works as expected.
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
2015-01-29 18:39:49 +00:00
|
|
|
@attributes
|
2015-01-27 16:53:17 +00:00
|
|
|
class C(object):
|
|
|
|
pass
|
|
|
|
|
2015-02-08 11:32:32 +00:00
|
|
|
_transform_attrs(C, None)
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
assert [] == C.__attrs_attrs__
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("attribute", [
|
|
|
|
"z",
|
|
|
|
"y",
|
|
|
|
"x",
|
|
|
|
])
|
|
|
|
def test_transforms_to_attribute(self, attribute):
|
|
|
|
"""
|
|
|
|
All `_CountingAttr`s are transformed into `Attribute`s.
|
|
|
|
"""
|
|
|
|
C = make_tc()
|
2015-02-08 11:32:32 +00:00
|
|
|
_transform_attrs(C, None)
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
assert isinstance(getattr(C, attribute), Attribute)
|
2015-01-27 16:53:17 +00:00
|
|
|
|
2015-01-29 19:50:42 +00:00
|
|
|
def test_conflicting_defaults(self):
|
|
|
|
"""
|
|
|
|
Raises `ValueError` if attributes with defaults are followed by
|
|
|
|
mandatory attributes.
|
|
|
|
"""
|
|
|
|
class C(object):
|
2015-01-29 21:32:41 +00:00
|
|
|
x = attr(default=None)
|
2015-01-29 19:50:42 +00:00
|
|
|
y = attr()
|
|
|
|
|
|
|
|
with pytest.raises(ValueError) as e:
|
2015-02-08 11:32:32 +00:00
|
|
|
_transform_attrs(C, None)
|
2015-01-29 19:50:42 +00:00
|
|
|
assert (
|
2015-02-07 20:03:17 +00:00
|
|
|
"No mandatory attributes allowed after an attribute with a "
|
2015-01-29 19:50:42 +00:00
|
|
|
"default value or factory. Attribute in question: Attribute"
|
2015-01-30 16:48:34 +00:00
|
|
|
"(name='y', default=NOTHING, validator=None, no_repr=False, "
|
|
|
|
"no_cmp=False, no_hash=False, no_init=False)",
|
2015-01-29 19:50:42 +00:00
|
|
|
) == e.value.args
|
|
|
|
|
2015-02-08 11:32:32 +00:00
|
|
|
def test_these(self):
|
|
|
|
"""
|
|
|
|
If these is passed, use it and ignore body.
|
|
|
|
"""
|
|
|
|
class C(object):
|
|
|
|
y = attr()
|
|
|
|
|
|
|
|
_transform_attrs(C, {"x": attr()})
|
|
|
|
assert [
|
|
|
|
simple_attr("x"),
|
|
|
|
] == C.__attrs_attrs__
|
|
|
|
assert isinstance(C.y, _CountingAttr)
|
|
|
|
|
2015-01-27 16:53:17 +00:00
|
|
|
|
2015-01-29 19:50:42 +00:00
|
|
|
class TestAttributes(object):
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
2015-01-29 18:39:49 +00:00
|
|
|
Tests for the `attributes` class decorator.
|
2015-01-27 16:53:17 +00:00
|
|
|
"""
|
|
|
|
def test_sets_attrs(self):
|
|
|
|
"""
|
|
|
|
Sets the `__attrs_attrs__` class attribute with a list of `Attribute`s.
|
|
|
|
"""
|
2015-01-29 18:39:49 +00:00
|
|
|
@attributes
|
2015-01-27 16:53:17 +00:00
|
|
|
class C(object):
|
2015-01-29 18:39:49 +00:00
|
|
|
x = attr()
|
2015-01-27 16:53:17 +00:00
|
|
|
assert "x" == C.__attrs_attrs__[0].name
|
|
|
|
assert all(isinstance(a, Attribute) for a in C.__attrs_attrs__)
|
|
|
|
|
|
|
|
def test_empty(self):
|
|
|
|
"""
|
|
|
|
No attributes, no problems.
|
|
|
|
"""
|
2015-01-29 18:39:49 +00:00
|
|
|
@attributes
|
2015-01-27 16:53:17 +00:00
|
|
|
class C3(object):
|
|
|
|
pass
|
2015-01-27 22:08:55 +00:00
|
|
|
assert "C3()" == repr(C3())
|
2015-01-27 16:53:17 +00:00
|
|
|
assert C3() == C3()
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
@pytest.mark.parametrize("method_name", [
|
|
|
|
"__repr__",
|
|
|
|
"__eq__",
|
|
|
|
"__hash__",
|
|
|
|
"__init__",
|
|
|
|
])
|
|
|
|
def test_adds_all_by_default(self, method_name):
|
|
|
|
"""
|
|
|
|
If no further arguments are supplied, all add_XXX functions are
|
|
|
|
applied.
|
|
|
|
"""
|
|
|
|
# Set the method name to a sentinel and check whether it has been
|
|
|
|
# overwritten afterwards.
|
|
|
|
sentinel = object()
|
|
|
|
|
|
|
|
class C1(object):
|
2015-01-29 18:39:49 +00:00
|
|
|
x = attr()
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
setattr(C1, method_name, sentinel)
|
|
|
|
|
2015-01-29 18:39:49 +00:00
|
|
|
C1 = attributes(C1)
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
class C2(object):
|
2015-01-29 18:39:49 +00:00
|
|
|
x = attr()
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
setattr(C2, method_name, sentinel)
|
|
|
|
|
2015-01-29 18:39:49 +00:00
|
|
|
C2 = attributes(C2)
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
assert sentinel != getattr(C1, method_name)
|
|
|
|
assert sentinel != getattr(C2, method_name)
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("arg_name, method_name", [
|
2015-02-07 08:15:59 +00:00
|
|
|
("no_repr", "__repr__"),
|
|
|
|
("no_cmp", "__eq__"),
|
|
|
|
("no_hash", "__hash__"),
|
|
|
|
("no_init", "__init__"),
|
2015-01-29 15:24:49 +00:00
|
|
|
])
|
|
|
|
def test_respects_add_arguments(self, arg_name, method_name):
|
|
|
|
"""
|
|
|
|
If a certain `add_XXX` is `True`, XXX is not added to the class.
|
|
|
|
"""
|
|
|
|
# Set the method name to a sentinel and check whether it has been
|
|
|
|
# overwritten afterwards.
|
|
|
|
sentinel = object()
|
|
|
|
|
|
|
|
am_args = {
|
2015-02-07 08:15:59 +00:00
|
|
|
"no_repr": False,
|
|
|
|
"no_cmp": False,
|
|
|
|
"no_hash": False,
|
|
|
|
"no_init": False
|
2015-01-29 15:24:49 +00:00
|
|
|
}
|
2015-02-07 08:15:59 +00:00
|
|
|
am_args[arg_name] = True
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
class C(object):
|
2015-01-29 18:39:49 +00:00
|
|
|
x = attr()
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
setattr(C, method_name, sentinel)
|
|
|
|
|
2015-01-29 18:39:49 +00:00
|
|
|
C = attributes(**am_args)(C)
|
2015-01-29 15:24:49 +00:00
|
|
|
|
|
|
|
assert sentinel == getattr(C, method_name)
|
|
|
|
|
|
|
|
|
|
|
|
class TestAttribute(object):
|
|
|
|
"""
|
|
|
|
Tests for `Attribute`.
|
|
|
|
"""
|
|
|
|
def test_missing_argument(self):
|
|
|
|
"""
|
2015-01-30 12:25:59 +00:00
|
|
|
Raises `TypeError` if an Argument is missing.
|
2015-01-29 15:24:49 +00:00
|
|
|
"""
|
|
|
|
with pytest.raises(TypeError) as e:
|
2015-01-30 12:25:59 +00:00
|
|
|
Attribute(default=NOTHING, validator=None)
|
2015-01-29 15:24:49 +00:00
|
|
|
assert ("Missing argument 'name'.",) == e.value.args
|
2015-01-30 07:57:33 +00:00
|
|
|
|
2015-01-30 12:25:59 +00:00
|
|
|
def test_too_many_arguments(self):
|
|
|
|
"""
|
|
|
|
Raises `TypeError` if extra arguments are passed.
|
|
|
|
"""
|
|
|
|
with pytest.raises(TypeError) as e:
|
|
|
|
Attribute(name="foo", default=NOTHING,
|
2015-01-30 16:48:34 +00:00
|
|
|
factory=NOTHING, validator=None, no_repr=False,
|
|
|
|
no_cmp=False, no_hash=False, no_init=False)
|
2015-01-30 12:25:59 +00:00
|
|
|
assert ("Too many arguments.",) == e.value.args
|
|
|
|
|
2015-01-30 07:57:33 +00:00
|
|
|
|
|
|
|
class TestMakeClass(object):
|
|
|
|
"""
|
|
|
|
Tests for `make_class`.
|
|
|
|
"""
|
|
|
|
@pytest.mark.parametrize("ls", [
|
|
|
|
list,
|
|
|
|
tuple
|
|
|
|
])
|
|
|
|
def test_simple(self, ls):
|
|
|
|
"""
|
|
|
|
Passing a list of strings creates attributes with default args.
|
|
|
|
"""
|
|
|
|
C1 = make_class("C1", ls(["a", "b"]))
|
|
|
|
|
|
|
|
@attributes
|
|
|
|
class C2(object):
|
|
|
|
a = attr()
|
|
|
|
b = attr()
|
|
|
|
|
|
|
|
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_attr_args(self):
|
|
|
|
"""
|
|
|
|
attributes_arguments are passed to attributes
|
|
|
|
"""
|
2015-02-07 08:15:59 +00:00
|
|
|
C = make_class("C", ["x"], no_repr=True)
|
2015-01-30 07:57:33 +00:00
|
|
|
assert repr(C(1)).startswith("<attr._make.C object at 0x")
|
|
|
|
|
|
|
|
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
|
2015-02-09 12:16:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TestFields(object):
|
|
|
|
"""
|
|
|
|
Tests for `fields`.
|
|
|
|
"""
|
|
|
|
def test_instance(self, C):
|
|
|
|
"""
|
|
|
|
Raises `TypeError` on non-classes.
|
|
|
|
"""
|
|
|
|
with pytest.raises(TypeError) as e:
|
|
|
|
fields(C(1, 2))
|
|
|
|
assert "Passed object must be a class." == e.value.args[0]
|
|
|
|
|
|
|
|
def test_handler_non_attrs_class(self, C):
|
|
|
|
"""
|
|
|
|
Raises `ValueError` if passed a non-``attrs`` instance.
|
|
|
|
"""
|
|
|
|
with pytest.raises(ValueError) as e:
|
|
|
|
fields(object)
|
|
|
|
assert (
|
|
|
|
"{o!r} is not an attrs-decorated class.".format(o=object)
|
|
|
|
) == e.value.args[0]
|
|
|
|
|
|
|
|
def test_fields(self, C):
|
|
|
|
"""
|
|
|
|
Returns a list of `Attribute`a.
|
|
|
|
"""
|
|
|
|
assert all(isinstance(a, Attribute) for a in fields(C))
|
|
|
|
|
|
|
|
def test_copies(self, C):
|
|
|
|
"""
|
|
|
|
Returns a new list object with new `Attribute` objects.
|
|
|
|
"""
|
|
|
|
assert C.__attrs_attrs__ is not fields(C)
|
|
|
|
assert all(new == original and new is not original
|
|
|
|
for new, original
|
|
|
|
in zip(fields(C), C.__attrs_attrs__))
|
|
|
|
|
|
|
|
|
|
|
|
class TestValidate(object):
|
|
|
|
"""
|
|
|
|
Tests for `validate`.
|
|
|
|
"""
|
|
|
|
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_propagates(self):
|
|
|
|
"""
|
|
|
|
The exception of the validator is handed through.
|
|
|
|
"""
|
|
|
|
def raiser(_, __, value):
|
|
|
|
if value == 42:
|
|
|
|
raise FloatingPointError
|
|
|
|
|
|
|
|
C = make_class("C", {"x": attr(validator=raiser)})
|
|
|
|
i = C(1)
|
|
|
|
i.x = 42
|
|
|
|
|
|
|
|
with pytest.raises(FloatingPointError):
|
|
|
|
validate(i)
|