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