1import pytest 2 3from marshmallow import ( 4 Schema, 5 fields, 6 pre_dump, 7 post_dump, 8 pre_load, 9 post_load, 10 validates, 11 validates_schema, 12 ValidationError, 13 EXCLUDE, 14 INCLUDE, 15 RAISE, 16) 17 18 19@pytest.mark.parametrize("partial_val", (True, False)) 20def test_decorated_processors(partial_val): 21 class ExampleSchema(Schema): 22 """Includes different ways to invoke decorators and set up methods""" 23 24 TAG = "TAG" 25 26 value = fields.Integer(as_string=True) 27 28 # Implicit default raw, pre dump, static method. 29 @pre_dump 30 def increment_value(self, item, **kwargs): 31 assert "many" in kwargs 32 item["value"] += 1 33 return item 34 35 # Implicit default raw, post dump, class method. 36 @post_dump 37 def add_tag(self, item, **kwargs): 38 assert "many" in kwargs 39 item["value"] = self.TAG + item["value"] 40 return item 41 42 # Explicitly raw, post dump, instance method. 43 @post_dump(pass_many=True) 44 def add_envelope(self, data, many, **kwargs): 45 key = self.get_envelope_key(many) 46 return {key: data} 47 48 # Explicitly raw, pre load, instance method. 49 @pre_load(pass_many=True) 50 def remove_envelope(self, data, many, partial, **kwargs): 51 assert partial is partial_val 52 key = self.get_envelope_key(many) 53 return data[key] 54 55 @staticmethod 56 def get_envelope_key(many): 57 return "data" if many else "datum" 58 59 # Explicitly not raw, pre load, instance method. 60 @pre_load(pass_many=False) 61 def remove_tag(self, item, partial, **kwargs): 62 assert partial is partial_val 63 assert "many" in kwargs 64 item["value"] = item["value"][len(self.TAG) :] 65 return item 66 67 # Explicit default raw, post load, instance method. 68 @post_load() 69 def decrement_value(self, item, partial, **kwargs): 70 assert partial is partial_val 71 assert "many" in kwargs 72 item["value"] -= 1 73 return item 74 75 schema = ExampleSchema(partial=partial_val) 76 77 # Need to re-create these because the processors will modify in place. 78 make_item = lambda: {"value": 3} 79 make_items = lambda: [make_item(), {"value": 5}] 80 81 item_dumped = schema.dump(make_item()) 82 assert item_dumped == {"datum": {"value": "TAG4"}} 83 item_loaded = schema.load(item_dumped) 84 assert item_loaded == make_item() 85 86 items_dumped = schema.dump(make_items(), many=True) 87 assert items_dumped == {"data": [{"value": "TAG4"}, {"value": "TAG6"}]} 88 items_loaded = schema.load(items_dumped, many=True) 89 assert items_loaded == make_items() 90 91 92# Regression test for https://github.com/marshmallow-code/marshmallow/issues/347 93@pytest.mark.parametrize("unknown", (EXCLUDE, INCLUDE, RAISE)) 94def test_decorated_processor_returning_none(unknown): 95 class PostSchema(Schema): 96 value = fields.Integer() 97 98 @post_load 99 def load_none(self, item, **kwargs): 100 return None 101 102 @post_dump 103 def dump_none(self, item, **kwargs): 104 return None 105 106 class PreSchema(Schema): 107 value = fields.Integer() 108 109 @pre_load 110 def load_none(self, item, **kwargs): 111 return None 112 113 @pre_dump 114 def dump_none(self, item, **kwargs): 115 return None 116 117 schema = PostSchema(unknown=unknown) 118 assert schema.dump({"value": 3}) is None 119 assert schema.load({"value": 3}) is None 120 schema = PreSchema(unknown=unknown) 121 assert schema.dump({"value": 3}) == {} 122 with pytest.raises(ValidationError) as excinfo: 123 schema.load({"value": 3}) 124 assert excinfo.value.messages == {"_schema": ["Invalid input type."]} 125 126 127class TestPassOriginal: 128 def test_pass_original_single(self): 129 class MySchema(Schema): 130 foo = fields.Field() 131 132 @post_load(pass_original=True) 133 def post_load(self, data, original_data, **kwargs): 134 ret = data.copy() 135 ret["_post_load"] = original_data["sentinel"] 136 return ret 137 138 @post_dump(pass_original=True) 139 def post_dump(self, data, obj, **kwargs): 140 ret = data.copy() 141 ret["_post_dump"] = obj["sentinel"] 142 return ret 143 144 schema = MySchema(unknown=EXCLUDE) 145 datum = {"foo": 42, "sentinel": 24} 146 item_loaded = schema.load(datum) 147 assert item_loaded["foo"] == 42 148 assert item_loaded["_post_load"] == 24 149 150 item_dumped = schema.dump(datum) 151 152 assert item_dumped["foo"] == 42 153 assert item_dumped["_post_dump"] == 24 154 155 def test_pass_original_many(self): 156 class MySchema(Schema): 157 foo = fields.Field() 158 159 @post_load(pass_many=True, pass_original=True) 160 def post_load(self, data, original, many, **kwargs): 161 if many: 162 ret = [] 163 for item, orig_item in zip(data, original): 164 item["_post_load"] = orig_item["sentinel"] 165 ret.append(item) 166 else: 167 ret = data.copy() 168 ret["_post_load"] = original["sentinel"] 169 return ret 170 171 @post_dump(pass_many=True, pass_original=True) 172 def post_dump(self, data, original, many, **kwargs): 173 if many: 174 ret = [] 175 for item, orig_item in zip(data, original): 176 item["_post_dump"] = orig_item["sentinel"] 177 ret.append(item) 178 else: 179 ret = data.copy() 180 ret["_post_dump"] = original["sentinel"] 181 return ret 182 183 schema = MySchema(unknown=EXCLUDE) 184 data = [{"foo": 42, "sentinel": 24}, {"foo": 424, "sentinel": 242}] 185 items_loaded = schema.load(data, many=True) 186 assert items_loaded == [ 187 {"foo": 42, "_post_load": 24}, 188 {"foo": 424, "_post_load": 242}, 189 ] 190 test_values = [e["_post_load"] for e in items_loaded] 191 assert test_values == [24, 242] 192 193 items_dumped = schema.dump(data, many=True) 194 assert items_dumped == [ 195 {"foo": 42, "_post_dump": 24}, 196 {"foo": 424, "_post_dump": 242}, 197 ] 198 199 # Also check load/dump of single item 200 201 datum = {"foo": 42, "sentinel": 24} 202 item_loaded = schema.load(datum, many=False) 203 assert item_loaded == {"foo": 42, "_post_load": 24} 204 205 item_dumped = schema.dump(datum, many=False) 206 assert item_dumped == {"foo": 42, "_post_dump": 24} 207 208 209def test_decorated_processor_inheritance(): 210 class ParentSchema(Schema): 211 @post_dump 212 def inherited(self, item, **kwargs): 213 item["inherited"] = "inherited" 214 return item 215 216 @post_dump 217 def overridden(self, item, **kwargs): 218 item["overridden"] = "base" 219 return item 220 221 @post_dump 222 def deleted(self, item, **kwargs): 223 item["deleted"] = "retained" 224 return item 225 226 class ChildSchema(ParentSchema): 227 @post_dump 228 def overridden(self, item, **kwargs): 229 item["overridden"] = "overridden" 230 return item 231 232 deleted = None 233 234 parent_dumped = ParentSchema().dump({}) 235 assert parent_dumped == { 236 "inherited": "inherited", 237 "overridden": "base", 238 "deleted": "retained", 239 } 240 241 child_dumped = ChildSchema().dump({}) 242 assert child_dumped == {"inherited": "inherited", "overridden": "overridden"} 243 244 245# https://github.com/marshmallow-code/marshmallow/issues/229#issuecomment-138949436 246def test_pre_dump_is_invoked_before_implicit_field_generation(): 247 class Foo(Schema): 248 field = fields.Integer() 249 250 @pre_dump 251 def hook(self, data, **kwargs): 252 data["generated_field"] = 7 253 return data 254 255 class Meta: 256 # Removing generated_field from here drops it from the output 257 fields = ("field", "generated_field") 258 259 assert Foo().dump({"field": 5}) == {"field": 5, "generated_field": 7} 260 261 262class ValidatesSchema(Schema): 263 foo = fields.Int() 264 265 @validates("foo") 266 def validate_foo(self, value): 267 if value != 42: 268 raise ValidationError("The answer to life the universe and everything.") 269 270 271class TestValidatesDecorator: 272 def test_validates(self): 273 class VSchema(Schema): 274 s = fields.String() 275 276 @validates("s") 277 def validate_string(self, data): 278 raise ValidationError("nope") 279 280 with pytest.raises(ValidationError) as excinfo: 281 VSchema().load({"s": "bar"}) 282 283 assert excinfo.value.messages == {"s": ["nope"]} 284 285 # Regression test for https://github.com/marshmallow-code/marshmallow/issues/350 286 def test_validates_with_attribute(self): 287 class S1(Schema): 288 s = fields.String(attribute="string_name") 289 290 @validates("s") 291 def validate_string(self, data): 292 raise ValidationError("nope") 293 294 with pytest.raises(ValidationError) as excinfo: 295 S1().load({"s": "foo"}) 296 assert excinfo.value.messages == {"s": ["nope"]} 297 298 with pytest.raises(ValidationError): 299 S1(many=True).load([{"s": "foo"}]) 300 301 def test_validates_decorator(self): 302 schema = ValidatesSchema() 303 304 errors = schema.validate({"foo": 41}) 305 assert "foo" in errors 306 assert errors["foo"][0] == "The answer to life the universe and everything." 307 308 errors = schema.validate({"foo": 42}) 309 assert errors == {} 310 311 errors = schema.validate([{"foo": 42}, {"foo": 43}], many=True) 312 assert "foo" in errors[1] 313 assert len(errors[1]["foo"]) == 1 314 assert errors[1]["foo"][0] == "The answer to life the universe and everything." 315 316 errors = schema.validate([{"foo": 42}, {"foo": 42}], many=True) 317 assert errors == {} 318 319 errors = schema.validate({}) 320 assert errors == {} 321 322 with pytest.raises(ValidationError) as excinfo: 323 schema.load({"foo": 41}) 324 errors = excinfo.value.messages 325 result = excinfo.value.valid_data 326 assert errors 327 assert result == {} 328 329 with pytest.raises(ValidationError) as excinfo: 330 schema.load([{"foo": 42}, {"foo": 43}], many=True) 331 errors = excinfo.value.messages 332 result = excinfo.value.valid_data 333 assert len(result) == 2 334 assert result[0] == {"foo": 42} 335 assert result[1] == {} 336 assert 1 in errors 337 assert "foo" in errors[1] 338 assert errors[1]["foo"] == ["The answer to life the universe and everything."] 339 340 def test_field_not_present(self): 341 class BadSchema(ValidatesSchema): 342 @validates("bar") 343 def validate_bar(self, value): 344 raise ValidationError("Never raised.") 345 346 schema = BadSchema() 347 348 with pytest.raises(ValueError, match='"bar" field does not exist.'): 349 schema.validate({"foo": 42}) 350 351 def test_precedence(self): 352 class Schema2(ValidatesSchema): 353 foo = fields.Int(validate=lambda n: n != 42) 354 bar = fields.Int(validate=lambda n: n == 1) 355 356 @validates("bar") 357 def validate_bar(self, value): 358 if value != 2: 359 raise ValidationError("Must be 2") 360 361 schema = Schema2() 362 363 errors = schema.validate({"foo": 42}) 364 assert "foo" in errors 365 assert len(errors["foo"]) == 1 366 assert "Invalid value." in errors["foo"][0] 367 368 errors = schema.validate({"bar": 3}) 369 assert "bar" in errors 370 assert len(errors["bar"]) == 1 371 assert "Invalid value." in errors["bar"][0] 372 373 errors = schema.validate({"bar": 1}) 374 assert "bar" in errors 375 assert len(errors["bar"]) == 1 376 assert errors["bar"][0] == "Must be 2" 377 378 # Regression test for https://github.com/marshmallow-code/marshmallow/issues/748 379 def test_validates_with_data_key(self): 380 class BadSchema(Schema): 381 foo = fields.String(data_key="foo-name") 382 383 @validates("foo") 384 def validate_string(self, data): 385 raise ValidationError("nope") 386 387 schema = BadSchema() 388 errors = schema.validate({"foo-name": "data"}) 389 assert "foo-name" in errors 390 assert errors["foo-name"] == ["nope"] 391 392 schema = BadSchema() 393 errors = schema.validate( 394 [{"foo-name": "data"}, {"foo-name": "data2"}], many=True 395 ) 396 assert errors == {0: {"foo-name": ["nope"]}, 1: {"foo-name": ["nope"]}} 397 398 399class TestValidatesSchemaDecorator: 400 def test_validator_nested_many_invalid_data(self): 401 class NestedSchema(Schema): 402 foo = fields.Int(required=True) 403 404 class MySchema(Schema): 405 nested = fields.Nested(NestedSchema, required=True, many=True) 406 407 schema = MySchema() 408 errors = schema.validate({"nested": [1]}) 409 assert errors 410 assert "nested" in errors 411 assert 0 in errors["nested"] 412 assert errors["nested"][0] == {"_schema": ["Invalid input type."]} 413 414 def test_validator_nested_many_schema_error(self): 415 class NestedSchema(Schema): 416 foo = fields.Int(required=True) 417 418 @validates_schema 419 def validate_schema(self, data, **kwargs): 420 raise ValidationError("This will never work.") 421 422 class MySchema(Schema): 423 nested = fields.Nested(NestedSchema, required=True, many=True) 424 425 schema = MySchema() 426 errors = schema.validate({"nested": [{"foo": 1}]}) 427 assert errors 428 assert "nested" in errors 429 assert 0 in errors["nested"] 430 assert errors["nested"][0] == {"_schema": ["This will never work."]} 431 432 def test_validator_nested_many_field_error(self): 433 class NestedSchema(Schema): 434 foo = fields.Int(required=True) 435 436 @validates_schema 437 def validate_schema(self, data, **kwargs): 438 raise ValidationError("This will never work.", "foo") 439 440 class MySchema(Schema): 441 nested = fields.Nested(NestedSchema, required=True, many=True) 442 443 schema = MySchema() 444 errors = schema.validate({"nested": [{"foo": 1}]}) 445 assert errors 446 assert "nested" in errors 447 assert 0 in errors["nested"] 448 assert errors["nested"][0] == {"foo": ["This will never work."]} 449 450 @pytest.mark.parametrize("data", ([{"foo": 1, "bar": 2}],)) 451 @pytest.mark.parametrize( 452 "pass_many,expected_data,expected_original_data", 453 ( 454 [True, [{"foo": 1}], [{"foo": 1, "bar": 2}]], 455 [False, {"foo": 1}, {"foo": 1, "bar": 2}], 456 ), 457 ) 458 def test_validator_nested_many_pass_original_and_pass_many( 459 self, pass_many, data, expected_data, expected_original_data 460 ): 461 class NestedSchema(Schema): 462 foo = fields.Int(required=True) 463 464 @validates_schema(pass_many=pass_many, pass_original=True) 465 def validate_schema(self, data, original_data, many, **kwargs): 466 assert data == expected_data 467 assert original_data == expected_original_data 468 assert many is True 469 raise ValidationError("Method called") 470 471 class MySchema(Schema): 472 nested = fields.Nested( 473 NestedSchema, required=True, many=True, unknown=EXCLUDE 474 ) 475 476 schema = MySchema() 477 errors = schema.validate({"nested": data}) 478 error = errors["nested"] if pass_many else errors["nested"][0] 479 assert error["_schema"][0] == "Method called" 480 481 def test_decorated_validators(self): 482 class MySchema(Schema): 483 foo = fields.Int() 484 bar = fields.Int() 485 486 @validates_schema 487 def validate_schema(self, data, **kwargs): 488 if data["foo"] <= 3: 489 raise ValidationError("Must be greater than 3") 490 491 @validates_schema(pass_many=True) 492 def validate_raw(self, data, many, **kwargs): 493 if many: 494 assert type(data) is list 495 if len(data) < 2: 496 raise ValidationError("Must provide at least 2 items") 497 498 @validates_schema 499 def validate_bar(self, data, **kwargs): 500 if "bar" in data and data["bar"] < 0: 501 raise ValidationError("bar must not be negative", "bar") 502 503 schema = MySchema() 504 errors = schema.validate({"foo": 3}) 505 assert "_schema" in errors 506 assert errors["_schema"][0] == "Must be greater than 3" 507 508 errors = schema.validate([{"foo": 4}], many=True) 509 assert "_schema" in errors 510 assert len(errors["_schema"]) == 1 511 assert errors["_schema"][0] == "Must provide at least 2 items" 512 513 errors = schema.validate({"foo": 4, "bar": -1}) 514 assert "bar" in errors 515 assert len(errors["bar"]) == 1 516 assert errors["bar"][0] == "bar must not be negative" 517 518 def test_multiple_validators(self): 519 class MySchema(Schema): 520 foo = fields.Int() 521 bar = fields.Int() 522 523 @validates_schema 524 def validate_schema(self, data, **kwargs): 525 if data["foo"] <= 3: 526 raise ValidationError("Must be greater than 3") 527 528 @validates_schema 529 def validate_bar(self, data, **kwargs): 530 if "bar" in data and data["bar"] < 0: 531 raise ValidationError("bar must not be negative") 532 533 schema = MySchema() 534 errors = schema.validate({"foo": 3, "bar": -1}) 535 assert type(errors) is dict 536 assert "_schema" in errors 537 assert len(errors["_schema"]) == 2 538 assert "Must be greater than 3" in errors["_schema"] 539 assert "bar must not be negative" in errors["_schema"] 540 541 errors = schema.validate([{"foo": 3, "bar": -1}, {"foo": 3}], many=True) 542 assert type(errors) is dict 543 assert "_schema" in errors[0] 544 assert len(errors[0]["_schema"]) == 2 545 assert "Must be greater than 3" in errors[0]["_schema"] 546 assert "bar must not be negative" in errors[0]["_schema"] 547 assert len(errors[1]["_schema"]) == 1 548 assert "Must be greater than 3" in errors[0]["_schema"] 549 550 def test_multiple_validators_merge_dict_errors(self): 551 class NestedSchema(Schema): 552 foo = fields.Int() 553 bar = fields.Int() 554 555 class MySchema(Schema): 556 nested = fields.Nested(NestedSchema) 557 558 @validates_schema 559 def validate_nested_foo(self, data, **kwargs): 560 raise ValidationError({"nested": {"foo": ["Invalid foo"]}}) 561 562 @validates_schema 563 def validate_nested_bar_1(self, data, **kwargs): 564 raise ValidationError({"nested": {"bar": ["Invalid bar 1"]}}) 565 566 @validates_schema 567 def validate_nested_bar_2(self, data, **kwargs): 568 raise ValidationError({"nested": {"bar": ["Invalid bar 2"]}}) 569 570 with pytest.raises(ValidationError) as excinfo: 571 MySchema().load({"nested": {"foo": 1, "bar": 2}}) 572 573 assert excinfo.value.messages == { 574 "nested": { 575 "foo": ["Invalid foo"], 576 "bar": ["Invalid bar 1", "Invalid bar 2"], 577 } 578 } 579 580 def test_passing_original_data(self): 581 class MySchema(Schema): 582 foo = fields.Int() 583 bar = fields.Int() 584 585 @validates_schema(pass_original=True) 586 def validate_original(self, data, original_data, partial, **kwargs): 587 if isinstance(original_data, dict) and isinstance( 588 original_data["foo"], str 589 ): 590 raise ValidationError("foo cannot be a string") 591 592 @validates_schema(pass_many=True, pass_original=True) 593 def validate_original_bar(self, data, original_data, many, **kwargs): 594 def check(datum): 595 if isinstance(datum, dict) and isinstance(datum["bar"], str): 596 raise ValidationError("bar cannot be a string") 597 598 if many: 599 for each in original_data: 600 check(each) 601 else: 602 check(original_data) 603 604 schema = MySchema() 605 606 errors = schema.validate({"foo": "4", "bar": 12}) 607 assert errors["_schema"] == ["foo cannot be a string"] 608 609 errors = schema.validate({"foo": 4, "bar": "42"}) 610 assert errors["_schema"] == ["bar cannot be a string"] 611 612 errors = schema.validate([{"foo": 4, "bar": "42"}], many=True) 613 assert errors["_schema"] == ["bar cannot be a string"] 614 615 def test_allow_reporting_field_errors_in_schema_validator(self): 616 class NestedSchema(Schema): 617 baz = fields.Int(required=True) 618 619 class MySchema(Schema): 620 foo = fields.Int(required=True) 621 bar = fields.Nested(NestedSchema, required=True) 622 bam = fields.Int(required=True) 623 624 @validates_schema(skip_on_field_errors=True) 625 def consistency_validation(self, data, **kwargs): 626 errors = {} 627 if data["bar"]["baz"] != data["foo"]: 628 errors["bar"] = {"baz": "Non-matching value"} 629 if data["bam"] > data["foo"]: 630 errors["bam"] = "Value should be less than foo" 631 if errors: 632 raise ValidationError(errors) 633 634 schema = MySchema() 635 errors = schema.validate({"foo": 2, "bar": {"baz": 5}, "bam": 6}) 636 assert errors["bar"]["baz"] == "Non-matching value" 637 assert errors["bam"] == "Value should be less than foo" 638 639 # https://github.com/marshmallow-code/marshmallow/issues/273 640 def test_allow_arbitrary_field_names_in_error(self): 641 class MySchema(Schema): 642 @validates_schema 643 def validator(self, data, **kwargs): 644 raise ValidationError("Error message", "arbitrary_key") 645 646 errors = MySchema().validate({}) 647 assert errors["arbitrary_key"] == ["Error message"] 648 649 def test_skip_on_field_errors(self): 650 class MySchema(Schema): 651 foo = fields.Int(required=True, validate=lambda n: n == 3) 652 bar = fields.Int(required=True) 653 654 @validates_schema(skip_on_field_errors=True) 655 def validate_schema(self, data, **kwargs): 656 if data["foo"] != data["bar"]: 657 raise ValidationError("Foo and bar must be equal.") 658 659 @validates_schema(skip_on_field_errors=True, pass_many=True) 660 def validate_many(self, data, many, **kwargs): 661 if many: 662 assert type(data) is list 663 if len(data) < 2: 664 raise ValidationError("Must provide at least 2 items") 665 666 schema = MySchema() 667 # check that schema errors still occur with no field errors 668 errors = schema.validate({"foo": 3, "bar": 4}) 669 assert "_schema" in errors 670 assert errors["_schema"][0] == "Foo and bar must be equal." 671 672 errors = schema.validate([{"foo": 3, "bar": 3}], many=True) 673 assert "_schema" in errors 674 assert errors["_schema"][0] == "Must provide at least 2 items" 675 676 # check that schema errors don't occur when field errors do 677 errors = schema.validate({"foo": 3, "bar": "not an int"}) 678 assert "bar" in errors 679 assert "_schema" not in errors 680 681 errors = schema.validate({"foo": 2, "bar": 2}) 682 assert "foo" in errors 683 assert "_schema" not in errors 684 685 errors = schema.validate([{"foo": 3, "bar": "not an int"}], many=True) 686 assert "bar" in errors[0] 687 assert "_schema" not in errors 688 689 690def test_decorator_error_handling(): # noqa: C901 691 class ExampleSchema(Schema): 692 foo = fields.Int() 693 bar = fields.Int() 694 695 @pre_load() 696 def pre_load_error1(self, item, **kwargs): 697 if item["foo"] != 0: 698 return item 699 errors = {"foo": ["preloadmsg1"], "bar": ["preloadmsg2", "preloadmsg3"]} 700 raise ValidationError(errors) 701 702 @pre_load() 703 def pre_load_error2(self, item, **kwargs): 704 if item["foo"] != 4: 705 return item 706 raise ValidationError("preloadmsg1", "foo") 707 708 @pre_load() 709 def pre_load_error3(self, item, **kwargs): 710 if item["foo"] != 8: 711 return item 712 raise ValidationError("preloadmsg1") 713 714 @post_load() 715 def post_load_error1(self, item, **kwargs): 716 if item["foo"] != 1: 717 return item 718 errors = {"foo": ["postloadmsg1"], "bar": ["postloadmsg2", "postloadmsg3"]} 719 raise ValidationError(errors) 720 721 @post_load() 722 def post_load_error2(self, item, **kwargs): 723 if item["foo"] != 5: 724 return item 725 raise ValidationError("postloadmsg1", "foo") 726 727 def make_item(foo, bar): 728 data = schema.load({"foo": foo, "bar": bar}) 729 assert data is not None 730 return data 731 732 schema = ExampleSchema() 733 with pytest.raises(ValidationError) as excinfo: 734 schema.load({"foo": 0, "bar": 1}) 735 errors = excinfo.value.messages 736 assert "foo" in errors 737 assert len(errors["foo"]) == 1 738 assert errors["foo"][0] == "preloadmsg1" 739 assert "bar" in errors 740 assert len(errors["bar"]) == 2 741 assert "preloadmsg2" in errors["bar"] 742 assert "preloadmsg3" in errors["bar"] 743 with pytest.raises(ValidationError) as excinfo: 744 schema.load({"foo": 1, "bar": 1}) 745 errors = excinfo.value.messages 746 assert "foo" in errors 747 assert len(errors["foo"]) == 1 748 assert errors["foo"][0] == "postloadmsg1" 749 assert "bar" in errors 750 assert len(errors["bar"]) == 2 751 assert "postloadmsg2" in errors["bar"] 752 assert "postloadmsg3" in errors["bar"] 753 with pytest.raises(ValidationError) as excinfo: 754 schema.load({"foo": 4, "bar": 1}) 755 errors = excinfo.value.messages 756 assert len(errors) == 1 757 assert "foo" in errors 758 assert len(errors["foo"]) == 1 759 assert errors["foo"][0] == "preloadmsg1" 760 with pytest.raises(ValidationError) as excinfo: 761 schema.load({"foo": 5, "bar": 1}) 762 errors = excinfo.value.messages 763 assert len(errors) == 1 764 assert "foo" in errors 765 assert len(errors["foo"]) == 1 766 assert errors["foo"][0] == "postloadmsg1" 767 with pytest.raises(ValidationError) as excinfo: 768 schema.load({"foo": 8, "bar": 1}) 769 errors = excinfo.value.messages 770 assert len(errors) == 1 771 assert "_schema" in errors 772 assert len(errors["_schema"]) == 1 773 assert errors["_schema"][0] == "preloadmsg1" 774 775 776@pytest.mark.parametrize("decorator", [pre_load, post_load]) 777def test_decorator_error_handling_with_load(decorator): 778 class ExampleSchema(Schema): 779 @decorator 780 def raise_value_error(self, item, **kwargs): 781 raise ValidationError({"foo": "error"}) 782 783 schema = ExampleSchema() 784 with pytest.raises(ValidationError) as exc: 785 schema.load({}) 786 assert exc.value.messages == {"foo": "error"} 787 schema.dump(object()) 788 789 790@pytest.mark.parametrize("decorator", [pre_load, post_load]) 791def test_decorator_error_handling_with_load_dict_error(decorator): 792 class ExampleSchema(Schema): 793 @decorator 794 def raise_value_error(self, item, **kwargs): 795 raise ValidationError({"foo": "error"}, "nested_field") 796 797 schema = ExampleSchema() 798 with pytest.raises(ValidationError) as exc: 799 schema.load({}) 800 assert exc.value.messages == {"nested_field": {"foo": "error"}} 801 schema.dump(object()) 802 803 804@pytest.mark.parametrize("decorator", [pre_dump, post_dump]) 805def test_decorator_error_handling_with_dump(decorator): 806 class ExampleSchema(Schema): 807 @decorator 808 def raise_value_error(self, item, **kwargs): 809 raise ValidationError({"foo": "error"}) 810 811 schema = ExampleSchema() 812 with pytest.raises(ValidationError) as exc: 813 schema.dump(object()) 814 assert exc.value.messages == {"foo": "error"} 815 schema.load({}) 816 817 818class Nested: 819 def __init__(self, foo): 820 self.foo = foo 821 822 823class Example: 824 def __init__(self, nested): 825 self.nested = nested 826 827 828example = Example(nested=[Nested(x) for x in range(1)]) 829 830 831@pytest.mark.parametrize( 832 "data,expected_data,expected_original_data", 833 ([example, {"foo": 0}, example.nested[0]],), 834) 835def test_decorator_post_dump_with_nested_original_and_pass_many( 836 data, expected_data, expected_original_data 837): 838 class NestedSchema(Schema): 839 foo = fields.Int(required=True) 840 841 @post_dump(pass_many=False, pass_original=True) 842 def check_pass_original_when_pass_many_false( 843 self, data, original_data, **kwargs 844 ): 845 assert data == expected_data 846 assert original_data == expected_original_data 847 return data 848 849 @post_dump(pass_many=True, pass_original=True) 850 def check_pass_original_when_pass_many_true( 851 self, data, original_data, many, **kwargs 852 ): 853 assert many is True 854 assert data == [expected_data] 855 assert original_data == [expected_original_data] 856 return data 857 858 class ExampleSchema(Schema): 859 nested = fields.Nested(NestedSchema, required=True, many=True) 860 861 schema = ExampleSchema() 862 assert schema.dump(data) == {"nested": [{"foo": 0}]} 863 864 865@pytest.mark.parametrize( 866 "data,expected_data,expected_original_data", 867 ([{"nested": [{"foo": 0}]}, {"foo": 0}, {"foo": 0}],), 868) 869def test_decorator_post_load_with_nested_original_and_pass_many( 870 data, expected_data, expected_original_data 871): 872 class NestedSchema(Schema): 873 foo = fields.Int(required=True) 874 875 @post_load(pass_many=False, pass_original=True) 876 def check_pass_original_when_pass_many_false( 877 self, data, original_data, **kwargs 878 ): 879 assert data == expected_data 880 assert original_data == expected_original_data 881 return data 882 883 @post_load(pass_many=True, pass_original=True) 884 def check_pass_original_when_pass_many_true( 885 self, data, original_data, many, **kwargs 886 ): 887 assert many is True 888 assert data == [expected_data] 889 assert original_data == [expected_original_data] 890 return data 891 892 class ExampleSchema(Schema): 893 nested = fields.Nested(NestedSchema, required=True, many=True) 894 895 schema = ExampleSchema() 896 assert schema.load(data) == data 897