1"""Tests for marshmallow.validate""" 2 3import re 4import pytest 5 6from marshmallow import validate, ValidationError 7 8 9@pytest.mark.parametrize( 10 "valid_url", 11 [ 12 "http://example.org", 13 "https://example.org", 14 "ftp://example.org", 15 "ftps://example.org", 16 "http://example.co.jp", 17 "http://www.example.com/a%C2%B1b", 18 "http://www.example.com/~username/", 19 "http://info.example.com/?fred", 20 "http://xn--mgbh0fb.xn--kgbechtv/", 21 "http://example.com/blue/red%3Fand+green", 22 "http://www.example.com/?array%5Bkey%5D=value", 23 "http://xn--rsum-bpad.example.org/", 24 "http://123.45.67.8/", 25 "http://123.45.67.8:8329/", 26 "http://[2001:db8::ff00:42]:8329", 27 "http://[2001::1]:8329", 28 "http://www.example.com:8000/foo", 29 "http://user@example.com", 30 "http://user:pass@example.com", 31 ], 32) 33def test_url_absolute_valid(valid_url): 34 validator = validate.URL(relative=False) 35 assert validator(valid_url) == valid_url 36 37 38@pytest.mark.parametrize( 39 "invalid_url", 40 [ 41 "http:///example.com/", 42 "https:///example.com/", 43 "https://example.org\\", 44 "https://example.org\n", 45 "ftp:///example.com/", 46 "ftps:///example.com/", 47 "http//example.org", 48 "http:///", 49 "http:/example.org", 50 "foo://example.org", 51 "../icons/logo.gif", 52 "http://2001:db8::ff00:42:8329", 53 "http://[192.168.1.1]:8329", 54 "abc", 55 "..", 56 "/", 57 " ", 58 "", 59 None, 60 ], 61) 62def test_url_absolute_invalid(invalid_url): 63 validator = validate.URL(relative=False) 64 with pytest.raises(ValidationError): 65 validator(invalid_url) 66 67 68@pytest.mark.parametrize( 69 "valid_url", 70 [ 71 "http://example.org", 72 "http://123.45.67.8/", 73 "http://example.com/foo/bar/../baz", 74 "https://example.com/../icons/logo.gif", 75 "http://example.com/./icons/logo.gif", 76 "ftp://example.com/../../../../g", 77 "http://example.com/g?y/./x", 78 ], 79) 80def test_url_relative_valid(valid_url): 81 validator = validate.URL(relative=True) 82 assert validator(valid_url) == valid_url 83 84 85@pytest.mark.parametrize( # noqa: W605 86 "invalid_url", 87 [ 88 "http//example.org", 89 "http://example.org\n", 90 "suppliers.html", 91 "../icons/logo.gif", 92 "icons/logo.gif", 93 "../.../g", 94 "...", 95 "\\", 96 " ", 97 "", 98 None, 99 ], 100) 101def test_url_relative_invalid(invalid_url): 102 validator = validate.URL(relative=True) 103 with pytest.raises(ValidationError): 104 validator(invalid_url) 105 106 107@pytest.mark.parametrize( 108 "valid_url", 109 [ 110 "http://example.org", 111 "http://123.45.67.8/", 112 "http://example", 113 "http://example.", 114 "http://example:80", 115 "http://user.name:pass.word@example", 116 "http://example/foo/bar", 117 ], 118) 119def test_url_dont_require_tld_valid(valid_url): 120 validator = validate.URL(require_tld=False) 121 assert validator(valid_url) == valid_url 122 123 124@pytest.mark.parametrize( 125 "invalid_url", 126 [ 127 "http//example", 128 "http://example\n", 129 "http://.example.org", 130 "http:///foo/bar", 131 "http:// /foo/bar", 132 "", 133 None, 134 ], 135) 136def test_url_dont_require_tld_invalid(invalid_url): 137 validator = validate.URL(require_tld=False) 138 with pytest.raises(ValidationError): 139 validator(invalid_url) 140 141 142def test_url_custom_scheme(): 143 validator = validate.URL() 144 # By default, ws not allowed 145 url = "ws://test.test" 146 with pytest.raises(ValidationError): 147 validator(url) 148 149 validator = validate.URL(schemes={"http", "https", "ws"}) 150 assert validator(url) == url 151 152 153def test_url_relative_and_custom_schemes(): 154 validator = validate.URL(relative=True) 155 # By default, ws not allowed 156 url = "ws://test.test" 157 with pytest.raises(ValidationError): 158 validator(url) 159 160 validator = validate.URL(relative=True, schemes={"http", "https", "ws"}) 161 assert validator(url) == url 162 163 164def test_url_custom_message(): 165 validator = validate.URL(error="{input} ain't an URL") 166 with pytest.raises(ValidationError, match="invalid ain't an URL"): 167 validator("invalid") 168 169 170def test_url_repr(): 171 assert repr( 172 validate.URL(relative=False, error=None) 173 ) == "<URL(relative=False, error={!r})>".format("Not a valid URL.") 174 assert repr( 175 validate.URL(relative=True, error="foo") 176 ) == "<URL(relative=True, error={!r})>".format("foo") 177 178 179@pytest.mark.parametrize( 180 "valid_email", 181 [ 182 "niceandsimple@example.com", 183 "NiCeAnDsImPlE@eXaMpLe.CoM", 184 "very.common@example.com", 185 "a.little.lengthy.but.fine@a.iana-servers.net", 186 "disposable.style.email.with+symbol@example.com", 187 '"very.unusual.@.unusual.com"@example.com', 188 "!#$%&'*+-/=?^_`{}|~@example.org", 189 "niceandsimple@[64.233.160.0]", 190 "niceandsimple@localhost", 191 "josé@blah.com", 192 "δοκ.ιμή@παράδειγμα.δοκιμή", 193 ], 194) 195def test_email_valid(valid_email): 196 validator = validate.Email() 197 assert validator(valid_email) == valid_email 198 199 200@pytest.mark.parametrize( 201 "invalid_email", 202 [ 203 "niceandsimple\n@example.com", 204 "NiCeAnDsImPlE@eXaMpLe.CoM\n", 205 'a"b(c)d,e:f;g<h>i[j\\k]l@example.com', 206 'just"not"right@example.com', 207 'this is"not\allowed@example.com', 208 'this\\ still\\"not\\\\allowed@example.com', 209 '"much.more unusual"@example.com', 210 '"very.(),:;<>[]".VERY."very@\\ "very".unusual"@strange.example.com', 211 '" "@example.org', 212 "user@example", 213 "@nouser.com", 214 "example.com", 215 "user", 216 "", 217 None, 218 ], 219) 220def test_email_invalid(invalid_email): 221 validator = validate.Email() 222 with pytest.raises(ValidationError): 223 validator(invalid_email) 224 225 226def test_email_custom_message(): 227 validator = validate.Email(error="{input} is not an email addy.") 228 with pytest.raises(ValidationError, match="invalid is not an email addy."): 229 validator("invalid") 230 231 232def test_email_repr(): 233 assert repr(validate.Email(error=None)) == "<Email(error={!r})>".format( 234 "Not a valid email address." 235 ) 236 assert repr(validate.Email(error="foo")) == "<Email(error={!r})>".format("foo") 237 238 239def test_range_min(): 240 assert validate.Range(1, 2)(1) == 1 241 assert validate.Range(0)(1) == 1 242 assert validate.Range()(1) == 1 243 assert validate.Range(min_inclusive=False, max_inclusive=False)(1) == 1 244 assert validate.Range(1, 1)(1) == 1 245 246 with pytest.raises(ValidationError, match="Must be greater than or equal to 2"): 247 validate.Range(2, 3)(1) 248 with pytest.raises(ValidationError, match="Must be greater than or equal to 2"): 249 validate.Range(2)(1) 250 with pytest.raises(ValidationError, match="Must be greater than 1"): 251 validate.Range(1, 2, min_inclusive=False, max_inclusive=True, error=None)(1) 252 with pytest.raises(ValidationError, match="less than 1"): 253 validate.Range(1, 1, min_inclusive=True, max_inclusive=False, error=None)(1) 254 255 256def test_range_max(): 257 assert validate.Range(1, 2)(2) == 2 258 assert validate.Range(None, 2)(2) == 2 259 assert validate.Range()(2) == 2 260 assert validate.Range(min_inclusive=False, max_inclusive=False)(2) == 2 261 assert validate.Range(2, 2)(2) == 2 262 263 with pytest.raises(ValidationError, match="less than or equal to 1"): 264 validate.Range(0, 1)(2) 265 with pytest.raises(ValidationError, match="less than or equal to 1"): 266 validate.Range(None, 1)(2) 267 with pytest.raises(ValidationError, match="less than 2"): 268 validate.Range(1, 2, min_inclusive=True, max_inclusive=False, error=None)(2) 269 with pytest.raises(ValidationError, match="greater than 2"): 270 validate.Range(2, 2, min_inclusive=False, max_inclusive=True, error=None)(2) 271 272 273def test_range_custom_message(): 274 v = validate.Range(2, 3, error="{input} is not between {min} and {max}") 275 with pytest.raises(ValidationError, match="1 is not between 2 and 3"): 276 v(1) 277 278 v = validate.Range(2, None, error="{input} is less than {min}") 279 with pytest.raises(ValidationError, match="1 is less than 2"): 280 v(1) 281 282 v = validate.Range(None, 3, error="{input} is greater than {max}") 283 with pytest.raises(ValidationError, match="4 is greater than 3"): 284 v(4) 285 286 287def test_range_repr(): 288 assert ( 289 repr( 290 validate.Range( 291 min=None, max=None, error=None, min_inclusive=True, max_inclusive=True 292 ) 293 ) 294 == "<Range(min=None, max=None, min_inclusive=True, max_inclusive=True, error=None)>" # noqa: B950 295 ) 296 assert repr( 297 validate.Range( 298 min=1, max=3, error="foo", min_inclusive=False, max_inclusive=False 299 ) 300 ) == "<Range(min=1, max=3, min_inclusive=False, max_inclusive=False, error={!r})>".format( # noqa: B950 301 "foo" 302 ) 303 304 305def test_length_min(): 306 assert validate.Length(3, 5)("foo") == "foo" 307 assert validate.Length(3, 5)([1, 2, 3]) == [1, 2, 3] 308 assert validate.Length(0)("a") == "a" 309 assert validate.Length(0)([1]) == [1] 310 assert validate.Length()("") == "" 311 assert validate.Length()([]) == [] 312 assert validate.Length(1, 1)("a") == "a" 313 assert validate.Length(1, 1)([1]) == [1] 314 315 with pytest.raises(ValidationError): 316 validate.Length(4, 5)("foo") 317 with pytest.raises(ValidationError): 318 validate.Length(4, 5)([1, 2, 3]) 319 with pytest.raises(ValidationError): 320 validate.Length(5)("foo") 321 with pytest.raises(ValidationError): 322 validate.Length(5)([1, 2, 3]) 323 324 325def test_length_max(): 326 assert validate.Length(1, 3)("foo") == "foo" 327 assert validate.Length(1, 3)([1, 2, 3]) == [1, 2, 3] 328 assert validate.Length(None, 1)("a") == "a" 329 assert validate.Length(None, 1)([1]) == [1] 330 assert validate.Length()("") == "" 331 assert validate.Length()([]) == [] 332 assert validate.Length(2, 2)("ab") == "ab" 333 assert validate.Length(2, 2)([1, 2]) == [1, 2] 334 335 with pytest.raises(ValidationError): 336 validate.Length(1, 2)("foo") 337 with pytest.raises(ValidationError): 338 validate.Length(1, 2)([1, 2, 3]) 339 with pytest.raises(ValidationError): 340 validate.Length(None, 2)("foo") 341 with pytest.raises(ValidationError): 342 validate.Length(None, 2)([1, 2, 3]) 343 344 345def test_length_equal(): 346 assert validate.Length(equal=3)("foo") == "foo" 347 assert validate.Length(equal=3)([1, 2, 3]) == [1, 2, 3] 348 assert validate.Length(equal=None)("") == "" 349 assert validate.Length(equal=None)([]) == [] 350 351 with pytest.raises(ValidationError): 352 validate.Length(equal=2)("foo") 353 with pytest.raises(ValidationError): 354 validate.Length(equal=2)([1, 2, 3]) 355 356 with pytest.raises(ValueError): 357 validate.Length(1, None, equal=3)("foo") 358 with pytest.raises(ValueError): 359 validate.Length(None, 5, equal=3)("foo") 360 with pytest.raises(ValueError): 361 validate.Length(1, 5, equal=3)("foo") 362 363 364def test_length_custom_message(): 365 v = validate.Length(5, 6, error="{input} is not between {min} and {max}") 366 with pytest.raises(ValidationError, match="foo is not between 5 and 6"): 367 v("foo") 368 369 v = validate.Length(5, None, error="{input} is shorter than {min}") 370 with pytest.raises(ValidationError, match="foo is shorter than 5"): 371 v("foo") 372 373 v = validate.Length(None, 2, error="{input} is longer than {max}") 374 with pytest.raises(ValidationError, match="foo is longer than 2"): 375 v("foo") 376 377 v = validate.Length(None, None, equal=4, error="{input} does not have {equal}") 378 with pytest.raises(ValidationError, match="foo does not have 4"): 379 v("foo") 380 381 382def test_length_repr(): 383 assert ( 384 repr(validate.Length(min=None, max=None, error=None, equal=None)) 385 == "<Length(min=None, max=None, equal=None, error=None)>" 386 ) 387 assert repr( 388 validate.Length(min=1, max=3, error="foo", equal=None) 389 ) == "<Length(min=1, max=3, equal=None, error={!r})>".format("foo") 390 assert repr( 391 validate.Length(min=None, max=None, error="foo", equal=5) 392 ) == "<Length(min=None, max=None, equal=5, error={!r})>".format("foo") 393 394 395def test_equal(): 396 assert validate.Equal("a")("a") == "a" 397 assert validate.Equal(1)(1) == 1 398 assert validate.Equal([1])([1]) == [1] 399 400 with pytest.raises(ValidationError): 401 validate.Equal("b")("a") 402 with pytest.raises(ValidationError): 403 validate.Equal(2)(1) 404 with pytest.raises(ValidationError): 405 validate.Equal([2])([1]) 406 407 408def test_equal_custom_message(): 409 v = validate.Equal("a", error="{input} is not equal to {other}.") 410 with pytest.raises(ValidationError, match="b is not equal to a."): 411 v("b") 412 413 414def test_equal_repr(): 415 assert repr( 416 validate.Equal(comparable=123, error=None) 417 ) == "<Equal(comparable=123, error={!r})>".format("Must be equal to {other}.") 418 assert repr( 419 validate.Equal(comparable=123, error="foo") 420 ) == "<Equal(comparable=123, error={!r})>".format("foo") 421 422 423def test_regexp_str(): 424 assert validate.Regexp(r"a")("a") == "a" 425 assert validate.Regexp(r"\w")("_") == "_" 426 assert validate.Regexp(r"\s")(" ") == " " 427 assert validate.Regexp(r"1")("1") == "1" 428 assert validate.Regexp(r"[0-9]+")("1") == "1" 429 assert validate.Regexp(r"a", re.IGNORECASE)("A") == "A" 430 431 with pytest.raises(ValidationError): 432 validate.Regexp(r"[0-9]+")("a") 433 with pytest.raises(ValidationError): 434 validate.Regexp(r"[a-z]+")("1") 435 with pytest.raises(ValidationError): 436 validate.Regexp(r"a")("A") 437 438 439def test_regexp_compile(): 440 assert validate.Regexp(re.compile(r"a"))("a") == "a" 441 assert validate.Regexp(re.compile(r"\w"))("_") == "_" 442 assert validate.Regexp(re.compile(r"\s"))(" ") == " " 443 assert validate.Regexp(re.compile(r"1"))("1") == "1" 444 assert validate.Regexp(re.compile(r"[0-9]+"))("1") == "1" 445 assert validate.Regexp(re.compile(r"a", re.IGNORECASE))("A") == "A" 446 assert validate.Regexp(re.compile(r"a", re.IGNORECASE), re.IGNORECASE)("A") == "A" 447 448 with pytest.raises(ValidationError): 449 validate.Regexp(re.compile(r"[0-9]+"))("a") 450 with pytest.raises(ValidationError): 451 validate.Regexp(re.compile(r"[a-z]+"))("1") 452 with pytest.raises(ValidationError): 453 validate.Regexp(re.compile(r"a"))("A") 454 with pytest.raises(ValidationError): 455 validate.Regexp(re.compile(r"a"), re.IGNORECASE)("A") 456 457 458def test_regexp_custom_message(): 459 rex = r"[0-9]+" 460 v = validate.Regexp(rex, error="{input} does not match {regex}") 461 with pytest.raises(ValidationError, match="a does not match"): 462 v("a") 463 464 465def test_regexp_repr(): 466 assert repr( 467 validate.Regexp(regex="abc", flags=0, error=None) 468 ) == "<Regexp(regex={!r}, error={!r})>".format( 469 re.compile("abc"), "String does not match expected pattern." 470 ) 471 assert repr( 472 validate.Regexp(regex="abc", flags=re.IGNORECASE, error="foo") 473 ) == "<Regexp(regex={!r}, error={!r})>".format( 474 re.compile("abc", re.IGNORECASE), "foo" 475 ) 476 477 478def test_predicate(): 479 class Dummy: 480 def _true(self): 481 return True 482 483 def _false(self): 484 return False 485 486 def _list(self): 487 return [1, 2, 3] 488 489 def _empty(self): 490 return [] 491 492 def _identity(self, arg): 493 return arg 494 495 d = Dummy() 496 497 assert validate.Predicate("_true")(d) == d 498 assert validate.Predicate("_list")(d) == d 499 assert validate.Predicate("_identity", arg=True)(d) == d 500 assert validate.Predicate("_identity", arg=1)(d) == d 501 assert validate.Predicate("_identity", arg="abc")(d) == d 502 503 with pytest.raises(ValidationError, match="Invalid input."): 504 validate.Predicate("_false")(d) 505 with pytest.raises(ValidationError): 506 validate.Predicate("_empty")(d) 507 with pytest.raises(ValidationError): 508 validate.Predicate("_identity", arg=False)(d) 509 with pytest.raises(ValidationError): 510 validate.Predicate("_identity", arg=0)(d) 511 with pytest.raises(ValidationError): 512 validate.Predicate("_identity", arg="")(d) 513 514 515def test_predicate_custom_message(): 516 class Dummy: 517 def _false(self): 518 return False 519 520 def __str__(self): 521 return "Dummy" 522 523 d = Dummy() 524 with pytest.raises(ValidationError, match="Dummy._false is invalid!"): 525 validate.Predicate("_false", error="{input}.{method} is invalid!")(d) 526 527 528def test_predicate_repr(): 529 assert repr( 530 validate.Predicate(method="foo", error=None) 531 ) == "<Predicate(method={!r}, kwargs={!r}, error={!r})>".format( 532 "foo", {}, "Invalid input." 533 ) 534 assert repr( 535 validate.Predicate(method="foo", error="bar", zoo=1) 536 ) == "<Predicate(method={!r}, kwargs={!r}, error={!r})>".format( 537 "foo", {"zoo": 1}, "bar" 538 ) 539 540 541def test_noneof(): 542 assert validate.NoneOf([1, 2, 3])(4) == 4 543 assert validate.NoneOf("abc")("d") == "d" 544 assert validate.NoneOf("")([]) == [] 545 assert validate.NoneOf([])("") == "" 546 assert validate.NoneOf([])([]) == [] 547 assert validate.NoneOf([1, 2, 3])(None) is None 548 549 with pytest.raises(ValidationError, match="Invalid input."): 550 validate.NoneOf([1, 2, 3])(3) 551 with pytest.raises(ValidationError): 552 validate.NoneOf("abc")("c") 553 with pytest.raises(ValidationError): 554 validate.NoneOf([1, 2, None])(None) 555 with pytest.raises(ValidationError): 556 validate.NoneOf("")("") 557 558 559def test_noneof_custom_message(): 560 with pytest.raises(ValidationError, match="<not valid>"): 561 validate.NoneOf([1, 2], error="<not valid>")(1) 562 563 none_of = validate.NoneOf([1, 2], error="{input} cannot be one of {values}") 564 with pytest.raises(ValidationError, match="1 cannot be one of 1, 2"): 565 none_of(1) 566 567 568def test_noneof_repr(): 569 assert repr( 570 validate.NoneOf(iterable=[1, 2, 3], error=None) 571 ) == "<NoneOf(iterable=[1, 2, 3], error={!r})>".format("Invalid input.") 572 assert repr( 573 validate.NoneOf(iterable=[1, 2, 3], error="foo") 574 ) == "<NoneOf(iterable=[1, 2, 3], error={!r})>".format("foo") 575 576 577def test_oneof(): 578 assert validate.OneOf([1, 2, 3])(2) == 2 579 assert validate.OneOf("abc")("b") == "b" 580 assert validate.OneOf("")("") == "" 581 assert validate.OneOf(dict(a=0, b=1))("a") == "a" 582 assert validate.OneOf((1, 2, None))(None) is None 583 584 with pytest.raises(ValidationError, match="Must be one of: 1, 2, 3."): 585 validate.OneOf([1, 2, 3])(4) 586 with pytest.raises(ValidationError): 587 validate.OneOf("abc")("d") 588 with pytest.raises(ValidationError): 589 validate.OneOf((1, 2, 3))(None) 590 with pytest.raises(ValidationError): 591 validate.OneOf([])([]) 592 with pytest.raises(ValidationError): 593 validate.OneOf(())(()) 594 with pytest.raises(ValidationError): 595 validate.OneOf(dict(a=0, b=1))(0) 596 with pytest.raises(ValidationError): 597 validate.OneOf("123")(1) 598 599 600def test_oneof_options(): 601 oneof = validate.OneOf([1, 2, 3], ["one", "two", "three"]) 602 expected = [("1", "one"), ("2", "two"), ("3", "three")] 603 assert list(oneof.options()) == expected 604 605 oneof = validate.OneOf([1, 2, 3], ["one", "two"]) 606 expected = [("1", "one"), ("2", "two"), ("3", "")] 607 assert list(oneof.options()) == expected 608 609 oneof = validate.OneOf([1, 2], ["one", "two", "three"]) 610 expected = [("1", "one"), ("2", "two"), ("", "three")] 611 assert list(oneof.options()) == expected 612 613 oneof = validate.OneOf([1, 2]) 614 expected = [("1", ""), ("2", "")] 615 assert list(oneof.options()) == expected 616 617 618def test_oneof_text(): 619 oneof = validate.OneOf([1, 2, 3], ["one", "two", "three"]) 620 assert oneof.choices_text == "1, 2, 3" 621 assert oneof.labels_text == "one, two, three" 622 623 oneof = validate.OneOf([1], ["one"]) 624 assert oneof.choices_text == "1" 625 assert oneof.labels_text == "one" 626 627 oneof = validate.OneOf(dict(a=0, b=1)) 628 assert ", ".join(sorted(oneof.choices_text.split(", "))) == "a, b" 629 assert oneof.labels_text == "" 630 631 632def test_oneof_custom_message(): 633 oneof = validate.OneOf([1, 2, 3], error="{input} is not one of {choices}") 634 expected = "4 is not one of 1, 2, 3" 635 with pytest.raises(ValidationError): 636 oneof(4) 637 assert expected in str(expected) 638 639 oneof = validate.OneOf( 640 [1, 2, 3], ["one", "two", "three"], error="{input} is not one of {labels}" 641 ) 642 expected = "4 is not one of one, two, three" 643 with pytest.raises(ValidationError): 644 oneof(4) 645 assert expected in str(expected) 646 647 648def test_oneof_repr(): 649 assert repr( 650 validate.OneOf(choices=[1, 2, 3], labels=None, error=None) 651 ) == "<OneOf(choices=[1, 2, 3], labels=[], error={!r})>".format( 652 "Must be one of: {choices}." 653 ) 654 assert repr( 655 validate.OneOf(choices=[1, 2, 3], labels=["a", "b", "c"], error="foo") 656 ) == "<OneOf(choices=[1, 2, 3], labels={!r}, error={!r})>".format( 657 ["a", "b", "c"], "foo" 658 ) 659 660 661def test_containsonly_in_list(): 662 assert validate.ContainsOnly([])([]) == [] 663 assert validate.ContainsOnly([1, 2, 3])([1]) == [1] 664 assert validate.ContainsOnly([1, 1, 2])([1, 1]) == [1, 1] 665 assert validate.ContainsOnly([1, 2, 3])([1, 2]) == [1, 2] 666 assert validate.ContainsOnly([1, 2, 3])([2, 1]) == [2, 1] 667 assert validate.ContainsOnly([1, 2, 3])([1, 2, 3]) == [1, 2, 3] 668 assert validate.ContainsOnly([1, 2, 3])([3, 1, 2]) == [3, 1, 2] 669 assert validate.ContainsOnly([1, 2, 3])([2, 3, 1]) == [2, 3, 1] 670 assert validate.ContainsOnly([1, 2, 3])([1, 2, 3, 1]) == [1, 2, 3, 1] 671 assert validate.ContainsOnly([1, 2, 3])([]) == [] 672 673 with pytest.raises( 674 ValidationError, 675 match="One or more of the choices you made was not in: 1, 2, 3.", 676 ): 677 validate.ContainsOnly([1, 2, 3])([4]) 678 with pytest.raises(ValidationError): 679 validate.ContainsOnly([])([1]) 680 681 682def test_contains_only_unhashable_types(): 683 assert validate.ContainsOnly([[1], [2], [3]])([[1]]) == [[1]] 684 assert validate.ContainsOnly([[1], [1], [2]])([[1], [1]]) == [[1], [1]] 685 assert validate.ContainsOnly([[1], [2], [3]])([[1], [2]]) == [[1], [2]] 686 assert validate.ContainsOnly([[1], [2], [3]])([[2], [1]]) == [[2], [1]] 687 assert validate.ContainsOnly([[1], [2], [3]])([[1], [2], [3]]) == [[1], [2], [3]] 688 assert validate.ContainsOnly([[1], [2], [3]])([[3], [1], [2]]) == [[3], [1], [2]] 689 assert validate.ContainsOnly([[1], [2], [3]])([[2], [3], [1]]) == [[2], [3], [1]] 690 assert validate.ContainsOnly([[1], [2], [3]])([]) == [] 691 692 with pytest.raises(ValidationError): 693 validate.ContainsOnly([[1], [2], [3]])([[4]]) 694 with pytest.raises(ValidationError): 695 validate.ContainsOnly([])([1]) 696 697 698def test_containsonly_in_tuple(): 699 assert validate.ContainsOnly(())(()) == () 700 assert validate.ContainsOnly((1, 2, 3))((1,)) == (1,) 701 assert validate.ContainsOnly((1, 1, 2))((1, 1)) == (1, 1) 702 assert validate.ContainsOnly((1, 2, 3))((1, 2)) == (1, 2) 703 assert validate.ContainsOnly((1, 2, 3))((2, 1)) == (2, 1) 704 assert validate.ContainsOnly((1, 2, 3))((1, 2, 3)) == (1, 2, 3) 705 assert validate.ContainsOnly((1, 2, 3))((3, 1, 2)) == (3, 1, 2) 706 assert validate.ContainsOnly((1, 2, 3))((2, 3, 1)) == (2, 3, 1) 707 assert validate.ContainsOnly((1, 2, 3))(()) == tuple() 708 with pytest.raises(ValidationError): 709 validate.ContainsOnly((1, 2, 3))((4,)) 710 with pytest.raises(ValidationError): 711 validate.ContainsOnly(())((1,)) 712 713 714def test_contains_only_in_string(): 715 assert validate.ContainsOnly("")("") == "" 716 assert validate.ContainsOnly("abc")("a") == "a" 717 assert validate.ContainsOnly("aab")("aa") == "aa" 718 assert validate.ContainsOnly("abc")("ab") == "ab" 719 assert validate.ContainsOnly("abc")("ba") == "ba" 720 assert validate.ContainsOnly("abc")("abc") == "abc" 721 assert validate.ContainsOnly("abc")("cab") == "cab" 722 assert validate.ContainsOnly("abc")("bca") == "bca" 723 assert validate.ContainsOnly("abc")("") == "" 724 725 with pytest.raises(ValidationError): 726 validate.ContainsOnly("abc")("d") 727 with pytest.raises(ValidationError): 728 validate.ContainsOnly("")("a") 729 730 731def test_containsonly_custom_message(): 732 containsonly = validate.ContainsOnly( 733 [1, 2, 3], error="{input} is not one of {choices}" 734 ) 735 expected = "4, 5 is not one of 1, 2, 3" 736 with pytest.raises(ValidationError): 737 containsonly([4, 5]) 738 assert expected in str(expected) 739 740 containsonly = validate.ContainsOnly( 741 [1, 2, 3], ["one", "two", "three"], error="{input} is not one of {labels}" 742 ) 743 expected = "4, 5 is not one of one, two, three" 744 with pytest.raises(ValidationError): 745 containsonly([4, 5]) 746 assert expected in str(expected) 747 748 749def test_containsonly_repr(): 750 assert repr( 751 validate.ContainsOnly(choices=[1, 2, 3], labels=None, error=None) 752 ) == "<ContainsOnly(choices=[1, 2, 3], labels=[], error={!r})>".format( 753 "One or more of the choices you made was not in: {choices}." 754 ) 755 assert repr( 756 validate.ContainsOnly(choices=[1, 2, 3], labels=["a", "b", "c"], error="foo") 757 ) == "<ContainsOnly(choices=[1, 2, 3], labels={!r}, error={!r})>".format( 758 ["a", "b", "c"], "foo" 759 ) 760 761 762def test_containsnoneof_error_message(): 763 with pytest.raises( 764 ValidationError, match="One or more of the choices you made was in: 1" 765 ): 766 validate.ContainsNoneOf([1])([1]) 767 768 with pytest.raises( 769 ValidationError, match="One or more of the choices you made was in: 1, 2, 3" 770 ): 771 validate.ContainsNoneOf([1, 2, 3])([1]) 772 773 with pytest.raises( 774 ValidationError, match="One or more of the choices you made was in: one, two" 775 ): 776 validate.ContainsNoneOf(["one", "two"])(["one"]) 777 778 with pytest.raises( 779 ValidationError, match="One or more of the choices you made was in: @, !, &, ?" 780 ): 781 validate.ContainsNoneOf("@!&?")("@") 782 783 784def test_containsnoneof_in_list(): 785 assert validate.ContainsNoneOf([])([]) == [] 786 assert validate.ContainsNoneOf([])([1, 2, 3]) == [1, 2, 3] 787 assert validate.ContainsNoneOf([4])([1, 2, 3]) == [1, 2, 3] 788 assert validate.ContainsNoneOf([2])([1, 3, 4]) == [1, 3, 4] 789 assert validate.ContainsNoneOf([1, 2, 3])([4]) == [4] 790 assert validate.ContainsNoneOf([4])([1, 1, 1, 1]) == [1, 1, 1, 1] 791 792 with pytest.raises(ValidationError): 793 validate.ContainsNoneOf([1])([1, 2, 3]) 794 795 with pytest.raises(ValidationError): 796 validate.ContainsNoneOf([1, 1, 1])([1, 2, 3]) 797 798 with pytest.raises(ValidationError): 799 validate.ContainsNoneOf([1, 2])([1, 2]) 800 801 with pytest.raises(ValidationError): 802 validate.ContainsNoneOf([1])([1, 1, 1, 1]) 803 804 805def test_containsnoneof_unhashable_types(): 806 assert validate.ContainsNoneOf([[1], [2], [3]])([]) == [] 807 assert validate.ContainsNoneOf([[1], [2], [3]])([[4]]) == [[4]] 808 assert validate.ContainsNoneOf([[1], [2], [3]])([[4], [4]]) == [[4], [4]] 809 assert validate.ContainsNoneOf([[1], [2], [3]])([[4], [5]]) == [[4], [5]] 810 811 with pytest.raises(ValidationError): 812 validate.ContainsNoneOf([[1], [2], [3]])([[1]]) 813 814 with pytest.raises(ValidationError): 815 validate.ContainsNoneOf([[1], [2], [3]])([[1], [2]]) 816 817 with pytest.raises(ValidationError): 818 validate.ContainsNoneOf([[1], [2], [3]])([[2], [1]]) 819 820 with pytest.raises(ValidationError): 821 validate.ContainsNoneOf([[1], [2], [3]])([[1], [2], [3]]) 822 823 with pytest.raises(ValidationError): 824 validate.ContainsNoneOf([[1], [2], [3]])([[3], [2], [1]]) 825 826 with pytest.raises(ValidationError): 827 validate.ContainsNoneOf([[1], [2], [3]])([[2], [3], [1]]) 828 829 830def test_containsnoneof_in_tuple(): 831 assert validate.ContainsNoneOf(())(()) == () 832 assert validate.ContainsNoneOf(())((1, 2, 3)) == (1, 2, 3) 833 assert validate.ContainsNoneOf((4,))((1, 2, 3)) == (1, 2, 3) 834 assert validate.ContainsNoneOf((2,))((1, 3, 4)) == (1, 3, 4) 835 assert validate.ContainsNoneOf((1, 2, 3))((4,)) == (4,) 836 assert validate.ContainsNoneOf((4,))((1, 1, 1, 1)) == (1, 1, 1, 1) 837 838 with pytest.raises(ValidationError): 839 validate.ContainsNoneOf((1,))((1, 2, 3)) 840 841 with pytest.raises(ValidationError): 842 validate.ContainsNoneOf((1, 1, 1))((1, 2, 3)) 843 844 with pytest.raises(ValidationError): 845 validate.ContainsNoneOf((1, 2))((1, 2)) 846 847 with pytest.raises(ValidationError): 848 validate.ContainsNoneOf((1,))((1, 1, 1, 1)) 849 850 851def test_containsnoneof_in_string(): 852 assert validate.ContainsNoneOf("")("") == "" 853 assert validate.ContainsNoneOf("")("abc") == "abc" 854 assert validate.ContainsNoneOf("d")("abc") == "abc" 855 assert validate.ContainsNoneOf("b")("acd") == "acd" 856 assert validate.ContainsNoneOf("abc")("d") == "d" 857 assert validate.ContainsNoneOf("d")("aaaa") == "aaaa" 858 859 with pytest.raises(ValidationError): 860 validate.ContainsNoneOf("a")("abc") 861 862 with pytest.raises(ValidationError): 863 validate.ContainsNoneOf("aaa")("abc") 864 865 with pytest.raises(ValidationError): 866 validate.ContainsNoneOf("ab")("ab") 867 868 with pytest.raises(ValidationError): 869 validate.ContainsNoneOf("a")("aaaa") 870 871 872def test_containsnoneof_custom_message(): 873 validator = validate.ContainsNoneOf( 874 [1, 2, 3], error="{input} was in the banned list: {values}" 875 ) 876 expected = "1 was in the banned list: 1, 2, 3" 877 with pytest.raises(ValidationError, match=expected): 878 validator([1]) 879 880 881def test_containsnoneof_mixing_types(): 882 with pytest.raises(ValidationError): 883 validate.ContainsNoneOf("abc")(["a"]) 884 885 with pytest.raises(ValidationError): 886 validate.ContainsNoneOf(["a", "b", "c"])("a") 887 888 with pytest.raises(ValidationError): 889 validate.ContainsNoneOf((1, 2, 3))([1]) 890 891 with pytest.raises(ValidationError): 892 validate.ContainsNoneOf([1, 2, 3])((1,)) 893 894 895def is_even(value): 896 if value % 2 != 0: 897 raise ValidationError("Not an even value.") 898 899 900def test_and(): 901 validator = validate.And(validate.Range(min=0), is_even) 902 assert validator(2) 903 with pytest.raises(ValidationError) as excinfo: 904 validator(-1) 905 errors = excinfo.value.messages 906 assert errors == ["Must be greater than or equal to 0.", "Not an even value."] 907 908 validator_with_composition = validate.And(validator, validate.Range(max=6)) 909 assert validator_with_composition(4) 910 with pytest.raises(ValidationError) as excinfo: 911 validator_with_composition(7) 912 913 errors = excinfo.value.messages 914 assert errors == ["Not an even value.", "Must be less than or equal to 6."] 915