/
niceSOFT
/
python3-attrs
Обзор
Документация
Войти
/
niceSOFT
/
python3-attrs
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/test_validators.py
1 404 строки
37 KB
James Beith
Add `ne` validator (#1571)
01 авг 2026, 15:40
Не верифицирован
01 авг 2026, 15:40
5aa76a4
Код
Авторство
О чём код?
# SPDX-License-Identifier: MIT """ Tests for `attr.validators`. """ import re import sys import pytest import attr from attr import _config, fields, has from attr import validators as validator_module from attr.validators import ( _subclass_of, and_, deep_iterable, deep_mapping, ge, gt, in_, instance_of, is_callable, le, lt, matches_re, max_len, min_len, ne, not_, optional, or_, ) from .utils import simple_attr class TestDisableValidators: @pytest.fixture(autouse=True) def _reset_default(self): """ Make sure validators are always enabled after a test. """ yield _config._run_validators = True def test_default(self): """ Run validators by default. """ assert _config._run_validators is True @pytest.mark.parametrize( ("value", "expected"), [(True, False), (False, True)] ) def test_set_validators_disabled(self, value, expected): """ Sets `_run_validators`. """ validator_module.set_disabled(value) assert _config._run_validators is expected @pytest.mark.parametrize( ("value", "expected"), [(True, False), (False, True)] ) def test_disabled(self, value, expected): """ Returns `_run_validators`. """ _config._run_validators = value assert validator_module.get_disabled() is expected def test_disabled_ctx(self): """ The `disabled` context manager disables running validators, but only within its context. """ assert _config._run_validators is True with validator_module.disabled(): assert _config._run_validators is False assert _config._run_validators is True def test_disabled_ctx_with_errors(self): """ Running validators is re-enabled even if an error is raised. """ assert _config._run_validators is True with pytest.raises(ValueError), validator_module.disabled(): assert _config._run_validators is False raise ValueError("haha!") assert _config._run_validators is True def test_disabled_ctx_nested(self): """ Nested contextmanagers restore correct state. """ assert _config._run_validators is True with validator_module.disabled(): assert _config._run_validators is False with validator_module.disabled(): assert _config._run_validators is False assert _config._run_validators is False assert _config._run_validators is True class TestInstanceOf: """ 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 <class 'int'> (got '42' that is a <class 'str'>).", a, int, "42", ) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = instance_of(int) assert ("<instance_of validator for type <class 'int'>>") == repr(v) class TestMatchesRe: """ Tests for `matches_re`. """ def test_in_all(self): """ validator is in ``__all__``. """ assert matches_re.__name__ in validator_module.__all__ def test_match(self): """ Silent on matches, raises ValueError on mismatches. """ @attr.s class ReTester: str_match = attr.ib(validator=matches_re("a|ab")) ReTester("ab") # shouldn't raise exceptions with pytest.raises(TypeError): ReTester(1) with pytest.raises(ValueError): ReTester("1") with pytest.raises(ValueError): ReTester("a1") def test_flags(self): """ Flags are propagated to the match function. """ @attr.s class MatchTester: val = attr.ib(validator=matches_re("a", re.IGNORECASE, re.match)) MatchTester("A1") # test flags and using re.match def test_precompiled_pattern(self): """ Pre-compiled patterns are accepted. """ pattern = re.compile("a") @attr.s class RePatternTester: val = attr.ib(validator=matches_re(pattern)) RePatternTester("a") def test_precompiled_pattern_no_flags(self): """ A pre-compiled pattern cannot be combined with a 'flags' argument. """ pattern = re.compile("") with pytest.raises( TypeError, match="can only be used with a string pattern" ): matches_re(pattern, flags=re.IGNORECASE) def test_different_func(self): """ Changing the match functions works. """ @attr.s class SearchTester: val = attr.ib(validator=matches_re("a", 0, re.search)) SearchTester("bab") # re.search will match def test_catches_invalid_func(self): """ Invalid match functions are caught. """ with pytest.raises(ValueError) as ei: matches_re("a", 0, lambda: None) if sys.version_info >= (3, 15): errmsg = ( "'func' must be one of None, fullmatch, prefixmatch, search." ) else: errmsg = "'func' must be one of None, fullmatch, match, search." assert errmsg == ei.value.args[0] @pytest.mark.parametrize( "func", [None, getattr(re, "fullmatch", None), re.match, re.search] ) def test_accepts_all_valid_func(self, func): """ Every valid match function is accepted. """ matches_re("a", func=func) def test_repr(self): """ __repr__ is meaningful. """ assert repr(matches_re("a")).startswith( "<matches_re validator for pattern" ) def always_pass(_, __, ___): """ Toy validator that always passes. """ def always_fail(_, __, ___): """ Toy validator that always fails. """ 0 / 0 class TestAnd: 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: 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 @pytest.mark.parametrize( "validator", [ instance_of(int), [always_pass, instance_of(int)], (always_pass, instance_of(int)), ], ) class TestOptional: """ 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 <class 'int'> (got '42' that is a <class 'str'>).", 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 = ( f"<optional validator for _AndValidator(_validators=[{always_pass!r}, " "<instance_of validator for type <class 'int'>>]) or None>" ) elif isinstance(validator, tuple): repr_s = ( f"<optional validator for _AndValidator(_validators=({always_pass!r}, " "<instance_of validator for type <class 'int'>>)) or None>" ) else: repr_s = ( "<optional validator for <instance_of validator for type " "<class 'int'>> or None>" ) assert repr_s == repr(v) class TestIn_: """ 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)", a, [1, 2, 3], 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)", a, "abc", None, ) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = in_([3, 4, 5]) assert ("<in_ validator with options [3, 4, 5]>") == repr(v) def test_is_hashable(self): """ `in_` is hashable, so fields using it can be used with the include and exclude filters. """ @attr.s class C: x: int = attr.ib(validator=attr.validators.in_({1, 2})) i = C(2) attr.asdict(i, filter=attr.filters.include(lambda val: True)) attr.asdict(i, filter=attr.filters.exclude(lambda val: True)) @pytest.fixture( name="member_validator", params=( instance_of(int), [always_pass, instance_of(int)], (always_pass, instance_of(int)), ), scope="module", ) def _member_validator(request): """ Provides sample `member_validator`s for some tests in `TestDeepIterable` """ return request.param class TestDeepIterable: """ 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, member_validator): """ If the member validator succeeds and the iterable validator is not set, nothing happens. """ v = deep_iterable(member_validator) a = simple_attr("test") v(None, a, [42]) def test_success_member_and_iterable(self, member_validator): """ If both the member and iterable validators succeed, nothing happens. """ 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), ([instance_of(int), 42], 42), ([42, instance_of(int)], 42), ], ) def test_noncallable_validators( self, member_validator, iterable_validator ): """ Raise `TypeError` if any validators are not callable. """ with pytest.raises(TypeError) as e: deep_iterable(member_validator, iterable_validator) value = 42 message = ( f"must be callable (got {value} that is a {value.__class__})." ) assert message in e.value.args[0] assert value == e.value.args[1] assert message in e.value.msg assert value == e.value.value def test_fail_invalid_member(self, member_validator): """ Raise member validator error if an invalid member is found. """ v = deep_iterable(member_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, [42, "42"]) def test_fail_invalid_iterable(self, member_validator): """ 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, member_validator): """ Raise iterable validator error if both the iterable and a member are invalid. """ 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 = "<instance_of validator for type <class 'int'>>" v = deep_iterable(member_validator) expected_repr = ( f"<deep_iterable validator for iterables of {member_repr}>" ) assert expected_repr == repr(v) def test_repr_member_only_sequence(self): """ Returned validator has a useful `__repr__` when only member validator is set and the member validator is a list of validators """ member_validator = [always_pass, instance_of(int)] member_repr = ( f"_AndValidator(_validators=({always_pass!r}, " "<instance_of validator for type <class 'int'>>))" ) v = deep_iterable(member_validator) expected_repr = ( f"<deep_iterable validator for iterables of {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 = "<instance_of validator for type <class 'int'>>" iterable_validator = instance_of(list) iterable_repr = "<instance_of validator for type <class 'list'>>" v = deep_iterable(member_validator, iterable_validator) expected_repr = ( "<deep_iterable validator for" f" {iterable_repr} iterables of {member_repr}>" ) assert expected_repr == repr(v) def test_repr_sequence_member_and_iterable(self): """ Returned validator has a useful `__repr__` when both member and iterable validators are set and the member validator is a list of validators """ member_validator = [always_pass, instance_of(int)] member_repr = ( f"_AndValidator(_validators=({always_pass!r}, " "<instance_of validator for type <class 'int'>>))" ) iterable_validator = instance_of(list) iterable_repr = "<instance_of validator for type <class 'list'>>" v = deep_iterable(member_validator, iterable_validator) expected_repr = ( "<deep_iterable validator for" f" {iterable_repr} iterables of {member_repr}>" ) assert expected_repr == repr(v) @pytest.mark.parametrize("conv", [list, tuple]) def test_validators_iterables(self, conv): """ If iterables are passed as validators, they are combined with and_. """ member_validator = (instance_of(int),) iterable_validator = (instance_of(list), min_len(1)) v = deep_iterable(conv(member_validator), conv(iterable_validator)) assert and_(*member_validator) == v.member_validator assert and_(*iterable_validator) == v.iterable_validator class TestDeepMapping: """ 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), (42, None, None), (None, 42, None), ], ) def test_noncallable_validators( self, key_validator, value_validator, mapping_validator ): """ Raise `TypeError` if any validators are not callable. """ with pytest.raises(TypeError) as e: deep_mapping(key_validator, value_validator, mapping_validator) value = 42 message = ( f"must be callable (got {value} that is a {value.__class__})." ) assert message in e.value.args[0] assert value == e.value.args[1] assert message in e.value.msg assert value == e.value.value def test_fail_invalid_mapping(self): """ Raise `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 = "<instance_of validator for type <class 'str'>>" value_validator = instance_of(int) value_repr = "<instance_of validator for type <class 'int'>>" v = deep_mapping(key_validator, value_validator) expected_repr = ( "<deep_mapping validator for objects mapping " f"{key_repr} to {value_repr}>" ) assert expected_repr == repr(v) def test_error_neither_validator_provided(self): """ Raise ValueError if neither key_validator nor value_validator is provided. """ with pytest.raises(ValueError) as e: deep_mapping() assert ( "At least one of key_validator or value_validator must be provided" == e.value.args[0] ) def test_key_validator_can_be_none(self): """ The key validator can be None. """ v = deep_mapping(value_validator=instance_of(int)) a = simple_attr("test") v(None, a, {"a": 6, "b": 7}) def test_value_validator_can_be_none(self): """ The value validator can be None. """ v = deep_mapping(key_validator=instance_of(str)) a = simple_attr("test") v(None, a, {"a": 6, "b": 7}) @pytest.mark.parametrize("conv", [list, tuple]) def test_validators_iterables(self, conv): """ If iterables are passed as validators, they are combined with and_. """ key_validator = (instance_of(str), min_len(2)) value_validator = (instance_of(int), ge(10)) mapping_validator = (instance_of(dict), max_len(2)) v = deep_mapping( conv(key_validator), conv(value_validator), conv(mapping_validator), ) assert and_(*key_validator) == v.key_validator assert and_(*value_validator) == v.value_validator assert and_(*mapping_validator) == v.mapping_validator class TestIsCallable: """ 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) value = None message = "'test' must be callable (got {value} that is a {type_})." expected_message = message.format(value=value, type_=value.__class__) assert expected_message == e.value.args[0] assert value == e.value.args[1] assert expected_message == e.value.msg assert value == e.value.value def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = is_callable() assert "<is_callable validator>" == repr(v) def test_exception_repr(self): """ Verify that NotCallableError exception has a useful `__str__`. """ from attr.exceptions import NotCallableError instance = NotCallableError(msg="Some Message", value=42) assert "Some Message" == str(instance) 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__ class TestLtLeGeGtNe: """ Tests for `Lt, Le, Ge, Gt, Ne`. """ BOUND = 4 def test_in_all(self): """ validator is in ``__all__``. """ assert all( f.__name__ in validator_module.__all__ for f in [lt, le, ge, gt, ne] ) @pytest.mark.parametrize("v", [lt, le, ge, gt, ne]) def test_retrieve_bound(self, v): """ The configured bound for the comparison can be extracted from the Attribute. """ @attr.s class Tester: value = attr.ib(validator=v(self.BOUND)) assert fields(Tester).value.validator.bound == self.BOUND @pytest.mark.parametrize( ("v", "value"), [ (ne, 3), (lt, 3), (le, 3), (le, 4), (ge, 4), (ge, 5), (gt, 5), (ne, 5), ], ) def test_check_valid(self, v, value): """Silent if value {op} bound.""" @attr.s class Tester: value = attr.ib(validator=v(self.BOUND)) Tester(value) # shouldn't raise exceptions @pytest.mark.parametrize( ("v", "value"), [ (lt, 4), (le, 5), (ge, 3), (gt, 4), (ne, 4), ], ) def test_check_invalid(self, v, value): """Raise ValueError if value {op} bound.""" @attr.s class Tester: value = attr.ib(validator=v(self.BOUND)) with pytest.raises(ValueError): Tester(value) @pytest.mark.parametrize("v", [lt, le, ge, gt, ne]) def test_repr(self, v): """ __repr__ is meaningful. """ nv = v(23) assert repr(nv) == f"<Validator for x {nv.compare_op} {23}>" class TestMaxLen: """ Tests for `max_len`. """ MAX_LENGTH = 4 def test_in_all(self): """ validator is in ``__all__``. """ assert max_len.__name__ in validator_module.__all__ def test_retrieve_max_len(self): """ The configured max. length can be extracted from the Attribute """ @attr.s class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) assert fields(Tester).value.validator.max_length == self.MAX_LENGTH @pytest.mark.parametrize( "value", [ "", "foo", "spam", [], list(range(MAX_LENGTH)), {"spam": 3, "eggs": 4}, ], ) def test_check_valid(self, value): """ Silent if len(value) <= max_len. Values can be strings and other iterables. """ @attr.s class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) Tester(value) # shouldn't raise exceptions @pytest.mark.parametrize( "value", [ "bacon", list(range(6)), ], ) def test_check_invalid(self, value): """ Raise ValueError if len(value) > max_len. """ @attr.s class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) with pytest.raises(ValueError): Tester(value) def test_repr(self): """ __repr__ is meaningful. """ assert repr(max_len(23)) == "<max_len validator for 23>" class TestMinLen: """ Tests for `min_len`. """ MIN_LENGTH = 2 def test_in_all(self): """ validator is in ``__all__``. """ assert min_len.__name__ in validator_module.__all__ def test_retrieve_min_len(self): """ The configured min. length can be extracted from the Attribute """ @attr.s class Tester: value = attr.ib(validator=min_len(self.MIN_LENGTH)) assert fields(Tester).value.validator.min_length == self.MIN_LENGTH @pytest.mark.parametrize( "value", [ "foo", "spam", list(range(MIN_LENGTH)), {"spam": 3, "eggs": 4}, ], ) def test_check_valid(self, value): """ Silent if len(value) => min_len. Values can be strings and other iterables. """ @attr.s class Tester: value = attr.ib(validator=min_len(self.MIN_LENGTH)) Tester(value) # shouldn't raise exceptions @pytest.mark.parametrize( "value", [ "", list(range(1)), ], ) def test_check_invalid(self, value): """ Raise ValueError if len(value) < min_len. """ @attr.s class Tester: value = attr.ib(validator=min_len(self.MIN_LENGTH)) with pytest.raises(ValueError): Tester(value) def test_repr(self): """ __repr__ is meaningful. """ assert repr(min_len(23)) == "<min_len validator for 23>" class TestSubclassOf: """ Tests for `_subclass_of`. """ def test_success(self): """ Nothing happens if classes match. """ v = _subclass_of(int) v(None, simple_attr("test"), int) def test_subclass(self): """ Subclasses are accepted too. """ v = _subclass_of(int) # yep, bools are a subclass of int :( v(None, simple_attr("test"), bool) def test_fail(self): """ Raises `TypeError` on wrong types. """ v = _subclass_of(int) a = simple_attr("test") with pytest.raises(TypeError) as e: v(None, a, str) assert ( "'test' must be a subclass of <class 'int'> (got <class 'str'>).", a, int, str, ) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = _subclass_of(int) assert ("<subclass_of validator for type <class 'int'>>") == repr(v) class TestNot_: """ Tests for `not_`. """ DEFAULT_EXC_TYPES = (ValueError, TypeError) def test_not_all(self): """ The validator is in ``__all__``. """ assert not_.__name__ in validator_module.__all__ def test_repr(self): """ Returned validator has a useful `__repr__`. """ wrapped = in_([3, 4, 5]) v = not_(wrapped) assert ( f"<not_ validator wrapping {wrapped!r}, capturing {v.exc_types!r}>" ) == repr(v) def test_success_because_fails(self): """ If the wrapped validator fails, we're happy. """ def always_fails(inst, attr, value): raise ValueError("always fails") v = not_(always_fails) a = simple_attr("test") input_value = 3 v(1, a, input_value) def test_fails_because_success(self): """ If the wrapped validator doesn't fail, not_ should fail. """ def always_passes(inst, attr, value): pass v = not_(always_passes) a = simple_attr("test") input_value = 3 with pytest.raises(ValueError) as e: v(1, a, input_value) assert ( ( f"not_ validator child '{always_passes!r}' did not raise a captured error" ), a, always_passes, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_composable_with_in_pass(self): """ Check something is ``not in`` something else. """ v = not_(in_("abc")) a = simple_attr("test") input_value = "d" v(None, a, input_value) def test_composable_with_in_fail(self): """ Check something is ``not in`` something else, but it is, so fail. """ wrapped = in_("abc") v = not_(wrapped) a = simple_attr("test") input_value = "b" with pytest.raises(ValueError) as e: v(None, a, input_value) assert ( ( "not_ validator child '{!r}' did not raise a captured error" ).format(in_("abc")), a, wrapped, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_composable_with_matches_re_pass(self): """ Check something does not match a regex. """ v = not_(matches_re("[a-z]{3}")) a = simple_attr("test") input_value = "spam" v(None, a, input_value) def test_composable_with_matches_re_fail(self): """ Check something does not match a regex, but it does, so fail. """ wrapped = matches_re("[a-z]{3}") v = not_(wrapped) a = simple_attr("test") input_value = "egg" with pytest.raises(ValueError) as e: v(None, a, input_value) assert ( ( f"not_ validator child '{wrapped!r}' did not raise a captured error" ), a, wrapped, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_composable_with_instance_of_pass(self): """ Check something is not a type. This validator raises a TypeError, rather than a ValueError like the others. """ v = not_(instance_of((int, float))) a = simple_attr("test") v(None, a, "spam") def test_composable_with_instance_of_fail(self): """ Check something is not a type, but it is, so fail. """ wrapped = instance_of((int, float)) v = not_(wrapped) a = simple_attr("test") input_value = 2.718281828 with pytest.raises(ValueError) as e: v(None, a, input_value) assert ( ( f"not_ validator child '{instance_of((int, float))!r}' did not raise a captured error" ), a, wrapped, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_custom_capture_match(self): """ Match a custom exception provided to `not_` """ v = not_(in_("abc"), exc_types=ValueError) a = simple_attr("test") v(None, a, "d") def test_custom_capture_miss(self): """ If the exception doesn't match, the underlying raise comes through """ class MyError(Exception): """:(""" wrapped = in_("abc") v = not_(wrapped, exc_types=MyError) a = simple_attr("test") input_value = "d" with pytest.raises(ValueError) as e: v(None, a, input_value) # get the underlying exception to compare with pytest.raises(Exception) as e_from_wrapped: wrapped(None, a, input_value) assert e_from_wrapped.value.args == e.value.args def test_custom_msg(self): """ If provided, use the custom message in the raised error """ custom_msg = "custom message!" wrapped = in_("abc") v = not_(wrapped, msg=custom_msg) a = simple_attr("test") input_value = "a" with pytest.raises(ValueError) as e: v(None, a, input_value) assert ( custom_msg, a, wrapped, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_bad_exception_args(self): """ Malformed exception arguments """ wrapped = in_("abc") with pytest.raises(TypeError) as e: not_(wrapped, exc_types=(str, int)) assert ( "'exc_types' must be a subclass of <class 'Exception'> " "(got <class 'str'>)." ) == e.value.args[0] class TestOr: def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert or_.__name__ in validator_module.__all__ def test_success(self): """ Succeeds if at least one of wrapped validators succeed. """ v = or_(instance_of(str), always_pass) v(None, simple_attr("test"), 42) def test_fail(self): """ Fails if all wrapped validators fail. """ v = or_(instance_of(str), always_fail) with pytest.raises(ValueError): v(None, simple_attr("test"), 42) def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = or_(instance_of(int), instance_of(str)) assert ( "<or validator wrapping (<instance_of validator for type " "<class 'int'>>, <instance_of validator for type <class 'str'>>)>" ) == repr(v)