""" Tests for `attr.validators`. """ from __future__ import absolute_import, division, print_function import pytest import zope.interface import attr from attr import has from attr import validators as validator_module from attr._compat import TYPE from attr.validators import ( and_, deep_iterable, deep_mapping, in_, instance_of, is_callable, optional, provides, ) from .utils import simple_attr class TestInstanceOf(object): """ Tests for `instance_of`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert instance_of.__name__ in validator_module.__all__ def test_success(self): """ Nothing happens if types match. """ v = instance_of(int) v(None, simple_attr("test"), 42) def test_subclass(self): """ Subclasses are accepted too. """ v = instance_of(int) # yep, bools are a subclass of int :( v(None, simple_attr("test"), True) def test_fail(self): """ Raises `TypeError` on wrong types. """ v = instance_of(int) a = simple_attr("test") with pytest.raises(TypeError) as e: v(None, a, "42") assert ( "'test' must be <{type} 'int'> (got '42' that is a <{type} " "'str'>).".format(type=TYPE), a, int, "42", ) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = instance_of(int) assert ( ">".format(type=TYPE) ) == repr(v) def always_pass(_, __, ___): """ Toy validator that always passses. """ def always_fail(_, __, ___): """ Toy validator that always fails. """ 0 / 0 class TestAnd(object): def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert and_.__name__ in validator_module.__all__ def test_success(self): """ Succeeds if all wrapped validators succeed. """ v = and_(instance_of(int), always_pass) v(None, simple_attr("test"), 42) def test_fail(self): """ Fails if any wrapped validator fails. """ v = and_(instance_of(int), always_fail) with pytest.raises(ZeroDivisionError): v(None, simple_attr("test"), 42) def test_sugar(self): """ `and_(v1, v2, v3)` and `[v1, v2, v3]` are equivalent. """ @attr.s class C(object): a1 = attr.ib("a1", validator=and_(instance_of(int))) a2 = attr.ib("a2", validator=[instance_of(int)]) assert C.__attrs_attrs__[0].validator == C.__attrs_attrs__[1].validator class IFoo(zope.interface.Interface): """ An interface. """ def f(): """ A function called f. """ class TestProvides(object): """ Tests for `provides`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert provides.__name__ in validator_module.__all__ def test_success(self): """ Nothing happens if value provides requested interface. """ @zope.interface.implementer(IFoo) class C(object): def f(self): pass v = provides(IFoo) v(None, simple_attr("x"), C()) def test_fail(self): """ Raises `TypeError` if interfaces isn't provided by value. """ value = object() a = simple_attr("x") v = provides(IFoo) with pytest.raises(TypeError) as e: v(None, a, value) assert ( "'x' must provide {interface!r} which {value!r} doesn't.".format( interface=IFoo, value=value ), a, IFoo, value, ) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = provides(IFoo) assert ( "".format( interface=IFoo ) ) == repr(v) @pytest.mark.parametrize( "validator", [instance_of(int), [always_pass, instance_of(int)]] ) class TestOptional(object): """ Tests for `optional`. """ def test_in_all(self, validator): """ Verify that this validator is in ``__all__``. """ assert optional.__name__ in validator_module.__all__ def test_success(self, validator): """ Nothing happens if validator succeeds. """ v = optional(validator) v(None, simple_attr("test"), 42) def test_success_with_none(self, validator): """ Nothing happens if None. """ v = optional(validator) v(None, simple_attr("test"), None) def test_fail(self, validator): """ Raises `TypeError` on wrong types. """ v = optional(validator) a = simple_attr("test") with pytest.raises(TypeError) as e: v(None, a, "42") assert ( "'test' must be <{type} 'int'> (got '42' that is a <{type} " "'str'>).".format(type=TYPE), a, int, "42", ) == e.value.args def test_repr(self, validator): """ Returned validator has a useful `__repr__`. """ v = optional(validator) if isinstance(validator, list): repr_s = ( ">]) or None>" ).format(func=repr(always_pass), type=TYPE) else: repr_s = ( "> or None>" ).format(type=TYPE) assert repr_s == repr(v) class TestIn_(object): """ Tests for `in_`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert in_.__name__ in validator_module.__all__ def test_success_with_value(self): """ If the value is in our options, nothing happens. """ v = in_([1, 2, 3]) a = simple_attr("test") v(1, a, 3) def test_fail(self): """ Raise ValueError if the value is outside our options. """ v = in_([1, 2, 3]) a = simple_attr("test") with pytest.raises(ValueError) as e: v(None, a, None) assert ("'test' must be in [1, 2, 3] (got None)",) == e.value.args def test_fail_with_string(self): """ Raise ValueError if the value is outside our options when the options are specified as a string and the value is not a string. """ v = in_("abc") a = simple_attr("test") with pytest.raises(ValueError) as e: v(None, a, None) assert ("'test' must be in 'abc' (got None)",) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = in_([3, 4, 5]) assert (("")) == repr(v) class TestDeepIterable(object): """ Tests for `deep_iterable`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert deep_iterable.__name__ in validator_module.__all__ def test_success_member_only(self): """ If the member validator succeeds and the iterable validator is not set, nothing happens. """ member_validator = instance_of(int) v = deep_iterable(member_validator) a = simple_attr("test") v(None, a, [42]) def test_success_member_and_iterable(self): """ If both the member and iterable validators succeed, nothing happens. """ member_validator = instance_of(int) iterable_validator = instance_of(list) v = deep_iterable(member_validator, iterable_validator) a = simple_attr("test") v(None, a, [42]) @pytest.mark.parametrize( "member_validator, iterable_validator", ( (instance_of(int), 42), (42, instance_of(list)), (42, 42), (42, None), ), ) def test_noncallable_validators( self, member_validator, iterable_validator ): """ Raise :class:`TypeError` if any validators are not callable. """ with pytest.raises(TypeError) as e: deep_iterable(member_validator, iterable_validator) e.match(r"\w* must be callable") def test_fail_invalid_member(self): """ Raise member validator error if an invalid member is found. """ member_validator = instance_of(int) v = deep_iterable(member_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, [42, "42"]) def test_fail_invalid_iterable(self): """ Raise iterable validator error if an invalid iterable is found. """ member_validator = instance_of(int) iterable_validator = instance_of(tuple) v = deep_iterable(member_validator, iterable_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, [42]) def test_fail_invalid_member_and_iterable(self): """ Raise iterable validator error if both the iterable and a member are invalid. """ member_validator = instance_of(int) iterable_validator = instance_of(tuple) v = deep_iterable(member_validator, iterable_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, [42, "42"]) def test_repr_member_only(self): """ Returned validator has a useful `__repr__` when only member validator is set. """ member_validator = instance_of(int) member_repr = ">".format( type=TYPE ) v = deep_iterable(member_validator) expected_repr = ( "" ).format(member_repr=member_repr) assert ((expected_repr)) == repr(v) def test_repr_member_and_iterable(self): """ Returned validator has a useful `__repr__` when both member and iterable validators are set. """ member_validator = instance_of(int) member_repr = ">".format( type=TYPE ) iterable_validator = instance_of(list) iterable_repr = ( ">" ).format(type=TYPE) v = deep_iterable(member_validator, iterable_validator) expected_repr = ( "" ).format(iterable_repr=iterable_repr, member_repr=member_repr) assert expected_repr == repr(v) class TestDeepMapping(object): """ Tests for `deep_mapping`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert deep_mapping.__name__ in validator_module.__all__ def test_success(self): """ If both the key and value validators succeed, nothing happens. """ key_validator = instance_of(str) value_validator = instance_of(int) v = deep_mapping(key_validator, value_validator) a = simple_attr("test") v(None, a, {"a": 6, "b": 7}) @pytest.mark.parametrize( "key_validator, value_validator, mapping_validator", ( (42, instance_of(int), None), (instance_of(str), 42, None), (instance_of(str), instance_of(int), 42), (42, 42, None), (42, 42, 42), ), ) def test_noncallable_validators( self, key_validator, value_validator, mapping_validator ): """ Raise :class:`TypeError` if any validators are not callable. """ with pytest.raises(TypeError) as e: deep_mapping(key_validator, value_validator, mapping_validator) e.match(r"\w* must be callable") def test_fail_invalid_mapping(self): """ Raise :class:`TypeError` if mapping validator fails. """ key_validator = instance_of(str) value_validator = instance_of(int) mapping_validator = instance_of(dict) v = deep_mapping(key_validator, value_validator, mapping_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, None) def test_fail_invalid_key(self): """ Raise key validator error if an invalid key is found. """ key_validator = instance_of(str) value_validator = instance_of(int) v = deep_mapping(key_validator, value_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, {"a": 6, 42: 7}) def test_fail_invalid_member(self): """ Raise key validator error if an invalid member value is found. """ key_validator = instance_of(str) value_validator = instance_of(int) v = deep_mapping(key_validator, value_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, {"a": "6", "b": 7}) def test_repr(self): """ Returned validator has a useful `__repr__`. """ key_validator = instance_of(str) key_repr = ">".format( type=TYPE ) value_validator = instance_of(int) value_repr = ">".format( type=TYPE ) v = deep_mapping(key_validator, value_validator) expected_repr = ( "" ).format(key_repr=key_repr, value_repr=value_repr) assert expected_repr == repr(v) class TestIsCallable(object): """ Tests for `is_callable`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert is_callable.__name__ in validator_module.__all__ def test_success(self): """ If the value is callable, nothing happens. """ v = is_callable() a = simple_attr("test") v(None, a, isinstance) def test_fail(self): """ Raise TypeError if the value is not callable. """ v = is_callable() a = simple_attr("test") with pytest.raises(TypeError) as e: v(None, a, None) e.match("'test' must be callable") def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = is_callable() assert "" == repr(v) def test_hashability(): """ Validator classes are hashable. """ for obj_name in dir(validator_module): obj = getattr(validator_module, obj_name) if not has(obj): continue hash_func = getattr(obj, "__hash__", None) assert hash_func is not None assert hash_func is not object.__hash__