"""Tests for marshmallow.validate""" import re import pytest from marshmallow import ValidationError, validate @pytest.mark.parametrize( "valid_url", [ "http://example.org", "https://example.org", "ftp://example.org", "ftps://example.org", "http://example.co.jp", "http://www.example.com/a%C2%B1b", "http://www.example.com/~username/", "http://info.example.com/?fred", "http://xn--mgbh0fb.xn--kgbechtv/", "http://example.com/blue/red%3Fand+green", "http://www.example.com/?array%5Bkey%5D=value", "http://xn--rsum-bpad.example.org/", "http://123.45.67.8/", "http://123.45.67.8:8329/", "http://[2001:db8::ff00:42]:8329", "http://[2001::1]:8329", "http://www.example.com:8000/foo", "http://user@example.com", "http://user:pass@example.com", "http://:pass@example.com", "http://@example.com", "http://AZaz09-._~%2A!$&'()*+,;=:@example.com", ], ) def test_url_absolute_valid(valid_url): validator = validate.URL(relative=False) assert validator(valid_url) == valid_url @pytest.mark.parametrize( "invalid_url", [ "http:///example.com/", "https:///example.com/", "https://example.org\\", "https://example.org\n", "ftp:///example.com/", "ftps:///example.com/", "http//example.org", "http:///", "http:/example.org", "foo://example.org", "../icons/logo.gif", "http://2001:db8::ff00:42:8329", "http://[192.168.1.1]:8329", "abc", "..", "/", " ", "", None, "http://user@pass@example.com", "http://@pass@example.com", "http://@@example.com", "http://^@example.com", "http://%0G@example.com", "http://%@example.com", ], ) def test_url_absolute_invalid(invalid_url): validator = validate.URL(relative=False) with pytest.raises(ValidationError): validator(invalid_url) @pytest.mark.parametrize( "valid_url", [ "http://example.org", "http://123.45.67.8/", "http://example.com/foo/bar/../baz", "https://example.com/../icons/logo.gif", "http://example.com/./icons/logo.gif", "ftp://example.com/../../../../g", "http://example.com/g?y/./x", "/foo/bar", "/foo?bar", "/foo?bar#baz", ], ) def test_url_relative_valid(valid_url): validator = validate.URL(relative=True) assert validator(valid_url) == valid_url @pytest.mark.parametrize( # noqa: W605 "invalid_url", [ "http//example.org", "http://example.org\n", "suppliers.html", "../icons/logo.gif", "icons/logo.gif", "../.../g", "...", "\\", " ", "", None, ], ) def test_url_relative_invalid(invalid_url): validator = validate.URL(relative=True) with pytest.raises(ValidationError): validator(invalid_url) @pytest.mark.parametrize( "valid_url", [ "/foo/bar", "/foo?bar", "?bar", "/foo?bar#baz", ], ) def test_url_relative_only_valid(valid_url): validator = validate.URL(relative=True, absolute=False) assert validator(valid_url) == valid_url @pytest.mark.parametrize( "invalid_url", [ "http//example.org", "http://example.org\n", "suppliers.html", "../icons/logo.gif", "icons/logo.gif", "../.../g", "...", "\\", " ", "", "http://example.org", "http://123.45.67.8/", "http://example.com/foo/bar/../baz", "https://example.com/../icons/logo.gif", "http://example.com/./icons/logo.gif", "ftp://example.com/../../../../g", "http://example.com/g?y/./x", ], ) def test_url_relative_only_invalid(invalid_url): validator = validate.URL(relative=True, absolute=False) with pytest.raises(ValidationError): validator(invalid_url) @pytest.mark.parametrize( "valid_url", [ "http://example.org", "http://123.45.67.8/", "http://example", "http://example.", "http://example:80", "http://user.name:pass.word@example", "http://example/foo/bar", ], ) def test_url_dont_require_tld_valid(valid_url): validator = validate.URL(require_tld=False) assert validator(valid_url) == valid_url @pytest.mark.parametrize( "invalid_url", [ "http//example", "http://example\n", "http://.example.org", "http:///foo/bar", "http:// /foo/bar", "", None, ], ) def test_url_dont_require_tld_invalid(invalid_url): validator = validate.URL(require_tld=False) with pytest.raises(ValidationError): validator(invalid_url) def test_url_custom_scheme(): validator = validate.URL() # By default, ws not allowed url = "ws://test.test" with pytest.raises(ValidationError): validator(url) validator = validate.URL(schemes={"http", "https", "ws"}) assert validator(url) == url def test_url_relative_and_custom_schemes(): validator = validate.URL(relative=True) # By default, ws not allowed url = "ws://test.test" with pytest.raises(ValidationError): validator(url) validator = validate.URL(relative=True, schemes={"http", "https", "ws"}) assert validator(url) == url def test_url_custom_message(): validator = validate.URL(error="{input} ain't an URL") with pytest.raises(ValidationError, match="invalid ain't an URL"): validator("invalid") def test_url_repr(): assert repr( validate.URL(relative=False, error=None) ) == "".format("Not a valid URL.") assert repr( validate.URL(relative=True, error="foo") ) == "".format("foo") assert repr( validate.URL(relative=True, absolute=False, error="foo") ) == "".format("foo") def test_url_rejects_invalid_relative_usage(): with pytest.raises( ValueError, match="URL validation cannot set both relative and absolute to False", ): validate.URL(relative=False, absolute=False) @pytest.mark.parametrize( "valid_email", [ "niceandsimple@example.com", "NiCeAnDsImPlE@eXaMpLe.CoM", "very.common@example.com", "a.little.lengthy.but.fine@a.iana-servers.net", "disposable.style.email.with+symbol@example.com", '"very.unusual.@.unusual.com"@example.com', "!#$%&'*+-/=?^_`{}|~@example.org", "niceandsimple@[64.233.160.0]", "niceandsimple@localhost", "josé@blah.com", "δοκ.ιμή@παράδειγμα.δοκιμή", ], ) def test_email_valid(valid_email): validator = validate.Email() assert validator(valid_email) == valid_email @pytest.mark.parametrize( "invalid_email", [ "niceandsimple\n@example.com", "NiCeAnDsImPlE@eXaMpLe.CoM\n", 'a"b(c)d,e:f;gi[j\\k]l@example.com', 'just"not"right@example.com', 'this is"not\allowed@example.com', 'this\\ still\\"not\\\\allowed@example.com', '"much.more unusual"@example.com', '"very.(),:;<>[]".VERY."very@\\ "very".unusual"@strange.example.com', '" "@example.org', "user@example", "@nouser.com", "example.com", "user", "", None, ], ) def test_email_invalid(invalid_email): validator = validate.Email() with pytest.raises(ValidationError): validator(invalid_email) def test_email_custom_message(): validator = validate.Email(error="{input} is not an email addy.") with pytest.raises(ValidationError, match="invalid is not an email addy."): validator("invalid") def test_email_repr(): assert repr(validate.Email(error=None)) == "".format( "Not a valid email address." ) assert repr(validate.Email(error="foo")) == "".format("foo") def test_range_min(): assert validate.Range(1, 2)(1) == 1 assert validate.Range(0)(1) == 1 assert validate.Range()(1) == 1 assert validate.Range(min_inclusive=False, max_inclusive=False)(1) == 1 assert validate.Range(1, 1)(1) == 1 with pytest.raises(ValidationError, match="Must be greater than or equal to 2"): validate.Range(2, 3)(1) with pytest.raises(ValidationError, match="Must be greater than or equal to 2"): validate.Range(2)(1) with pytest.raises(ValidationError, match="Must be greater than 1"): validate.Range(1, 2, min_inclusive=False, max_inclusive=True, error=None)(1) with pytest.raises(ValidationError, match="less than 1"): validate.Range(1, 1, min_inclusive=True, max_inclusive=False, error=None)(1) def test_range_max(): assert validate.Range(1, 2)(2) == 2 assert validate.Range(None, 2)(2) == 2 assert validate.Range()(2) == 2 assert validate.Range(min_inclusive=False, max_inclusive=False)(2) == 2 assert validate.Range(2, 2)(2) == 2 with pytest.raises(ValidationError, match="less than or equal to 1"): validate.Range(0, 1)(2) with pytest.raises(ValidationError, match="less than or equal to 1"): validate.Range(None, 1)(2) with pytest.raises(ValidationError, match="less than 2"): validate.Range(1, 2, min_inclusive=True, max_inclusive=False, error=None)(2) with pytest.raises(ValidationError, match="greater than 2"): validate.Range(2, 2, min_inclusive=False, max_inclusive=True, error=None)(2) def test_range_custom_message(): v = validate.Range(2, 3, error="{input} is not between {min} and {max}") with pytest.raises(ValidationError, match="1 is not between 2 and 3"): v(1) v = validate.Range(2, None, error="{input} is less than {min}") with pytest.raises(ValidationError, match="1 is less than 2"): v(1) v = validate.Range(None, 3, error="{input} is greater than {max}") with pytest.raises(ValidationError, match="4 is greater than 3"): v(4) def test_range_repr(): assert ( repr( validate.Range( min=None, max=None, error=None, min_inclusive=True, max_inclusive=True ) ) == "" # noqa: B950 ) assert ( repr( validate.Range( min=1, max=3, error="foo", min_inclusive=False, max_inclusive=False ) ) == "".format( # noqa: B950 "foo" ) ) def test_length_min(): assert validate.Length(3, 5)("foo") == "foo" assert validate.Length(3, 5)([1, 2, 3]) == [1, 2, 3] assert validate.Length(0)("a") == "a" assert validate.Length(0)([1]) == [1] assert validate.Length()("") == "" assert validate.Length()([]) == [] assert validate.Length(1, 1)("a") == "a" assert validate.Length(1, 1)([1]) == [1] with pytest.raises(ValidationError): validate.Length(4, 5)("foo") with pytest.raises(ValidationError): validate.Length(4, 5)([1, 2, 3]) with pytest.raises(ValidationError): validate.Length(5)("foo") with pytest.raises(ValidationError): validate.Length(5)([1, 2, 3]) def test_length_max(): assert validate.Length(1, 3)("foo") == "foo" assert validate.Length(1, 3)([1, 2, 3]) == [1, 2, 3] assert validate.Length(None, 1)("a") == "a" assert validate.Length(None, 1)([1]) == [1] assert validate.Length()("") == "" assert validate.Length()([]) == [] assert validate.Length(2, 2)("ab") == "ab" assert validate.Length(2, 2)([1, 2]) == [1, 2] with pytest.raises(ValidationError): validate.Length(1, 2)("foo") with pytest.raises(ValidationError): validate.Length(1, 2)([1, 2, 3]) with pytest.raises(ValidationError): validate.Length(None, 2)("foo") with pytest.raises(ValidationError): validate.Length(None, 2)([1, 2, 3]) def test_length_equal(): assert validate.Length(equal=3)("foo") == "foo" assert validate.Length(equal=3)([1, 2, 3]) == [1, 2, 3] assert validate.Length(equal=None)("") == "" assert validate.Length(equal=None)([]) == [] with pytest.raises(ValidationError): validate.Length(equal=2)("foo") with pytest.raises(ValidationError): validate.Length(equal=2)([1, 2, 3]) with pytest.raises(ValueError): validate.Length(1, None, equal=3)("foo") with pytest.raises(ValueError): validate.Length(None, 5, equal=3)("foo") with pytest.raises(ValueError): validate.Length(1, 5, equal=3)("foo") def test_length_custom_message(): v = validate.Length(5, 6, error="{input} is not between {min} and {max}") with pytest.raises(ValidationError, match="foo is not between 5 and 6"): v("foo") v = validate.Length(5, None, error="{input} is shorter than {min}") with pytest.raises(ValidationError, match="foo is shorter than 5"): v("foo") v = validate.Length(None, 2, error="{input} is longer than {max}") with pytest.raises(ValidationError, match="foo is longer than 2"): v("foo") v = validate.Length(None, None, equal=4, error="{input} does not have {equal}") with pytest.raises(ValidationError, match="foo does not have 4"): v("foo") def test_length_repr(): assert ( repr(validate.Length(min=None, max=None, error=None, equal=None)) == "" ) assert repr( validate.Length(min=1, max=3, error="foo", equal=None) ) == "".format("foo") assert repr( validate.Length(min=None, max=None, error="foo", equal=5) ) == "".format("foo") def test_equal(): assert validate.Equal("a")("a") == "a" assert validate.Equal(1)(1) == 1 assert validate.Equal([1])([1]) == [1] with pytest.raises(ValidationError): validate.Equal("b")("a") with pytest.raises(ValidationError): validate.Equal(2)(1) with pytest.raises(ValidationError): validate.Equal([2])([1]) def test_equal_custom_message(): v = validate.Equal("a", error="{input} is not equal to {other}.") with pytest.raises(ValidationError, match="b is not equal to a."): v("b") def test_equal_repr(): assert repr( validate.Equal(comparable=123, error=None) ) == "".format("Must be equal to {other}.") assert repr( validate.Equal(comparable=123, error="foo") ) == "".format("foo") def test_regexp_str(): assert validate.Regexp(r"a")("a") == "a" assert validate.Regexp(r"\w")("_") == "_" assert validate.Regexp(r"\s")(" ") == " " assert validate.Regexp(r"1")("1") == "1" assert validate.Regexp(r"[0-9]+")("1") == "1" assert validate.Regexp(r"a", re.IGNORECASE)("A") == "A" with pytest.raises(ValidationError): validate.Regexp(r"[0-9]+")("a") with pytest.raises(ValidationError): validate.Regexp(r"[a-z]+")("1") with pytest.raises(ValidationError): validate.Regexp(r"a")("A") def test_regexp_compile(): assert validate.Regexp(re.compile(r"a"))("a") == "a" assert validate.Regexp(re.compile(r"\w"))("_") == "_" assert validate.Regexp(re.compile(r"\s"))(" ") == " " assert validate.Regexp(re.compile(r"1"))("1") == "1" assert validate.Regexp(re.compile(r"[0-9]+"))("1") == "1" assert validate.Regexp(re.compile(r"a", re.IGNORECASE))("A") == "A" assert validate.Regexp(re.compile(r"a", re.IGNORECASE), re.IGNORECASE)("A") == "A" with pytest.raises(ValidationError): validate.Regexp(re.compile(r"[0-9]+"))("a") with pytest.raises(ValidationError): validate.Regexp(re.compile(r"[a-z]+"))("1") with pytest.raises(ValidationError): validate.Regexp(re.compile(r"a"))("A") with pytest.raises(ValidationError): validate.Regexp(re.compile(r"a"), re.IGNORECASE)("A") def test_regexp_custom_message(): rex = r"[0-9]+" v = validate.Regexp(rex, error="{input} does not match {regex}") with pytest.raises(ValidationError, match="a does not match"): v("a") def test_regexp_repr(): assert repr( validate.Regexp(regex="abc", flags=0, error=None) ) == "".format( re.compile("abc"), "String does not match expected pattern." ) assert repr( validate.Regexp(regex="abc", flags=re.IGNORECASE, error="foo") ) == "".format( re.compile("abc", re.IGNORECASE), "foo" ) def test_predicate(): class Dummy: def _true(self): return True def _false(self): return False def _list(self): return [1, 2, 3] def _empty(self): return [] def _identity(self, arg): return arg d = Dummy() assert validate.Predicate("_true")(d) == d assert validate.Predicate("_list")(d) == d assert validate.Predicate("_identity", arg=True)(d) == d assert validate.Predicate("_identity", arg=1)(d) == d assert validate.Predicate("_identity", arg="abc")(d) == d with pytest.raises(ValidationError, match="Invalid input."): validate.Predicate("_false")(d) with pytest.raises(ValidationError): validate.Predicate("_empty")(d) with pytest.raises(ValidationError): validate.Predicate("_identity", arg=False)(d) with pytest.raises(ValidationError): validate.Predicate("_identity", arg=0)(d) with pytest.raises(ValidationError): validate.Predicate("_identity", arg="")(d) def test_predicate_custom_message(): class Dummy: def _false(self): return False def __str__(self): return "Dummy" d = Dummy() with pytest.raises(ValidationError, match="Dummy._false is invalid!"): validate.Predicate("_false", error="{input}.{method} is invalid!")(d) def test_predicate_repr(): assert repr( validate.Predicate(method="foo", error=None) ) == "".format( "foo", {}, "Invalid input." ) assert repr( validate.Predicate(method="foo", error="bar", zoo=1) ) == "".format( "foo", {"zoo": 1}, "bar" ) def test_noneof(): assert validate.NoneOf([1, 2, 3])(4) == 4 assert validate.NoneOf("abc")("d") == "d" assert validate.NoneOf("")([]) == [] assert validate.NoneOf([])("") == "" assert validate.NoneOf([])([]) == [] assert validate.NoneOf([1, 2, 3])(None) is None with pytest.raises(ValidationError, match="Invalid input."): validate.NoneOf([1, 2, 3])(3) with pytest.raises(ValidationError): validate.NoneOf("abc")("c") with pytest.raises(ValidationError): validate.NoneOf([1, 2, None])(None) with pytest.raises(ValidationError): validate.NoneOf("")("") def test_noneof_custom_message(): with pytest.raises(ValidationError, match=""): validate.NoneOf([1, 2], error="")(1) none_of = validate.NoneOf([1, 2], error="{input} cannot be one of {values}") with pytest.raises(ValidationError, match="1 cannot be one of 1, 2"): none_of(1) def test_noneof_repr(): assert repr( validate.NoneOf(iterable=[1, 2, 3], error=None) ) == "".format("Invalid input.") assert repr( validate.NoneOf(iterable=[1, 2, 3], error="foo") ) == "".format("foo") def test_oneof(): assert validate.OneOf([1, 2, 3])(2) == 2 assert validate.OneOf("abc")("b") == "b" assert validate.OneOf("")("") == "" assert validate.OneOf(dict(a=0, b=1))("a") == "a" assert validate.OneOf((1, 2, None))(None) is None with pytest.raises(ValidationError, match="Must be one of: 1, 2, 3."): validate.OneOf([1, 2, 3])(4) with pytest.raises(ValidationError): validate.OneOf("abc")("d") with pytest.raises(ValidationError): validate.OneOf((1, 2, 3))(None) with pytest.raises(ValidationError): validate.OneOf([])([]) with pytest.raises(ValidationError): validate.OneOf(())(()) with pytest.raises(ValidationError): validate.OneOf(dict(a=0, b=1))(0) with pytest.raises(ValidationError): validate.OneOf("123")(1) def test_oneof_options(): oneof = validate.OneOf([1, 2, 3], ["one", "two", "three"]) expected = [("1", "one"), ("2", "two"), ("3", "three")] assert list(oneof.options()) == expected oneof = validate.OneOf([1, 2, 3], ["one", "two"]) expected = [("1", "one"), ("2", "two"), ("3", "")] assert list(oneof.options()) == expected oneof = validate.OneOf([1, 2], ["one", "two", "three"]) expected = [("1", "one"), ("2", "two"), ("", "three")] assert list(oneof.options()) == expected oneof = validate.OneOf([1, 2]) expected = [("1", ""), ("2", "")] assert list(oneof.options()) == expected def test_oneof_text(): oneof = validate.OneOf([1, 2, 3], ["one", "two", "three"]) assert oneof.choices_text == "1, 2, 3" assert oneof.labels_text == "one, two, three" oneof = validate.OneOf([1], ["one"]) assert oneof.choices_text == "1" assert oneof.labels_text == "one" oneof = validate.OneOf(dict(a=0, b=1)) assert ", ".join(sorted(oneof.choices_text.split(", "))) == "a, b" assert oneof.labels_text == "" def test_oneof_custom_message(): oneof = validate.OneOf([1, 2, 3], error="{input} is not one of {choices}") expected = "4 is not one of 1, 2, 3" with pytest.raises(ValidationError): oneof(4) assert expected in str(expected) oneof = validate.OneOf( [1, 2, 3], ["one", "two", "three"], error="{input} is not one of {labels}" ) expected = "4 is not one of one, two, three" with pytest.raises(ValidationError): oneof(4) assert expected in str(expected) def test_oneof_repr(): assert repr( validate.OneOf(choices=[1, 2, 3], labels=None, error=None) ) == "".format( "Must be one of: {choices}." ) assert repr( validate.OneOf(choices=[1, 2, 3], labels=["a", "b", "c"], error="foo") ) == "".format( ["a", "b", "c"], "foo" ) def test_containsonly_in_list(): assert validate.ContainsOnly([])([]) == [] assert validate.ContainsOnly([1, 2, 3])([1]) == [1] assert validate.ContainsOnly([1, 1, 2])([1, 1]) == [1, 1] assert validate.ContainsOnly([1, 2, 3])([1, 2]) == [1, 2] assert validate.ContainsOnly([1, 2, 3])([2, 1]) == [2, 1] assert validate.ContainsOnly([1, 2, 3])([1, 2, 3]) == [1, 2, 3] assert validate.ContainsOnly([1, 2, 3])([3, 1, 2]) == [3, 1, 2] assert validate.ContainsOnly([1, 2, 3])([2, 3, 1]) == [2, 3, 1] assert validate.ContainsOnly([1, 2, 3])([1, 2, 3, 1]) == [1, 2, 3, 1] assert validate.ContainsOnly([1, 2, 3])([]) == [] with pytest.raises( ValidationError, match="One or more of the choices you made was not in: 1, 2, 3.", ): validate.ContainsOnly([1, 2, 3])([4]) with pytest.raises(ValidationError): validate.ContainsOnly([])([1]) def test_contains_only_unhashable_types(): assert validate.ContainsOnly([[1], [2], [3]])([[1]]) == [[1]] assert validate.ContainsOnly([[1], [1], [2]])([[1], [1]]) == [[1], [1]] assert validate.ContainsOnly([[1], [2], [3]])([[1], [2]]) == [[1], [2]] assert validate.ContainsOnly([[1], [2], [3]])([[2], [1]]) == [[2], [1]] assert validate.ContainsOnly([[1], [2], [3]])([[1], [2], [3]]) == [[1], [2], [3]] assert validate.ContainsOnly([[1], [2], [3]])([[3], [1], [2]]) == [[3], [1], [2]] assert validate.ContainsOnly([[1], [2], [3]])([[2], [3], [1]]) == [[2], [3], [1]] assert validate.ContainsOnly([[1], [2], [3]])([]) == [] with pytest.raises(ValidationError): validate.ContainsOnly([[1], [2], [3]])([[4]]) with pytest.raises(ValidationError): validate.ContainsOnly([])([1]) def test_containsonly_in_tuple(): assert validate.ContainsOnly(())(()) == () assert validate.ContainsOnly((1, 2, 3))((1,)) == (1,) assert validate.ContainsOnly((1, 1, 2))((1, 1)) == (1, 1) assert validate.ContainsOnly((1, 2, 3))((1, 2)) == (1, 2) assert validate.ContainsOnly((1, 2, 3))((2, 1)) == (2, 1) assert validate.ContainsOnly((1, 2, 3))((1, 2, 3)) == (1, 2, 3) assert validate.ContainsOnly((1, 2, 3))((3, 1, 2)) == (3, 1, 2) assert validate.ContainsOnly((1, 2, 3))((2, 3, 1)) == (2, 3, 1) assert validate.ContainsOnly((1, 2, 3))(()) == tuple() with pytest.raises(ValidationError): validate.ContainsOnly((1, 2, 3))((4,)) with pytest.raises(ValidationError): validate.ContainsOnly(())((1,)) def test_contains_only_in_string(): assert validate.ContainsOnly("")("") == "" assert validate.ContainsOnly("abc")("a") == "a" assert validate.ContainsOnly("aab")("aa") == "aa" assert validate.ContainsOnly("abc")("ab") == "ab" assert validate.ContainsOnly("abc")("ba") == "ba" assert validate.ContainsOnly("abc")("abc") == "abc" assert validate.ContainsOnly("abc")("cab") == "cab" assert validate.ContainsOnly("abc")("bca") == "bca" assert validate.ContainsOnly("abc")("") == "" with pytest.raises(ValidationError): validate.ContainsOnly("abc")("d") with pytest.raises(ValidationError): validate.ContainsOnly("")("a") def test_containsonly_custom_message(): containsonly = validate.ContainsOnly( [1, 2, 3], error="{input} is not one of {choices}" ) expected = "4, 5 is not one of 1, 2, 3" with pytest.raises(ValidationError): containsonly([4, 5]) assert expected in str(expected) containsonly = validate.ContainsOnly( [1, 2, 3], ["one", "two", "three"], error="{input} is not one of {labels}" ) expected = "4, 5 is not one of one, two, three" with pytest.raises(ValidationError): containsonly([4, 5]) assert expected in str(expected) def test_containsonly_repr(): assert repr( validate.ContainsOnly(choices=[1, 2, 3], labels=None, error=None) ) == "".format( "One or more of the choices you made was not in: {choices}." ) assert repr( validate.ContainsOnly(choices=[1, 2, 3], labels=["a", "b", "c"], error="foo") ) == "".format( ["a", "b", "c"], "foo" ) def test_containsnoneof_error_message(): with pytest.raises( ValidationError, match="One or more of the choices you made was in: 1" ): validate.ContainsNoneOf([1])([1]) with pytest.raises( ValidationError, match="One or more of the choices you made was in: 1, 2, 3" ): validate.ContainsNoneOf([1, 2, 3])([1]) with pytest.raises( ValidationError, match="One or more of the choices you made was in: one, two" ): validate.ContainsNoneOf(["one", "two"])(["one"]) with pytest.raises( ValidationError, match="One or more of the choices you made was in: @, !, &, ?" ): validate.ContainsNoneOf("@!&?")("@") def test_containsnoneof_in_list(): assert validate.ContainsNoneOf([])([]) == [] assert validate.ContainsNoneOf([])([1, 2, 3]) == [1, 2, 3] assert validate.ContainsNoneOf([4])([1, 2, 3]) == [1, 2, 3] assert validate.ContainsNoneOf([2])([1, 3, 4]) == [1, 3, 4] assert validate.ContainsNoneOf([1, 2, 3])([4]) == [4] assert validate.ContainsNoneOf([4])([1, 1, 1, 1]) == [1, 1, 1, 1] with pytest.raises(ValidationError): validate.ContainsNoneOf([1])([1, 2, 3]) with pytest.raises(ValidationError): validate.ContainsNoneOf([1, 1, 1])([1, 2, 3]) with pytest.raises(ValidationError): validate.ContainsNoneOf([1, 2])([1, 2]) with pytest.raises(ValidationError): validate.ContainsNoneOf([1])([1, 1, 1, 1]) def test_containsnoneof_unhashable_types(): assert validate.ContainsNoneOf([[1], [2], [3]])([]) == [] assert validate.ContainsNoneOf([[1], [2], [3]])([[4]]) == [[4]] assert validate.ContainsNoneOf([[1], [2], [3]])([[4], [4]]) == [[4], [4]] assert validate.ContainsNoneOf([[1], [2], [3]])([[4], [5]]) == [[4], [5]] with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[1]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[1], [2]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[2], [1]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[1], [2], [3]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[3], [2], [1]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[2], [3], [1]]) def test_containsnoneof_in_tuple(): assert validate.ContainsNoneOf(())(()) == () assert validate.ContainsNoneOf(())((1, 2, 3)) == (1, 2, 3) assert validate.ContainsNoneOf((4,))((1, 2, 3)) == (1, 2, 3) assert validate.ContainsNoneOf((2,))((1, 3, 4)) == (1, 3, 4) assert validate.ContainsNoneOf((1, 2, 3))((4,)) == (4,) assert validate.ContainsNoneOf((4,))((1, 1, 1, 1)) == (1, 1, 1, 1) with pytest.raises(ValidationError): validate.ContainsNoneOf((1,))((1, 2, 3)) with pytest.raises(ValidationError): validate.ContainsNoneOf((1, 1, 1))((1, 2, 3)) with pytest.raises(ValidationError): validate.ContainsNoneOf((1, 2))((1, 2)) with pytest.raises(ValidationError): validate.ContainsNoneOf((1,))((1, 1, 1, 1)) def test_containsnoneof_in_string(): assert validate.ContainsNoneOf("")("") == "" assert validate.ContainsNoneOf("")("abc") == "abc" assert validate.ContainsNoneOf("d")("abc") == "abc" assert validate.ContainsNoneOf("b")("acd") == "acd" assert validate.ContainsNoneOf("abc")("d") == "d" assert validate.ContainsNoneOf("d")("aaaa") == "aaaa" with pytest.raises(ValidationError): validate.ContainsNoneOf("a")("abc") with pytest.raises(ValidationError): validate.ContainsNoneOf("aaa")("abc") with pytest.raises(ValidationError): validate.ContainsNoneOf("ab")("ab") with pytest.raises(ValidationError): validate.ContainsNoneOf("a")("aaaa") def test_containsnoneof_custom_message(): validator = validate.ContainsNoneOf( [1, 2, 3], error="{input} was in the banned list: {values}" ) expected = "1 was in the banned list: 1, 2, 3" with pytest.raises(ValidationError, match=expected): validator([1]) def test_containsnoneof_mixing_types(): with pytest.raises(ValidationError): validate.ContainsNoneOf("abc")(["a"]) with pytest.raises(ValidationError): validate.ContainsNoneOf(["a", "b", "c"])("a") with pytest.raises(ValidationError): validate.ContainsNoneOf((1, 2, 3))([1]) with pytest.raises(ValidationError): validate.ContainsNoneOf([1, 2, 3])((1,)) def is_even(value): if value % 2 != 0: raise ValidationError("Not an even value.") def test_and(): validator = validate.And(validate.Range(min=0), is_even) assert validator(2) with pytest.raises(ValidationError) as excinfo: validator(-1) errors = excinfo.value.messages assert errors == ["Must be greater than or equal to 0.", "Not an even value."] validator_with_composition = validate.And(validator, validate.Range(max=6)) assert validator_with_composition(4) with pytest.raises(ValidationError) as excinfo: validator_with_composition(7) errors = excinfo.value.messages assert errors == ["Not an even value.", "Must be less than or equal to 6."]