attrs/tests/test_funcs.py

232 lines
5.0 KiB
Python
Raw Normal View History

2015-01-27 16:53:17 +00:00
# -*- coding: utf-8 -*-
2015-01-29 11:20:17 +00:00
"""
Tests for `attr._funcs`.
"""
2015-01-27 16:53:17 +00:00
from __future__ import absolute_import, division, print_function
import pytest
2015-01-29 19:50:42 +00:00
from attr._funcs import (
asdict,
2015-01-29 19:50:42 +00:00
assoc,
2015-01-29 20:55:25 +00:00
fields,
2015-01-29 19:50:42 +00:00
has,
validate,
2015-01-29 19:50:42 +00:00
)
2015-01-27 16:53:17 +00:00
from attr._make import (
Attribute,
attr,
attributes,
2015-02-02 11:13:11 +00:00
make_class,
2015-01-27 16:53:17 +00:00
)
@attributes
2015-01-27 16:53:17 +00:00
class C(object):
x = attr()
y = attr()
2015-01-27 16:53:17 +00:00
2015-01-29 20:55:25 +00:00
class TestFields(object):
2015-01-28 10:15:40 +00:00
"""
2015-01-29 20:55:25 +00:00
Tests for `fields`.
2015-01-28 10:15:40 +00:00
"""
2015-01-27 16:53:17 +00:00
def test_instance(self):
"""
Raises `TypeError` on non-classes.
2015-01-27 16:53:17 +00:00
"""
with pytest.raises(TypeError) as e:
2015-01-29 20:55:25 +00:00
fields(C(1, 2))
assert "Passed object must be a class." == e.value.args[0]
2015-01-27 16:53:17 +00:00
def test_handler_non_attrs_class(self):
"""
Raises `ValueError` if passed a non-``attrs`` instance.
2015-01-27 16:53:17 +00:00
"""
with pytest.raises(ValueError) as e:
2015-01-29 20:55:25 +00:00
fields(object)
2015-01-27 16:53:17 +00:00
assert (
"{o!r} is not an attrs-decorated class.".format(o=object)
) == e.value.args[0]
2015-01-29 20:55:25 +00:00
def test_fields(self):
2015-01-27 16:53:17 +00:00
"""
Returns a list of `Attribute`a.
2015-01-27 16:53:17 +00:00
"""
2015-01-29 20:55:25 +00:00
assert all(isinstance(a, Attribute) for a in fields(C))
2015-01-27 16:53:17 +00:00
def test_copies(self):
"""
Returns a new list object with new `Attribute` objects.
"""
2015-01-29 20:55:25 +00:00
assert C.__attrs_attrs__ is not fields(C)
assert all(new == original and new is not original
for new, original
2015-01-29 20:55:25 +00:00
in zip(fields(C), C.__attrs_attrs__))
2015-01-27 16:53:17 +00:00
class TestAsDict(object):
2015-01-28 10:15:40 +00:00
"""
Tests for `asdict`.
2015-01-28 10:15:40 +00:00
"""
2015-01-27 16:53:17 +00:00
def test_shallow(self):
"""
Shallow asdict returns correct dict.
2015-01-27 16:53:17 +00:00
"""
assert {
"x": 1,
"y": 2,
} == asdict(C(x=1, y=2), False)
2015-01-27 16:53:17 +00:00
def test_recurse(self):
"""
Deep asdict returns correct dict.
2015-01-27 16:53:17 +00:00
"""
assert {
"x": {"x": 1, "y": 2},
"y": {"x": 3, "y": 4},
} == asdict(C(
2015-01-27 16:53:17 +00:00
C(1, 2),
C(3, 4),
))
2015-01-28 10:15:40 +00:00
def test_skip(self):
"""
Attributes that are supposed to be skipped are skipped.
"""
assert {
"x": {"x": 1},
} == asdict(C(
C(1, 2),
C(3, 4),
), skip=lambda a, v: a.name == "y")
@pytest.mark.parametrize("container", [
list,
tuple,
])
def test_lists_tuples(self, container):
"""
If recurse is True, also recurse into lists.
"""
assert {
"x": 1,
"y": [{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"],
} == asdict(C(1, container([C(2, 3), C(4, 5), "a"])))
def test_dicts(self):
"""
If recurse is True, also recurse into dicts.
"""
assert {
"x": 1,
"y": {"a": {"x": 4, "y": 5}},
} == asdict(C(1, {"a": C(4, 5)}))
2015-01-28 10:15:40 +00:00
class TestHas(object):
"""
Tests for `has`.
"""
def test_positive(self):
"""
Returns `True` on decorated classes.
"""
assert has(C)
def test_positive_empty(self):
"""
Returns `True` on decorated classes even if there are no attributes.
"""
@attributes
2015-01-28 10:15:40 +00:00
class D(object):
pass
assert has(D)
def test_negative(self):
"""
Returns `False` on non-decorated classes.
"""
assert not has(object)
2015-01-29 19:50:42 +00:00
class TestAssoc(object):
"""
Tests for `assoc`.
"""
def test_empty(self):
"""
Empty classes without changes get copied.
"""
@attributes
class C(object):
pass
i1 = C()
i2 = assoc(i1)
assert i1 is not i2
assert i1 == i2
def test_no_changes(self):
"""
No changes means a verbatim copy.
"""
i1 = C(1, 2)
i2 = assoc(i1)
assert i1 is not i2
assert i1 == i2
def test_change(self):
"""
Changes work.
"""
i = assoc(C(1, 2), x=42)
assert C(42, 2) == i
def test_unknown(self):
"""
Wanting to change an unknown attribute raises a ValueError.
"""
@attributes
class C(object):
x = attr()
y = 42
with pytest.raises(ValueError) as e:
assoc(C(1), y=2)
assert (
"y is not an attrs attribute on {cl!r}.".format(cl=C),
) == e.value.args
2015-02-02 11:13:11 +00:00
class TestValidate(object):
2015-02-02 11:13:11 +00:00
"""
Tests for `validate`.
2015-02-02 11:13:11 +00:00
"""
def test_success(self):
"""
If the validator suceeds, nothing gets raised.
"""
C = make_class("C", {"x": attr(validator=lambda _, __: None)})
validate(C(1))
2015-02-02 11:13:11 +00:00
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)