1import datetime as dt
2import uuid
3import ipaddress
4import decimal
5import math
6
7import pytest
8
9from marshmallow import EXCLUDE, INCLUDE, RAISE, fields, Schema, validate
10from marshmallow.exceptions import ValidationError
11from marshmallow.validate import Equal
12
13from tests.base import assert_date_equal, assert_time_equal, central, ALL_FIELDS
14
15
16class TestDeserializingNone:
17    @pytest.mark.parametrize("FieldClass", ALL_FIELDS)
18    def test_fields_allow_none_deserialize_to_none(self, FieldClass):
19        field = FieldClass(allow_none=True)
20        assert field.deserialize(None) is None
21
22    # https://github.com/marshmallow-code/marshmallow/issues/111
23    @pytest.mark.parametrize("FieldClass", ALL_FIELDS)
24    def test_fields_dont_allow_none_by_default(self, FieldClass):
25        field = FieldClass()
26        with pytest.raises(ValidationError, match="Field may not be null."):
27            field.deserialize(None)
28
29    def test_allow_none_is_true_if_missing_is_true(self):
30        field = fields.Field(load_default=None)
31        assert field.allow_none is True
32        assert field.deserialize(None) is None
33
34    def test_list_field_deserialize_none_to_none(self):
35        field = fields.List(fields.String(allow_none=True), allow_none=True)
36        assert field.deserialize(None) is None
37
38    def test_tuple_field_deserialize_none_to_none(self):
39        field = fields.Tuple([fields.String()], allow_none=True)
40        assert field.deserialize(None) is None
41
42    def test_list_of_nested_allow_none_deserialize_none_to_none(self):
43        field = fields.List(fields.Nested(Schema(), allow_none=True))
44        assert field.deserialize([None]) == [None]
45
46    def test_list_of_nested_non_allow_none_deserialize_none_to_validation_error(self):
47        field = fields.List(fields.Nested(Schema(), allow_none=False))
48        with pytest.raises(ValidationError):
49            field.deserialize([None])
50
51
52class TestFieldDeserialization:
53    def test_float_field_deserialization(self):
54        field = fields.Float()
55        assert math.isclose(field.deserialize("12.3"), 12.3)
56        assert math.isclose(field.deserialize(12.3), 12.3)
57
58    @pytest.mark.parametrize("in_val", ["bad", "", {}, True, False])
59    def test_invalid_float_field_deserialization(self, in_val):
60        field = fields.Float()
61        with pytest.raises(ValidationError) as excinfo:
62            field.deserialize(in_val)
63        assert excinfo.value.args[0] == "Not a valid number."
64
65    def test_float_field_overflow(self):
66        field = fields.Float()
67        with pytest.raises(ValidationError) as excinfo:
68            field.deserialize(2 ** 1024)
69        assert excinfo.value.args[0] == "Number too large."
70
71    def test_integer_field_deserialization(self):
72        field = fields.Integer()
73        assert field.deserialize("42") == 42
74        with pytest.raises(ValidationError) as excinfo:
75            field.deserialize("42.0")
76        assert excinfo.value.args[0] == "Not a valid integer."
77        with pytest.raises(ValidationError):
78            field.deserialize("bad")
79        assert excinfo.value.args[0] == "Not a valid integer."
80        with pytest.raises(ValidationError):
81            field.deserialize({})
82        assert excinfo.value.args[0] == "Not a valid integer."
83
84    def test_strict_integer_field_deserialization(self):
85        field = fields.Integer(strict=True)
86        assert field.deserialize(42) == 42
87        with pytest.raises(ValidationError) as excinfo:
88            field.deserialize(42.0)
89        assert excinfo.value.args[0] == "Not a valid integer."
90        with pytest.raises(ValidationError) as excinfo:
91            field.deserialize(decimal.Decimal("42.0"))
92        assert excinfo.value.args[0] == "Not a valid integer."
93        with pytest.raises(ValidationError) as excinfo:
94            field.deserialize("42")
95        assert excinfo.value.args[0] == "Not a valid integer."
96
97    def test_decimal_field_deserialization(self):
98        m1 = 12
99        m2 = "12.355"
100        m3 = decimal.Decimal(1)
101        m4 = 3.14
102        m5 = "abc"
103        m6 = [1, 2]
104
105        field = fields.Decimal()
106        assert isinstance(field.deserialize(m1), decimal.Decimal)
107        assert field.deserialize(m1) == decimal.Decimal(12)
108        assert isinstance(field.deserialize(m2), decimal.Decimal)
109        assert field.deserialize(m2) == decimal.Decimal("12.355")
110        assert isinstance(field.deserialize(m3), decimal.Decimal)
111        assert field.deserialize(m3) == decimal.Decimal(1)
112        assert isinstance(field.deserialize(m4), decimal.Decimal)
113        assert field.deserialize(m4).as_tuple() == (0, (3, 1, 4), -2)
114        with pytest.raises(ValidationError) as excinfo:
115            field.deserialize(m5)
116        assert excinfo.value.args[0] == "Not a valid number."
117        with pytest.raises(ValidationError) as excinfo:
118            field.deserialize(m6)
119        assert excinfo.value.args[0] == "Not a valid number."
120
121    def test_decimal_field_with_places(self):
122        m1 = 12
123        m2 = "12.355"
124        m3 = decimal.Decimal(1)
125        m4 = "abc"
126        m5 = [1, 2]
127
128        field = fields.Decimal(1)
129        assert isinstance(field.deserialize(m1), decimal.Decimal)
130        assert field.deserialize(m1) == decimal.Decimal(12)
131        assert isinstance(field.deserialize(m2), decimal.Decimal)
132        assert field.deserialize(m2) == decimal.Decimal("12.4")
133        assert isinstance(field.deserialize(m3), decimal.Decimal)
134        assert field.deserialize(m3) == decimal.Decimal(1)
135        with pytest.raises(ValidationError) as excinfo:
136            field.deserialize(m4)
137        assert excinfo.value.args[0] == "Not a valid number."
138        with pytest.raises(ValidationError) as excinfo:
139            field.deserialize(m5)
140        assert excinfo.value.args[0] == "Not a valid number."
141
142    def test_decimal_field_with_places_and_rounding(self):
143        m1 = 12
144        m2 = "12.355"
145        m3 = decimal.Decimal(1)
146        m4 = "abc"
147        m5 = [1, 2]
148
149        field = fields.Decimal(1, decimal.ROUND_DOWN)
150        assert isinstance(field.deserialize(m1), decimal.Decimal)
151        assert field.deserialize(m1) == decimal.Decimal(12)
152        assert isinstance(field.deserialize(m2), decimal.Decimal)
153        assert field.deserialize(m2) == decimal.Decimal("12.3")
154        assert isinstance(field.deserialize(m3), decimal.Decimal)
155        assert field.deserialize(m3) == decimal.Decimal(1)
156        with pytest.raises(ValidationError):
157            field.deserialize(m4)
158        with pytest.raises(ValidationError):
159            field.deserialize(m5)
160
161    def test_decimal_field_deserialization_string(self):
162        m1 = 12
163        m2 = "12.355"
164        m3 = decimal.Decimal(1)
165        m4 = "abc"
166        m5 = [1, 2]
167
168        field = fields.Decimal(as_string=True)
169        assert isinstance(field.deserialize(m1), decimal.Decimal)
170        assert field.deserialize(m1) == decimal.Decimal(12)
171        assert isinstance(field.deserialize(m2), decimal.Decimal)
172        assert field.deserialize(m2) == decimal.Decimal("12.355")
173        assert isinstance(field.deserialize(m3), decimal.Decimal)
174        assert field.deserialize(m3) == decimal.Decimal(1)
175        with pytest.raises(ValidationError):
176            field.deserialize(m4)
177        with pytest.raises(ValidationError):
178            field.deserialize(m5)
179
180    def test_decimal_field_special_values(self):
181        m1 = "-NaN"
182        m2 = "NaN"
183        m3 = "-sNaN"
184        m4 = "sNaN"
185        m5 = "-Infinity"
186        m6 = "Infinity"
187        m7 = "-0"
188
189        field = fields.Decimal(places=2, allow_nan=True)
190
191        m1d = field.deserialize(m1)
192        assert isinstance(m1d, decimal.Decimal)
193        assert m1d.is_qnan() and not m1d.is_signed()
194
195        m2d = field.deserialize(m2)
196        assert isinstance(m2d, decimal.Decimal)
197        assert m2d.is_qnan() and not m2d.is_signed()
198
199        m3d = field.deserialize(m3)
200        assert isinstance(m3d, decimal.Decimal)
201        assert m3d.is_qnan() and not m3d.is_signed()
202
203        m4d = field.deserialize(m4)
204        assert isinstance(m4d, decimal.Decimal)
205        assert m4d.is_qnan() and not m4d.is_signed()
206
207        m5d = field.deserialize(m5)
208        assert isinstance(m5d, decimal.Decimal)
209        assert m5d.is_infinite() and m5d.is_signed()
210
211        m6d = field.deserialize(m6)
212        assert isinstance(m6d, decimal.Decimal)
213        assert m6d.is_infinite() and not m6d.is_signed()
214
215        m7d = field.deserialize(m7)
216        assert isinstance(m7d, decimal.Decimal)
217        assert m7d.is_zero() and m7d.is_signed()
218
219    def test_decimal_field_special_values_not_permitted(self):
220        m1 = "-NaN"
221        m2 = "NaN"
222        m3 = "-sNaN"
223        m4 = "sNaN"
224        m5 = "-Infinity"
225        m6 = "Infinity"
226        m7 = "-0"
227
228        field = fields.Decimal(places=2)
229
230        with pytest.raises(ValidationError) as excinfo:
231            field.deserialize(m1)
232        assert str(excinfo.value.args[0]) == (
233            "Special numeric values (nan or infinity) are not permitted."
234        )
235        with pytest.raises(ValidationError):
236            field.deserialize(m2)
237        with pytest.raises(ValidationError):
238            field.deserialize(m3)
239        with pytest.raises(ValidationError):
240            field.deserialize(m4)
241        with pytest.raises(ValidationError):
242            field.deserialize(m5)
243        with pytest.raises(ValidationError):
244            field.deserialize(m6)
245
246        m7d = field.deserialize(m7)
247        assert isinstance(m7d, decimal.Decimal)
248        assert m7d.is_zero() and m7d.is_signed()
249
250    @pytest.mark.parametrize("allow_nan", (None, False, True))
251    @pytest.mark.parametrize("value", ("nan", "-nan", "inf", "-inf"))
252    def test_float_field_allow_nan(self, value, allow_nan):
253
254        if allow_nan is None:
255            # Test default case is False
256            field = fields.Float()
257        else:
258            field = fields.Float(allow_nan=allow_nan)
259
260        if allow_nan is True:
261            res = field.deserialize(value)
262            assert isinstance(res, float)
263            if value.endswith("nan"):
264                assert math.isnan(res)
265            else:
266                assert res == float(value)
267        else:
268            with pytest.raises(ValidationError) as excinfo:
269                field.deserialize(value)
270            assert str(excinfo.value.args[0]) == (
271                "Special numeric values (nan or infinity) are not permitted."
272            )
273
274    def test_string_field_deserialization(self):
275        field = fields.String()
276        assert field.deserialize("foo") == "foo"
277        assert field.deserialize(b"foo") == "foo"
278
279        # https://github.com/marshmallow-code/marshmallow/issues/231
280        with pytest.raises(ValidationError) as excinfo:
281            field.deserialize(42)
282        assert excinfo.value.args[0] == "Not a valid string."
283
284        with pytest.raises(ValidationError):
285            field.deserialize({})
286
287    def test_boolean_field_deserialization(self):
288        field = fields.Boolean()
289        assert field.deserialize(True) is True
290        assert field.deserialize(False) is False
291        assert field.deserialize("True") is True
292        assert field.deserialize("False") is False
293        assert field.deserialize("true") is True
294        assert field.deserialize("false") is False
295        assert field.deserialize("1") is True
296        assert field.deserialize("0") is False
297        assert field.deserialize("on") is True
298        assert field.deserialize("ON") is True
299        assert field.deserialize("On") is True
300        assert field.deserialize("off") is False
301        assert field.deserialize("OFF") is False
302        assert field.deserialize("Off") is False
303        assert field.deserialize("y") is True
304        assert field.deserialize("Y") is True
305        assert field.deserialize("yes") is True
306        assert field.deserialize("YES") is True
307        assert field.deserialize("Yes") is True
308        assert field.deserialize("n") is False
309        assert field.deserialize("N") is False
310        assert field.deserialize("no") is False
311        assert field.deserialize("NO") is False
312        assert field.deserialize("No") is False
313        assert field.deserialize(1) is True
314        assert field.deserialize(0) is False
315
316        with pytest.raises(ValidationError) as excinfo:
317            field.deserialize({})
318        assert excinfo.value.args[0] == "Not a valid boolean."
319
320        with pytest.raises(ValidationError) as excinfo:
321            field.deserialize(42)
322
323        with pytest.raises(ValidationError) as excinfo:
324            field.deserialize("invalid-string")
325
326    def test_boolean_field_deserialization_with_custom_truthy_values(self):
327        class MyBoolean(fields.Boolean):
328            truthy = {"yep"}
329
330        field = MyBoolean()
331        assert field.deserialize("yep") is True
332
333        field = fields.Boolean(truthy=("yep",))
334        assert field.deserialize("yep") is True
335        assert field.deserialize(False) is False
336
337    @pytest.mark.parametrize("in_val", ["notvalid", 123])
338    def test_boolean_field_deserialization_with_custom_truthy_values_invalid(
339        self, in_val
340    ):
341        class MyBoolean(fields.Boolean):
342            truthy = {"yep"}
343
344        field = MyBoolean()
345        with pytest.raises(ValidationError) as excinfo:
346            field.deserialize(in_val)
347        expected_msg = "Not a valid boolean."
348        assert str(excinfo.value.args[0]) == expected_msg
349
350        field = fields.Boolean(truthy=("yep",))
351        with pytest.raises(ValidationError) as excinfo:
352            field.deserialize(in_val)
353        expected_msg = "Not a valid boolean."
354        assert str(excinfo.value.args[0]) == expected_msg
355
356        field2 = MyBoolean(error_messages={"invalid": "bad input"})
357        with pytest.raises(ValidationError) as excinfo:
358            field2.deserialize(in_val)
359        assert str(excinfo.value.args[0]) == "bad input"
360
361        field2 = fields.Boolean(
362            truthy=("yep",), error_messages={"invalid": "bad input"}
363        )
364
365    def test_boolean_field_deserialization_with_empty_truthy(self):
366        field = fields.Boolean(truthy=())
367        assert field.deserialize("yep") is True
368        assert field.deserialize(True) is True
369        assert field.deserialize(False) is False
370
371    def test_boolean_field_deserialization_with_custom_falsy_values(self):
372        field = fields.Boolean(falsy=("nope",))
373        assert field.deserialize("nope") is False
374        assert field.deserialize(True) is True
375
376    def test_field_toggle_show_invalid_value_in_error_message(self):
377        error_messages = {"invalid": "Not valid: {input}"}
378        boolfield = fields.Boolean(error_messages=error_messages)
379        with pytest.raises(ValidationError) as excinfo:
380            boolfield.deserialize("notabool")
381        assert str(excinfo.value.args[0]) == "Not valid: notabool"
382
383        numfield = fields.Number(error_messages=error_messages)
384        with pytest.raises(ValidationError) as excinfo:
385            numfield.deserialize("notanum")
386        assert str(excinfo.value.args[0]) == "Not valid: notanum"
387
388        intfield = fields.Integer(error_messages=error_messages)
389        with pytest.raises(ValidationError) as excinfo:
390            intfield.deserialize("notanint")
391        assert str(excinfo.value.args[0]) == "Not valid: notanint"
392
393        date_error_messages = {"invalid": "Not a valid {obj_type}: {input}"}
394        datefield = fields.DateTime(error_messages=date_error_messages)
395        with pytest.raises(ValidationError) as excinfo:
396            datefield.deserialize("notadate")
397        assert str(excinfo.value.args[0]) == "Not a valid datetime: notadate"
398
399    @pytest.mark.parametrize(
400        "in_value",
401        [
402            "not-a-datetime",
403            42,
404            "",
405            [],
406            "2018",
407            "2018-01-01",
408            dt.datetime.now().strftime("%H:%M:%S %Y-%m-%d"),
409            dt.datetime.now().strftime("%m-%d-%Y %H:%M:%S"),
410        ],
411    )
412    def test_invalid_datetime_deserialization(self, in_value):
413        field = fields.DateTime()
414        with pytest.raises(ValidationError, match="Not a valid datetime."):
415            field.deserialize(in_value)
416
417    def test_custom_date_format_datetime_field_deserialization(self):
418        # Datetime string with format "%H:%M:%S.%f %Y-%m-%d"
419        datestring = "10:11:12.123456 2019-01-02"
420
421        # Deserialization should fail when datestring is not of same format
422        field = fields.DateTime(format="%d-%m-%Y %H:%M:%S")
423        with pytest.raises(ValidationError, match="Not a valid datetime."):
424            field.deserialize(datestring)
425
426        field = fields.DateTime(format="%H:%M:%S.%f %Y-%m-%d")
427        assert field.deserialize(datestring) == dt.datetime(
428            2019, 1, 2, 10, 11, 12, 123456
429        )
430
431        field = fields.NaiveDateTime(format="%H:%M:%S.%f %Y-%m-%d")
432        assert field.deserialize(datestring) == dt.datetime(
433            2019, 1, 2, 10, 11, 12, 123456
434        )
435
436        field = fields.AwareDateTime(format="%H:%M:%S.%f %Y-%m-%d")
437        with pytest.raises(ValidationError, match="Not a valid aware datetime."):
438            field.deserialize(datestring)
439
440    @pytest.mark.parametrize("fmt", ["rfc", "rfc822"])
441    @pytest.mark.parametrize(
442        ("value", "expected", "aware"),
443        [
444            (
445                "Sun, 10 Nov 2013 01:23:45 -0000",
446                dt.datetime(2013, 11, 10, 1, 23, 45),
447                False,
448            ),
449            (
450                "Sun, 10 Nov 2013 01:23:45 +0000",
451                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
452                True,
453            ),
454            (
455                "Sun, 10 Nov 2013 01:23:45 -0600",
456                central.localize(dt.datetime(2013, 11, 10, 1, 23, 45), is_dst=False),
457                True,
458            ),
459        ],
460    )
461    def test_rfc_datetime_field_deserialization(self, fmt, value, expected, aware):
462        field = fields.DateTime(format=fmt)
463        assert field.deserialize(value) == expected
464        field = fields.NaiveDateTime(format=fmt)
465        if aware:
466            with pytest.raises(ValidationError, match="Not a valid naive datetime."):
467                field.deserialize(value)
468        else:
469            assert field.deserialize(value) == expected
470        field = fields.AwareDateTime(format=fmt)
471        if not aware:
472            with pytest.raises(ValidationError, match="Not a valid aware datetime."):
473                field.deserialize(value)
474        else:
475            assert field.deserialize(value) == expected
476
477    @pytest.mark.parametrize("fmt", ["iso", "iso8601"])
478    @pytest.mark.parametrize(
479        ("value", "expected", "aware"),
480        [
481            ("2013-11-10T01:23:45", dt.datetime(2013, 11, 10, 1, 23, 45), False),
482            (
483                "2013-11-10T01:23:45+00:00",
484                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
485                True,
486            ),
487            (
488                # Regression test for https://github.com/marshmallow-code/marshmallow/issues/1251
489                "2013-11-10T01:23:45.123+00:00",
490                dt.datetime(2013, 11, 10, 1, 23, 45, 123000, tzinfo=dt.timezone.utc),
491                True,
492            ),
493            (
494                "2013-11-10T01:23:45.123456+00:00",
495                dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc),
496                True,
497            ),
498            (
499                "2013-11-10T01:23:45-06:00",
500                central.localize(dt.datetime(2013, 11, 10, 1, 23, 45), is_dst=False),
501                True,
502            ),
503        ],
504    )
505    def test_iso_datetime_field_deserialization(self, fmt, value, expected, aware):
506        field = fields.DateTime(format=fmt)
507        assert field.deserialize(value) == expected
508        field = fields.NaiveDateTime(format=fmt)
509        if aware:
510            with pytest.raises(ValidationError, match="Not a valid naive datetime."):
511                field.deserialize(value)
512        else:
513            assert field.deserialize(value) == expected
514        field = fields.AwareDateTime(format=fmt)
515        if not aware:
516            with pytest.raises(ValidationError, match="Not a valid aware datetime."):
517                field.deserialize(value)
518        else:
519            assert field.deserialize(value) == expected
520
521    @pytest.mark.parametrize(
522        ("fmt", "timezone", "value", "expected"),
523        [
524            ("iso", None, "2013-11-10T01:23:45", dt.datetime(2013, 11, 10, 1, 23, 45)),
525            (
526                "iso",
527                dt.timezone.utc,
528                "2013-11-10T01:23:45+00:00",
529                dt.datetime(2013, 11, 10, 1, 23, 45),
530            ),
531            (
532                "iso",
533                central,
534                "2013-11-10T01:23:45-03:00",
535                dt.datetime(2013, 11, 9, 22, 23, 45),
536            ),
537            (
538                "rfc",
539                None,
540                "Sun, 10 Nov 2013 01:23:45 -0000",
541                dt.datetime(2013, 11, 10, 1, 23, 45),
542            ),
543            (
544                "rfc",
545                dt.timezone.utc,
546                "Sun, 10 Nov 2013 01:23:45 +0000",
547                dt.datetime(2013, 11, 10, 1, 23, 45),
548            ),
549            (
550                "rfc",
551                central,
552                "Sun, 10 Nov 2013 01:23:45 -0300",
553                dt.datetime(2013, 11, 9, 22, 23, 45),
554            ),
555        ],
556    )
557    def test_naive_datetime_with_timezone(self, fmt, timezone, value, expected):
558        field = fields.NaiveDateTime(format=fmt, timezone=timezone)
559        assert field.deserialize(value) == expected
560
561    @pytest.mark.parametrize("timezone", (dt.timezone.utc, central))
562    @pytest.mark.parametrize(
563        ("fmt", "value"),
564        [("iso", "2013-11-10T01:23:45"), ("rfc", "Sun, 10 Nov 2013 01:23:45")],
565    )
566    def test_aware_datetime_default_timezone(self, fmt, timezone, value):
567        field = fields.AwareDateTime(format=fmt, default_timezone=timezone)
568        assert field.deserialize(value) == dt.datetime(
569            2013, 11, 10, 1, 23, 45, tzinfo=timezone
570        )
571
572    def test_time_field_deserialization(self):
573        field = fields.Time()
574        t = dt.time(1, 23, 45)
575        t_formatted = t.isoformat()
576        result = field.deserialize(t_formatted)
577        assert isinstance(result, dt.time)
578        assert_time_equal(result, t)
579        # With microseconds
580        t2 = dt.time(1, 23, 45, 6789)
581        t2_formatted = t2.isoformat()
582        result2 = field.deserialize(t2_formatted)
583        assert_time_equal(result2, t2)
584
585    @pytest.mark.parametrize("in_data", ["badvalue", "", [], 42])
586    def test_invalid_time_field_deserialization(self, in_data):
587        field = fields.Time()
588        with pytest.raises(ValidationError) as excinfo:
589            field.deserialize(in_data)
590        assert excinfo.value.args[0] == "Not a valid time."
591
592    def test_custom_time_format_time_field_deserialization(self):
593        # Time string with format "%f.%S:%M:%H"
594        timestring = "123456.12:11:10"
595
596        # Deserialization should fail when timestring is not of same format
597        field = fields.Time(format="%S:%M:%H")
598        with pytest.raises(ValidationError, match="Not a valid time."):
599            field.deserialize(timestring)
600
601        field = fields.Time(format="%f.%S:%M:%H")
602        assert field.deserialize(timestring) == dt.time(10, 11, 12, 123456)
603
604    @pytest.mark.parametrize("fmt", ["iso", "iso8601", None])
605    @pytest.mark.parametrize(
606        ("value", "expected"),
607        [
608            ("01:23:45", dt.time(1, 23, 45)),
609            ("01:23:45+01:00", dt.time(1, 23, 45)),
610            ("01:23:45.123", dt.time(1, 23, 45, 123000)),
611            ("01:23:45.123456", dt.time(1, 23, 45, 123456)),
612        ],
613    )
614    def test_iso_time_field_deserialization(self, fmt, value, expected):
615        if fmt is None:
616            field = fields.Time()
617        else:
618            field = fields.Time(format=fmt)
619        assert field.deserialize(value) == expected
620
621    def test_invalid_timedelta_precision(self):
622        with pytest.raises(ValueError, match='The precision must be "days",'):
623            fields.TimeDelta("invalid")
624
625    def test_timedelta_field_deserialization(self):
626        field = fields.TimeDelta()
627        result = field.deserialize("42")
628        assert isinstance(result, dt.timedelta)
629        assert result.days == 0
630        assert result.seconds == 42
631        assert result.microseconds == 0
632
633        field = fields.TimeDelta(fields.TimeDelta.SECONDS)
634        result = field.deserialize(100000)
635        assert result.days == 1
636        assert result.seconds == 13600
637        assert result.microseconds == 0
638
639        field = fields.TimeDelta(fields.TimeDelta.DAYS)
640        result = field.deserialize("-42")
641        assert isinstance(result, dt.timedelta)
642        assert result.days == -42
643        assert result.seconds == 0
644        assert result.microseconds == 0
645
646        field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS)
647        result = field.deserialize(10 ** 6 + 1)
648        assert isinstance(result, dt.timedelta)
649        assert result.days == 0
650        assert result.seconds == 1
651        assert result.microseconds == 1
652
653        field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS)
654        result = field.deserialize(86400 * 10 ** 6 + 1)
655        assert isinstance(result, dt.timedelta)
656        assert result.days == 1
657        assert result.seconds == 0
658        assert result.microseconds == 1
659
660        field = fields.TimeDelta()
661        result = field.deserialize(12.9)
662        assert isinstance(result, dt.timedelta)
663        assert result.days == 0
664        assert result.seconds == 12
665        assert result.microseconds == 0
666
667        field = fields.TimeDelta(fields.TimeDelta.WEEKS)
668        result = field.deserialize(1)
669        assert isinstance(result, dt.timedelta)
670        assert result.days == 7
671        assert result.seconds == 0
672        assert result.microseconds == 0
673
674        field = fields.TimeDelta(fields.TimeDelta.HOURS)
675        result = field.deserialize(25)
676        assert isinstance(result, dt.timedelta)
677        assert result.days == 1
678        assert result.seconds == 3600
679        assert result.microseconds == 0
680
681        field = fields.TimeDelta(fields.TimeDelta.MINUTES)
682        result = field.deserialize(1441)
683        assert isinstance(result, dt.timedelta)
684        assert result.days == 1
685        assert result.seconds == 60
686        assert result.microseconds == 0
687
688        field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS)
689        result = field.deserialize(123456)
690        assert isinstance(result, dt.timedelta)
691        assert result.days == 0
692        assert result.seconds == 123
693        assert result.microseconds == 456000
694
695    @pytest.mark.parametrize("in_value", ["", "badvalue", [], 9999999999])
696    def test_invalid_timedelta_field_deserialization(self, in_value):
697        field = fields.TimeDelta(fields.TimeDelta.DAYS)
698        with pytest.raises(ValidationError) as excinfo:
699            field.deserialize(in_value)
700        assert excinfo.value.args[0] == "Not a valid period of time."
701
702    @pytest.mark.parametrize("format", (None, "%Y-%m-%d"))
703    def test_date_field_deserialization(self, format):
704        field = fields.Date(format=format)
705        d = dt.date(2014, 8, 21)
706        iso_date = d.isoformat()
707        result = field.deserialize(iso_date)
708        assert type(result) == dt.date
709        assert_date_equal(result, d)
710
711    @pytest.mark.parametrize(
712        "in_value", ["", 123, [], dt.date(2014, 8, 21).strftime("%d-%m-%Y")]
713    )
714    def test_invalid_date_field_deserialization(self, in_value):
715        field = fields.Date()
716        with pytest.raises(ValidationError) as excinfo:
717            field.deserialize(in_value)
718        msg = "Not a valid date."
719        assert excinfo.value.args[0] == msg
720
721    def test_dict_field_deserialization(self):
722        data = {"foo": "bar"}
723        field = fields.Dict()
724        load = field.deserialize(data)
725        assert load == {"foo": "bar"}
726        # Check load is a distinct object
727        load["foo"] = "baz"
728        assert data["foo"] == "bar"
729        with pytest.raises(ValidationError) as excinfo:
730            field.deserialize("baddict")
731        assert excinfo.value.args[0] == "Not a valid mapping type."
732
733    def test_structured_dict_value_deserialization(self):
734        field = fields.Dict(values=fields.List(fields.Str))
735        assert field.deserialize({"foo": ["bar", "baz"]}) == {"foo": ["bar", "baz"]}
736        with pytest.raises(ValidationError) as excinfo:
737            field.deserialize({"foo": [1, 2], "bar": "baz", "ham": ["spam"]})
738        assert excinfo.value.args[0] == {
739            "foo": {"value": {0: ["Not a valid string."], 1: ["Not a valid string."]}},
740            "bar": {"value": ["Not a valid list."]},
741        }
742        assert excinfo.value.valid_data == {"foo": [], "ham": ["spam"]}
743
744    def test_structured_dict_key_deserialization(self):
745        field = fields.Dict(keys=fields.Str)
746        assert field.deserialize({"foo": "bar"}) == {"foo": "bar"}
747        with pytest.raises(ValidationError) as excinfo:
748            field.deserialize({1: "bar", "foo": "baz"})
749        assert excinfo.value.args[0] == {1: {"key": ["Not a valid string."]}}
750        assert excinfo.value.valid_data == {"foo": "baz"}
751
752    def test_structured_dict_key_value_deserialization(self):
753        field = fields.Dict(
754            keys=fields.Str(
755                validate=[validate.Email(), validate.Regexp(r".*@test\.com$")]
756            ),
757            values=fields.Decimal,
758        )
759        assert field.deserialize({"foo@test.com": 1}) == {
760            "foo@test.com": decimal.Decimal(1)
761        }
762        with pytest.raises(ValidationError) as excinfo:
763            field.deserialize({1: "bar"})
764        assert excinfo.value.args[0] == {
765            1: {"key": ["Not a valid string."], "value": ["Not a valid number."]}
766        }
767        with pytest.raises(ValidationError) as excinfo:
768            field.deserialize({"foo@test.com": "bar"})
769        assert excinfo.value.args[0] == {
770            "foo@test.com": {"value": ["Not a valid number."]}
771        }
772        assert excinfo.value.valid_data == {}
773        with pytest.raises(ValidationError) as excinfo:
774            field.deserialize({1: 1})
775        assert excinfo.value.args[0] == {1: {"key": ["Not a valid string."]}}
776        assert excinfo.value.valid_data == {}
777        with pytest.raises(ValidationError) as excinfo:
778            field.deserialize({"foo": "bar"})
779        assert excinfo.value.args[0] == {
780            "foo": {
781                "key": [
782                    "Not a valid email address.",
783                    "String does not match expected pattern.",
784                ],
785                "value": ["Not a valid number."],
786            }
787        }
788        assert excinfo.value.valid_data == {}
789
790    def test_url_field_deserialization(self):
791        field = fields.Url()
792        assert field.deserialize("https://duckduckgo.com") == "https://duckduckgo.com"
793        with pytest.raises(ValidationError) as excinfo:
794            field.deserialize("badurl")
795        assert excinfo.value.args[0][0] == "Not a valid URL."
796        # Relative URLS not allowed by default
797        with pytest.raises(ValidationError) as excinfo:
798            field.deserialize("/foo/bar")
799        assert excinfo.value.args[0][0] == "Not a valid URL."
800
801    # regression test for https://github.com/marshmallow-code/marshmallow/issues/1400
802    def test_url_field_non_list_validators(self):
803        field = fields.Url(validate=(validate.Length(min=16),))
804        with pytest.raises(ValidationError, match="Shorter than minimum length"):
805            field.deserialize("https://abc.def")
806
807    def test_relative_url_field_deserialization(self):
808        field = fields.Url(relative=True)
809        assert field.deserialize("/foo/bar") == "/foo/bar"
810
811    def test_url_field_schemes_argument(self):
812        field = fields.URL()
813        url = "ws://test.test"
814        with pytest.raises(ValidationError):
815            field.deserialize(url)
816        field2 = fields.URL(schemes={"http", "https", "ws"})
817        assert field2.deserialize(url) == url
818
819    def test_email_field_deserialization(self):
820        field = fields.Email()
821        assert field.deserialize("foo@bar.com") == "foo@bar.com"
822        with pytest.raises(ValidationError) as excinfo:
823            field.deserialize("invalidemail")
824        assert excinfo.value.args[0][0] == "Not a valid email address."
825
826        field = fields.Email(validate=[validate.Length(min=12)])
827        with pytest.raises(ValidationError) as excinfo:
828            field.deserialize("foo@bar.com")
829        assert excinfo.value.args[0][0] == "Shorter than minimum length 12."
830
831    # regression test for https://github.com/marshmallow-code/marshmallow/issues/1400
832    def test_email_field_non_list_validators(self):
833        field = fields.Email(validate=(validate.Length(min=9),))
834        with pytest.raises(ValidationError, match="Shorter than minimum length"):
835            field.deserialize("a@bc.com")
836
837    def test_function_field_deserialization_is_noop_by_default(self):
838        field = fields.Function(lambda x: None)
839        # Default is noop
840        assert field.deserialize("foo") == "foo"
841        assert field.deserialize(42) == 42
842
843    def test_function_field_deserialization_with_callable(self):
844        field = fields.Function(lambda x: None, deserialize=lambda val: val.upper())
845        assert field.deserialize("foo") == "FOO"
846
847    def test_function_field_deserialization_with_context(self):
848        class Parent(Schema):
849            pass
850
851        field = fields.Function(
852            lambda x: None,
853            deserialize=lambda val, context: val.upper() + context["key"],
854        )
855        field.parent = Parent(context={"key": "BAR"})
856        assert field.deserialize("foo") == "FOOBAR"
857
858    def test_function_field_passed_deserialize_only_is_load_only(self):
859        field = fields.Function(deserialize=lambda val: val.upper())
860        assert field.load_only is True
861
862    def test_function_field_passed_deserialize_and_serialize_is_not_load_only(self):
863        field = fields.Function(
864            serialize=lambda val: val.lower(), deserialize=lambda val: val.upper()
865        )
866        assert field.load_only is False
867
868    def test_uuid_field_deserialization(self):
869        field = fields.UUID()
870        uuid_str = str(uuid.uuid4())
871        result = field.deserialize(uuid_str)
872        assert isinstance(result, uuid.UUID)
873        assert str(result) == uuid_str
874
875        uuid4 = uuid.uuid4()
876        result = field.deserialize(uuid4)
877        assert isinstance(result, uuid.UUID)
878        assert result == uuid4
879
880        uuid_bytes = b"]\xc7wW\x132O\xf9\xa5\xbe\x13\x1f\x02\x18\xda\xbf"
881        result = field.deserialize(uuid_bytes)
882        assert isinstance(result, uuid.UUID)
883        assert result.bytes == uuid_bytes
884
885    @pytest.mark.parametrize("in_value", ["malformed", 123, [], b"tooshort"])
886    def test_invalid_uuid_deserialization(self, in_value):
887        field = fields.UUID()
888        with pytest.raises(ValidationError) as excinfo:
889            field.deserialize(in_value)
890
891        assert excinfo.value.args[0] == "Not a valid UUID."
892
893    def test_ip_field_deserialization(self):
894        field = fields.IP()
895        ipv4_str = "140.82.118.3"
896        result = field.deserialize(ipv4_str)
897        assert isinstance(result, ipaddress.IPv4Address)
898        assert str(result) == ipv4_str
899
900        ipv6_str = "2a00:1450:4001:824::200e"
901        result = field.deserialize(ipv6_str)
902        assert isinstance(result, ipaddress.IPv6Address)
903        assert str(result) == ipv6_str
904
905    @pytest.mark.parametrize(
906        "in_value",
907        ["malformed", 123, b"\x01\x02\03", "192.168", "192.168.0.1/24", "ff::aa:1::2"],
908    )
909    def test_invalid_ip_deserialization(self, in_value):
910        field = fields.IP()
911        with pytest.raises(ValidationError) as excinfo:
912            field.deserialize(in_value)
913
914        assert excinfo.value.args[0] == "Not a valid IP address."
915
916    def test_ipv4_field_deserialization(self):
917        field = fields.IPv4()
918        ipv4_str = "140.82.118.3"
919        result = field.deserialize(ipv4_str)
920        assert isinstance(result, ipaddress.IPv4Address)
921        assert str(result) == ipv4_str
922
923    @pytest.mark.parametrize(
924        "in_value",
925        [
926            "malformed",
927            123,
928            b"\x01\x02\03",
929            "192.168",
930            "192.168.0.1/24",
931            "2a00:1450:4001:81d::200e",
932        ],
933    )
934    def test_invalid_ipv4_deserialization(self, in_value):
935        field = fields.IPv4()
936        with pytest.raises(ValidationError) as excinfo:
937            field.deserialize(in_value)
938
939        assert excinfo.value.args[0] == "Not a valid IPv4 address."
940
941    def test_ipv6_field_deserialization(self):
942        field = fields.IPv6()
943        ipv6_str = "2a00:1450:4001:824::200e"
944        result = field.deserialize(ipv6_str)
945        assert isinstance(result, ipaddress.IPv6Address)
946        assert str(result) == ipv6_str
947
948    def test_ipinterface_field_deserialization(self):
949        field = fields.IPInterface()
950        ipv4interface_str = "140.82.118.3/24"
951        result = field.deserialize(ipv4interface_str)
952        assert isinstance(result, ipaddress.IPv4Interface)
953        assert str(result) == ipv4interface_str
954
955        ipv6interface_str = "2a00:1450:4001:824::200e/128"
956        result = field.deserialize(ipv6interface_str)
957        assert isinstance(result, ipaddress.IPv6Interface)
958        assert str(result) == ipv6interface_str
959
960    @pytest.mark.parametrize(
961        "in_value",
962        [
963            "malformed",
964            123,
965            b"\x01\x02\03",
966            "192.168",
967            "192.168.0.1/33",
968            "ff::aa:1::2",
969            "2a00:1450:4001:824::200e/129",
970        ],
971    )
972    def test_invalid_ipinterface_deserialization(self, in_value):
973        field = fields.IPInterface()
974        with pytest.raises(ValidationError) as excinfo:
975            field.deserialize(in_value)
976
977        assert excinfo.value.args[0] == "Not a valid IP interface."
978
979    def test_ipv4interface_field_deserialization(self):
980        field = fields.IPv4Interface()
981        ipv4interface_str = "140.82.118.3/24"
982        result = field.deserialize(ipv4interface_str)
983        assert isinstance(result, ipaddress.IPv4Interface)
984        assert str(result) == ipv4interface_str
985
986    @pytest.mark.parametrize(
987        "in_value",
988        [
989            "malformed",
990            123,
991            b"\x01\x02\03",
992            "192.168",
993            "192.168.0.1/33",
994            "2a00:1450:4001:81d::200e",
995            "2a00:1450:4001:824::200e/129",
996        ],
997    )
998    def test_invalid_ipv4interface_deserialization(self, in_value):
999        field = fields.IPv4Interface()
1000        with pytest.raises(ValidationError) as excinfo:
1001            field.deserialize(in_value)
1002
1003        assert excinfo.value.args[0] == "Not a valid IPv4 interface."
1004
1005    def test_ipv6interface_field_deserialization(self):
1006        field = fields.IPv6Interface()
1007        ipv6interface_str = "2a00:1450:4001:824::200e/128"
1008        result = field.deserialize(ipv6interface_str)
1009        assert isinstance(result, ipaddress.IPv6Interface)
1010        assert str(result) == ipv6interface_str
1011
1012    @pytest.mark.parametrize(
1013        "in_value",
1014        [
1015            "malformed",
1016            123,
1017            b"\x01\x02\03",
1018            "ff::aa:1::2",
1019            "192.168.0.1",
1020            "192.168.0.1/24",
1021            "2a00:1450:4001:824::200e/129",
1022        ],
1023    )
1024    def test_invalid_ipv6interface_deserialization(self, in_value):
1025        field = fields.IPv6Interface()
1026        with pytest.raises(ValidationError) as excinfo:
1027            field.deserialize(in_value)
1028
1029        assert excinfo.value.args[0] == "Not a valid IPv6 interface."
1030
1031    def test_deserialization_function_must_be_callable(self):
1032        with pytest.raises(TypeError):
1033            fields.Function(lambda x: None, deserialize="notvalid")
1034
1035    def test_method_field_deserialization_is_noop_by_default(self):
1036        class MiniUserSchema(Schema):
1037            uppername = fields.Method("uppercase_name")
1038
1039            def uppercase_name(self, obj):
1040                return obj.upper()
1041
1042        s = MiniUserSchema()
1043        assert s.fields["uppername"].deserialize("steve") == "steve"
1044
1045    def test_deserialization_method(self):
1046        class MiniUserSchema(Schema):
1047            uppername = fields.Method("uppercase_name", deserialize="lowercase_name")
1048
1049            def uppercase_name(self, obj):
1050                return obj.name.upper()
1051
1052            def lowercase_name(self, value):
1053                return value.lower()
1054
1055        s = MiniUserSchema()
1056        assert s.fields["uppername"].deserialize("STEVE") == "steve"
1057
1058    def test_deserialization_method_must_be_a_method(self):
1059        class BadSchema(Schema):
1060            uppername = fields.Method("uppercase_name", deserialize="lowercase_name")
1061
1062        with pytest.raises(AttributeError):
1063            BadSchema()
1064
1065    def test_method_field_deserialize_only(self):
1066        class MethodDeserializeOnly(Schema):
1067            name = fields.Method(deserialize="lowercase_name")
1068
1069            def lowercase_name(self, value):
1070                return value.lower()
1071
1072        assert MethodDeserializeOnly().load({"name": "ALEC"})["name"] == "alec"
1073
1074    def test_datetime_list_field_deserialization(self):
1075        dtimes = dt.datetime.now(), dt.datetime.now(), dt.datetime.utcnow()
1076        dstrings = [each.isoformat() for each in dtimes]
1077        field = fields.List(fields.DateTime())
1078        result = field.deserialize(dstrings)
1079        assert all(isinstance(each, dt.datetime) for each in result)
1080        for actual, expected in zip(result, dtimes):
1081            assert_date_equal(actual, expected)
1082
1083    def test_list_field_deserialize_invalid_item(self):
1084        field = fields.List(fields.DateTime)
1085        with pytest.raises(ValidationError) as excinfo:
1086            field.deserialize(["badvalue"])
1087        assert excinfo.value.args[0] == {0: ["Not a valid datetime."]}
1088
1089        field = fields.List(fields.Str())
1090        with pytest.raises(ValidationError) as excinfo:
1091            field.deserialize(["good", 42])
1092        assert excinfo.value.args[0] == {1: ["Not a valid string."]}
1093
1094    def test_list_field_deserialize_multiple_invalid_items(self):
1095        field = fields.List(
1096            fields.Int(
1097                validate=validate.Range(10, 20, error="Value {input} not in range")
1098            )
1099        )
1100        with pytest.raises(ValidationError) as excinfo:
1101            field.deserialize([10, 5, 25])
1102        assert len(excinfo.value.args[0]) == 2
1103        assert excinfo.value.args[0][1] == ["Value 5 not in range"]
1104        assert excinfo.value.args[0][2] == ["Value 25 not in range"]
1105
1106    @pytest.mark.parametrize("value", ["notalist", 42, {}])
1107    def test_list_field_deserialize_value_that_is_not_a_list(self, value):
1108        field = fields.List(fields.Str())
1109        with pytest.raises(ValidationError) as excinfo:
1110            field.deserialize(value)
1111        assert excinfo.value.args[0] == "Not a valid list."
1112
1113    def test_datetime_int_tuple_field_deserialization(self):
1114        dtime = dt.datetime.now()
1115        data = dtime.isoformat(), 42
1116        field = fields.Tuple([fields.DateTime(), fields.Integer()])
1117        result = field.deserialize(data)
1118
1119        assert isinstance(result, tuple)
1120        assert len(result) == 2
1121        for val, type_, true_val in zip(result, (dt.datetime, int), (dtime, 42)):
1122            assert isinstance(val, type_)
1123            assert val == true_val
1124
1125    def test_tuple_field_deserialize_invalid_item(self):
1126        field = fields.Tuple([fields.DateTime])
1127        with pytest.raises(ValidationError) as excinfo:
1128            field.deserialize(["badvalue"])
1129        assert excinfo.value.args[0] == {0: ["Not a valid datetime."]}
1130
1131        field = fields.Tuple([fields.Str(), fields.Integer()])
1132        with pytest.raises(ValidationError) as excinfo:
1133            field.deserialize(["good", "bad"])
1134        assert excinfo.value.args[0] == {1: ["Not a valid integer."]}
1135
1136    def test_tuple_field_deserialize_multiple_invalid_items(self):
1137        validator = validate.Range(10, 20, error="Value {input} not in range")
1138        field = fields.Tuple(
1139            [
1140                fields.Int(validate=validator),
1141                fields.Int(validate=validator),
1142                fields.Int(validate=validator),
1143            ]
1144        )
1145
1146        with pytest.raises(ValidationError) as excinfo:
1147            field.deserialize([10, 5, 25])
1148        assert len(excinfo.value.args[0]) == 2
1149        assert excinfo.value.args[0][1] == ["Value 5 not in range"]
1150        assert excinfo.value.args[0][2] == ["Value 25 not in range"]
1151
1152    @pytest.mark.parametrize("value", ["notalist", 42, {}])
1153    def test_tuple_field_deserialize_value_that_is_not_a_collection(self, value):
1154        field = fields.Tuple([fields.Str()])
1155        with pytest.raises(ValidationError) as excinfo:
1156            field.deserialize(value)
1157        assert excinfo.value.args[0] == "Not a valid tuple."
1158
1159    def test_tuple_field_deserialize_invalid_length(self):
1160        field = fields.Tuple([fields.Str(), fields.Str()])
1161        with pytest.raises(ValidationError) as excinfo:
1162            field.deserialize([42])
1163        assert excinfo.value.args[0] == "Length must be 2."
1164
1165    def test_constant_field_deserialization(self):
1166        field = fields.Constant("something")
1167        assert field.deserialize("whatever") == "something"
1168
1169    def test_constant_is_always_included_in_deserialized_data(self):
1170        class MySchema(Schema):
1171            foo = fields.Constant(42)
1172
1173        sch = MySchema()
1174        assert sch.load({})["foo"] == 42
1175        assert sch.load({"foo": 24})["foo"] == 42
1176
1177    def test_field_deserialization_with_user_validator_function(self):
1178        field = fields.String(validate=lambda s: s.lower() == "valid")
1179        assert field.deserialize("Valid") == "Valid"
1180        with pytest.raises(ValidationError) as excinfo:
1181            field.deserialize("invalid")
1182        assert excinfo.value.args[0][0] == "Invalid value."
1183        assert type(excinfo.value) == ValidationError
1184
1185    def test_field_deserialization_with_user_validator_class_that_returns_bool(self):
1186        class MyValidator:
1187            def __call__(self, val):
1188                if val == "valid":
1189                    return True
1190                return False
1191
1192        field = fields.Field(validate=MyValidator())
1193        assert field.deserialize("valid") == "valid"
1194        with pytest.raises(ValidationError, match="Invalid value."):
1195            field.deserialize("invalid")
1196
1197    def test_field_deserialization_with_user_validator_that_raises_error_with_list(
1198        self,
1199    ):
1200        def validator(val):
1201            raise ValidationError(["err1", "err2"])
1202
1203        class MySchema(Schema):
1204            foo = fields.Field(validate=validator)
1205
1206        errors = MySchema().validate({"foo": 42})
1207        assert errors["foo"] == ["err1", "err2"]
1208
1209    def test_validator_must_return_false_to_raise_error(self):
1210        # validator returns None, so anything validates
1211        field = fields.String(validate=lambda s: None)
1212        assert field.deserialize("Valid") == "Valid"
1213        # validator returns False, so nothing validates
1214        field2 = fields.String(validate=lambda s: False)
1215        with pytest.raises(ValidationError):
1216            field2.deserialize("invalid")
1217
1218    def test_field_deserialization_with_validator_with_nonascii_input(self):
1219        field = fields.String(validate=lambda s: False)
1220        with pytest.raises(ValidationError) as excinfo:
1221            field.deserialize("привет")
1222        assert type(excinfo.value) == ValidationError
1223
1224    def test_field_deserialization_with_user_validators(self):
1225        validators_gen = (
1226            func
1227            for func in (
1228                lambda s: s.lower() == "valid",
1229                lambda s: s.lower()[::-1] == "dilav",
1230            )
1231        )
1232
1233        m_colletion_type = [
1234            fields.String(
1235                validate=[
1236                    lambda s: s.lower() == "valid",
1237                    lambda s: s.lower()[::-1] == "dilav",
1238                ]
1239            ),
1240            fields.String(
1241                validate=(
1242                    lambda s: s.lower() == "valid",
1243                    lambda s: s.lower()[::-1] == "dilav",
1244                )
1245            ),
1246            fields.String(validate=validators_gen),
1247        ]
1248
1249        for field in m_colletion_type:
1250            assert field.deserialize("Valid") == "Valid"
1251            with pytest.raises(ValidationError, match="Invalid value."):
1252                field.deserialize("invalid")
1253
1254    def test_field_deserialization_with_custom_error_message(self):
1255        field = fields.String(
1256            validate=lambda s: s.lower() == "valid",
1257            error_messages={"validator_failed": "Bad value."},
1258        )
1259        with pytest.raises(ValidationError, match="Bad value."):
1260            field.deserialize("invalid")
1261
1262
1263# No custom deserialization behavior, so a dict is returned
1264class SimpleUserSchema(Schema):
1265    name = fields.String()
1266    age = fields.Float()
1267
1268
1269class Validator(Schema):
1270    email = fields.Email()
1271    colors = fields.Str(validate=validate.OneOf(["red", "blue"]))
1272    age = fields.Integer(validate=lambda n: n > 0)
1273
1274
1275class Validators(Schema):
1276    email = fields.Email()
1277    colors = fields.Str(validate=validate.OneOf(["red", "blue"]))
1278    age = fields.Integer(validate=[lambda n: n > 0, lambda n: n < 100])
1279
1280
1281class TestSchemaDeserialization:
1282    def test_deserialize_to_dict(self):
1283        user_dict = {"name": "Monty", "age": "42.3"}
1284        result = SimpleUserSchema().load(user_dict)
1285        assert result["name"] == "Monty"
1286        assert math.isclose(result["age"], 42.3)
1287
1288    def test_deserialize_with_missing_values(self):
1289        user_dict = {"name": "Monty"}
1290        result = SimpleUserSchema().load(user_dict)
1291        # 'age' is not included in result
1292        assert result == {"name": "Monty"}
1293
1294    def test_deserialize_many(self):
1295        users_data = [{"name": "Mick", "age": "914"}, {"name": "Keith", "age": "8442"}]
1296        result = SimpleUserSchema(many=True).load(users_data)
1297        assert isinstance(result, list)
1298        user = result[0]
1299        assert user["age"] == int(users_data[0]["age"])
1300
1301    def test_exclude(self):
1302        schema = SimpleUserSchema(exclude=("age",), unknown=EXCLUDE)
1303        result = schema.load({"name": "Monty", "age": 42})
1304        assert "name" in result
1305        assert "age" not in result
1306
1307    def test_nested_single_deserialization_to_dict(self):
1308        class SimpleBlogSerializer(Schema):
1309            title = fields.String()
1310            author = fields.Nested(SimpleUserSchema, unknown=EXCLUDE)
1311
1312        blog_dict = {
1313            "title": "Gimme Shelter",
1314            "author": {"name": "Mick", "age": "914", "email": "mick@stones.com"},
1315        }
1316        result = SimpleBlogSerializer().load(blog_dict)
1317        author = result["author"]
1318        assert author["name"] == "Mick"
1319        assert author["age"] == 914
1320        assert "email" not in author
1321
1322    def test_nested_list_deserialization_to_dict(self):
1323        class SimpleBlogSerializer(Schema):
1324            title = fields.String()
1325            authors = fields.Nested(SimpleUserSchema, many=True)
1326
1327        blog_dict = {
1328            "title": "Gimme Shelter",
1329            "authors": [
1330                {"name": "Mick", "age": "914"},
1331                {"name": "Keith", "age": "8442"},
1332            ],
1333        }
1334        result = SimpleBlogSerializer().load(blog_dict)
1335        assert isinstance(result["authors"], list)
1336        author = result["authors"][0]
1337        assert author["name"] == "Mick"
1338        assert author["age"] == 914
1339
1340    def test_nested_single_none_not_allowed(self):
1341        class PetSchema(Schema):
1342            name = fields.Str()
1343
1344        class OwnerSchema(Schema):
1345            pet = fields.Nested(PetSchema(), allow_none=False)
1346
1347        sch = OwnerSchema()
1348        errors = sch.validate({"pet": None})
1349        assert "pet" in errors
1350        assert errors["pet"] == ["Field may not be null."]
1351
1352    def test_nested_many_non_not_allowed(self):
1353        class PetSchema(Schema):
1354            name = fields.Str()
1355
1356        class StoreSchema(Schema):
1357            pets = fields.Nested(PetSchema(), allow_none=False, many=True)
1358
1359        sch = StoreSchema()
1360        errors = sch.validate({"pets": None})
1361        assert "pets" in errors
1362        assert errors["pets"] == ["Field may not be null."]
1363
1364    def test_nested_single_required_missing(self):
1365        class PetSchema(Schema):
1366            name = fields.Str()
1367
1368        class OwnerSchema(Schema):
1369            pet = fields.Nested(PetSchema(), required=True)
1370
1371        sch = OwnerSchema()
1372        errors = sch.validate({})
1373        assert "pet" in errors
1374        assert errors["pet"] == ["Missing data for required field."]
1375
1376    def test_nested_many_required_missing(self):
1377        class PetSchema(Schema):
1378            name = fields.Str()
1379
1380        class StoreSchema(Schema):
1381            pets = fields.Nested(PetSchema(), required=True, many=True)
1382
1383        sch = StoreSchema()
1384        errors = sch.validate({})
1385        assert "pets" in errors
1386        assert errors["pets"] == ["Missing data for required field."]
1387
1388    def test_nested_only_basestring(self):
1389        class ANestedSchema(Schema):
1390            pk = fields.Str()
1391
1392        class MainSchema(Schema):
1393            pk = fields.Str()
1394            child = fields.Pluck(ANestedSchema, "pk")
1395
1396        sch = MainSchema()
1397        result = sch.load({"pk": "123", "child": "456"})
1398        assert result["child"]["pk"] == "456"
1399
1400    def test_nested_only_basestring_with_list_data(self):
1401        class ANestedSchema(Schema):
1402            pk = fields.Str()
1403
1404        class MainSchema(Schema):
1405            pk = fields.Str()
1406            children = fields.Pluck(ANestedSchema, "pk", many=True)
1407
1408        sch = MainSchema()
1409        result = sch.load({"pk": "123", "children": ["456", "789"]})
1410        assert result["children"][0]["pk"] == "456"
1411        assert result["children"][1]["pk"] == "789"
1412
1413    def test_nested_none_deserialization(self):
1414        class SimpleBlogSerializer(Schema):
1415            title = fields.String()
1416            author = fields.Nested(SimpleUserSchema, allow_none=True)
1417
1418        blog_dict = {"title": "Gimme Shelter", "author": None}
1419        result = SimpleBlogSerializer().load(blog_dict)
1420        assert result["author"] is None
1421        assert result["title"] == blog_dict["title"]
1422
1423    def test_deserialize_with_attribute_param(self):
1424        class AliasingUserSerializer(Schema):
1425            username = fields.Email(attribute="email")
1426            years = fields.Integer(attribute="age")
1427
1428        data = {"username": "foo@bar.com", "years": "42"}
1429        result = AliasingUserSerializer().load(data)
1430        assert result["email"] == "foo@bar.com"
1431        assert result["age"] == 42
1432
1433    # regression test for https://github.com/marshmallow-code/marshmallow/issues/450
1434    def test_deserialize_with_attribute_param_symmetry(self):
1435        class MySchema(Schema):
1436            foo = fields.Field(attribute="bar.baz")
1437
1438        schema = MySchema()
1439        dump_data = schema.dump({"bar": {"baz": 42}})
1440        assert dump_data == {"foo": 42}
1441
1442        load_data = schema.load({"foo": 42})
1443        assert load_data == {"bar": {"baz": 42}}
1444
1445    def test_deserialize_with_attribute_param_error_returns_field_name_not_attribute_name(
1446        self,
1447    ):
1448        class AliasingUserSerializer(Schema):
1449            username = fields.Email(attribute="email")
1450            years = fields.Integer(attribute="age")
1451
1452        data = {"username": "foobar.com", "years": "42"}
1453        with pytest.raises(ValidationError) as excinfo:
1454            AliasingUserSerializer().load(data)
1455        errors = excinfo.value.messages
1456        assert errors["username"] == ["Not a valid email address."]
1457
1458    def test_deserialize_with_attribute_param_error_returns_data_key_not_attribute_name(
1459        self,
1460    ):
1461        class AliasingUserSerializer(Schema):
1462            name = fields.String(data_key="Name")
1463            username = fields.Email(attribute="email", data_key="UserName")
1464            years = fields.Integer(attribute="age", data_key="Years")
1465
1466        data = {"Name": "Mick", "UserName": "foobar.com", "Years": "abc"}
1467        with pytest.raises(ValidationError) as excinfo:
1468            AliasingUserSerializer().load(data)
1469        errors = excinfo.value.messages
1470        assert errors["UserName"] == ["Not a valid email address."]
1471        assert errors["Years"] == ["Not a valid integer."]
1472
1473    def test_deserialize_with_data_key_param(self):
1474        class AliasingUserSerializer(Schema):
1475            name = fields.String(data_key="Name")
1476            username = fields.Email(attribute="email", data_key="UserName")
1477            years = fields.Integer(data_key="Years")
1478
1479        data = {"Name": "Mick", "UserName": "foo@bar.com", "years": "42"}
1480        result = AliasingUserSerializer(unknown=EXCLUDE).load(data)
1481        assert result["name"] == "Mick"
1482        assert result["email"] == "foo@bar.com"
1483        assert "years" not in result
1484
1485    def test_deserialize_with_data_key_as_empty_string(self):
1486        class MySchema(Schema):
1487            name = fields.Field(data_key="")
1488
1489        schema = MySchema()
1490        assert schema.load({"": "Grace"}) == {"name": "Grace"}
1491
1492    def test_deserialize_with_dump_only_param(self):
1493        class AliasingUserSerializer(Schema):
1494            name = fields.String()
1495            years = fields.Integer(dump_only=True)
1496            size = fields.Integer(dump_only=True, load_only=True)
1497            nicknames = fields.List(fields.Str(), dump_only=True)
1498
1499        data = {
1500            "name": "Mick",
1501            "years": "42",
1502            "size": "12",
1503            "nicknames": ["Your Majesty", "Brenda"],
1504        }
1505        result = AliasingUserSerializer(unknown=EXCLUDE).load(data)
1506        assert result["name"] == "Mick"
1507        assert "years" not in result
1508        assert "size" not in result
1509        assert "nicknames" not in result
1510
1511    def test_deserialize_with_missing_param_value(self):
1512        bdate = dt.datetime(2017, 9, 29)
1513
1514        class AliasingUserSerializer(Schema):
1515            name = fields.String()
1516            birthdate = fields.DateTime(load_default=bdate)
1517
1518        data = {"name": "Mick"}
1519        result = AliasingUserSerializer().load(data)
1520        assert result["name"] == "Mick"
1521        assert result["birthdate"] == bdate
1522
1523    def test_deserialize_with_missing_param_callable(self):
1524        bdate = dt.datetime(2017, 9, 29)
1525
1526        class AliasingUserSerializer(Schema):
1527            name = fields.String()
1528            birthdate = fields.DateTime(load_default=lambda: bdate)
1529
1530        data = {"name": "Mick"}
1531        result = AliasingUserSerializer().load(data)
1532        assert result["name"] == "Mick"
1533        assert result["birthdate"] == bdate
1534
1535    def test_deserialize_with_missing_param_none(self):
1536        class AliasingUserSerializer(Schema):
1537            name = fields.String()
1538            years = fields.Integer(load_default=None, allow_none=True)
1539
1540        data = {"name": "Mick"}
1541        result = AliasingUserSerializer().load(data)
1542        assert result["name"] == "Mick"
1543        assert result["years"] is None
1544
1545    def test_deserialization_raises_with_errors(self):
1546        bad_data = {"email": "invalid-email", "colors": "burger", "age": -1}
1547        v = Validator()
1548        with pytest.raises(ValidationError) as excinfo:
1549            v.load(bad_data)
1550        errors = excinfo.value.messages
1551        assert "email" in errors
1552        assert "colors" in errors
1553        assert "age" in errors
1554
1555    def test_deserialization_raises_with_errors_with_multiple_validators(self):
1556        bad_data = {"email": "invalid-email", "colors": "burger", "age": -1}
1557        v = Validators()
1558        with pytest.raises(ValidationError) as excinfo:
1559            v.load(bad_data)
1560        errors = excinfo.value.messages
1561        assert "email" in errors
1562        assert "colors" in errors
1563        assert "age" in errors
1564
1565    def test_deserialization_many_raises_errors(self):
1566        bad_data = [
1567            {"email": "foo@bar.com", "colors": "red", "age": 18},
1568            {"email": "bad", "colors": "pizza", "age": -1},
1569        ]
1570        v = Validator(many=True)
1571        with pytest.raises(ValidationError):
1572            v.load(bad_data)
1573
1574    def test_validation_errors_are_stored(self):
1575        def validate_field(val):
1576            raise ValidationError("Something went wrong")
1577
1578        class MySchema(Schema):
1579            foo = fields.Field(validate=validate_field)
1580
1581        with pytest.raises(ValidationError) as excinfo:
1582            MySchema().load({"foo": 42})
1583        errors = excinfo.value.messages
1584        assert "Something went wrong" in errors["foo"]
1585
1586    def test_multiple_errors_can_be_stored_for_a_field(self):
1587        def validate_with_bool(n):
1588            return False
1589
1590        def validate_with_error(n):
1591            raise ValidationError("foo is not valid")
1592
1593        class MySchema(Schema):
1594            foo = fields.Field(
1595                required=True, validate=[validate_with_bool, validate_with_error]
1596            )
1597
1598        with pytest.raises(ValidationError) as excinfo:
1599            MySchema().load({"foo": "bar"})
1600        errors = excinfo.value.messages
1601
1602        assert type(errors["foo"]) == list
1603        assert len(errors["foo"]) == 2
1604
1605    def test_multiple_errors_can_be_stored_for_an_email_field(self):
1606        def validate_with_bool(val):
1607            return False
1608
1609        class MySchema(Schema):
1610            email = fields.Email(validate=[validate_with_bool])
1611
1612        with pytest.raises(ValidationError) as excinfo:
1613            MySchema().load({"email": "foo"})
1614        errors = excinfo.value.messages
1615        assert len(errors["email"]) == 2
1616        assert "Not a valid email address." in errors["email"][0]
1617
1618    def test_multiple_errors_can_be_stored_for_a_url_field(self):
1619        def validate_with_bool(val):
1620            return False
1621
1622        class MySchema(Schema):
1623            url = fields.Url(validate=[validate_with_bool])
1624
1625        with pytest.raises(ValidationError) as excinfo:
1626            MySchema().load({"url": "foo"})
1627        errors = excinfo.value.messages
1628        assert len(errors["url"]) == 2
1629        assert "Not a valid URL." in errors["url"][0]
1630
1631    def test_required_value_only_passed_to_validators_if_provided(self):
1632        class MySchema(Schema):
1633            foo = fields.Field(required=True, validate=lambda f: False)
1634
1635        with pytest.raises(ValidationError) as excinfo:
1636            MySchema().load({})
1637        errors = excinfo.value.messages
1638        # required value missing
1639        assert len(errors["foo"]) == 1
1640        assert "Missing data for required field." in errors["foo"]
1641
1642    @pytest.mark.parametrize("partial_schema", [True, False])
1643    def test_partial_deserialization(self, partial_schema):
1644        class MySchema(Schema):
1645            foo = fields.Field(required=True)
1646            bar = fields.Field(required=True)
1647
1648        schema_args = {}
1649        load_args = {}
1650        if partial_schema:
1651            schema_args["partial"] = True
1652        else:
1653            load_args["partial"] = True
1654        data = MySchema(**schema_args).load({"foo": 3}, **load_args)
1655
1656        assert data["foo"] == 3
1657        assert "bar" not in data
1658
1659    def test_partial_fields_deserialization(self):
1660        class MySchema(Schema):
1661            foo = fields.Field(required=True)
1662            bar = fields.Field(required=True)
1663            baz = fields.Field(required=True)
1664
1665        with pytest.raises(ValidationError) as excinfo:
1666            MySchema().load({"foo": 3}, partial=tuple())
1667        data, errors = excinfo.value.valid_data, excinfo.value.messages
1668        assert data["foo"] == 3
1669        assert "bar" in errors
1670        assert "baz" in errors
1671
1672        data = MySchema().load({"foo": 3}, partial=("bar", "baz"))
1673        assert data["foo"] == 3
1674        assert "bar" not in data
1675        assert "baz" not in data
1676
1677        data = MySchema(partial=True).load({"foo": 3}, partial=("bar", "baz"))
1678        assert data["foo"] == 3
1679        assert "bar" not in data
1680        assert "baz" not in data
1681
1682    def test_partial_fields_validation(self):
1683        class MySchema(Schema):
1684            foo = fields.Field(required=True)
1685            bar = fields.Field(required=True)
1686            baz = fields.Field(required=True)
1687
1688        errors = MySchema().validate({"foo": 3}, partial=tuple())
1689        assert "bar" in errors
1690        assert "baz" in errors
1691
1692        errors = MySchema().validate({"foo": 3}, partial=("bar", "baz"))
1693        assert errors == {}
1694
1695        errors = MySchema(partial=True).validate({"foo": 3}, partial=("bar", "baz"))
1696        assert errors == {}
1697
1698    def test_unknown_fields_deserialization(self):
1699        class MySchema(Schema):
1700            foo = fields.Integer()
1701
1702        data = MySchema(unknown=EXCLUDE).load({"foo": 3, "bar": 5})
1703        assert data["foo"] == 3
1704        assert "bar" not in data
1705
1706        data = MySchema(unknown=INCLUDE).load({"foo": 3, "bar": 5}, unknown=EXCLUDE)
1707        assert data["foo"] == 3
1708        assert "bar" not in data
1709
1710        data = MySchema(unknown=EXCLUDE).load({"foo": 3, "bar": 5}, unknown=INCLUDE)
1711        assert data["foo"] == 3
1712        assert data["bar"]
1713
1714        data = MySchema(unknown=INCLUDE).load({"foo": 3, "bar": 5})
1715        assert data["foo"] == 3
1716        assert data["bar"]
1717
1718        with pytest.raises(ValidationError, match="foo"):
1719            MySchema(unknown=INCLUDE).load({"foo": "asd", "bar": 5})
1720
1721        data = MySchema(unknown=INCLUDE, many=True).load(
1722            [{"foo": 1}, {"foo": 3, "bar": 5}]
1723        )
1724        assert "foo" in data[1]
1725        assert "bar" in data[1]
1726
1727        with pytest.raises(ValidationError) as excinfo:
1728            MySchema().load({"foo": 3, "bar": 5})
1729        err = excinfo.value
1730        assert "bar" in err.messages
1731        assert err.messages["bar"] == ["Unknown field."]
1732
1733        with pytest.raises(ValidationError) as excinfo:
1734            MySchema(many=True).load([{"foo": "abc"}, {"foo": 3, "bar": 5}])
1735        err = excinfo.value
1736        assert 0 in err.messages
1737        assert "foo" in err.messages[0]
1738        assert err.messages[0]["foo"] == ["Not a valid integer."]
1739        assert 1 in err.messages
1740        assert "bar" in err.messages[1]
1741        assert err.messages[1]["bar"] == ["Unknown field."]
1742
1743    def test_unknown_fields_deserialization_precedence(self):
1744        class MySchema(Schema):
1745            class Meta:
1746                unknown = INCLUDE
1747
1748            foo = fields.Integer()
1749
1750        data = MySchema().load({"foo": 3, "bar": 5})
1751        assert data["foo"] == 3
1752        assert data["bar"] == 5
1753
1754        data = MySchema(unknown=EXCLUDE).load({"foo": 3, "bar": 5})
1755        assert data["foo"] == 3
1756        assert "bar" not in data
1757
1758        data = MySchema().load({"foo": 3, "bar": 5}, unknown=EXCLUDE)
1759        assert data["foo"] == 3
1760        assert "bar" not in data
1761
1762        with pytest.raises(ValidationError):
1763            MySchema(unknown=EXCLUDE).load({"foo": 3, "bar": 5}, unknown=RAISE)
1764
1765    def test_unknown_fields_deserialization_with_data_key(self):
1766        class MySchema(Schema):
1767            foo = fields.Integer(data_key="Foo")
1768
1769        data = MySchema().load({"Foo": 1})
1770        assert data["foo"] == 1
1771        assert "Foo" not in data
1772
1773        data = MySchema(unknown=RAISE).load({"Foo": 1})
1774        assert data["foo"] == 1
1775        assert "Foo" not in data
1776
1777        with pytest.raises(ValidationError):
1778            MySchema(unknown=RAISE).load({"foo": 1})
1779
1780        data = MySchema(unknown=INCLUDE).load({"Foo": 1})
1781        assert data["foo"] == 1
1782        assert "Foo" not in data
1783
1784    def test_unknown_fields_deserialization_with_index_errors_false(self):
1785        class MySchema(Schema):
1786            foo = fields.Integer()
1787
1788            class Meta:
1789                unknown = RAISE
1790                index_errors = False
1791
1792        with pytest.raises(ValidationError) as excinfo:
1793            MySchema(many=True).load([{"foo": "invalid"}, {"foo": 42, "bar": 24}])
1794        err = excinfo.value
1795        assert 1 not in err.messages
1796        assert "foo" in err.messages
1797        assert "bar" in err.messages
1798        assert err.messages["foo"] == ["Not a valid integer."]
1799        assert err.messages["bar"] == ["Unknown field."]
1800
1801    def test_dump_only_fields_considered_unknown(self):
1802        class MySchema(Schema):
1803            foo = fields.Int(dump_only=True)
1804
1805        with pytest.raises(ValidationError) as excinfo:
1806            MySchema().load({"foo": 42})
1807        err = excinfo.value
1808        assert "foo" in err.messages
1809        assert err.messages["foo"] == ["Unknown field."]
1810
1811        # When unknown = INCLUDE, dump-only fields are included as unknown
1812        # without any validation.
1813        data = MySchema(unknown=INCLUDE).load({"foo": "LOL"})
1814        assert data["foo"] == "LOL"
1815
1816    def test_unknown_fields_do_not_unpack_dotted_names(self):
1817        class MySchema(Schema):
1818            class Meta:
1819                unknown = INCLUDE
1820
1821            foo = fields.Str()
1822            bar = fields.Str(data_key="bar.baz")
1823
1824        # dotted names are still supported
1825        data = MySchema().load({"foo": "hi", "bar.baz": "okay"})
1826        assert data == {"foo": "hi", "bar": "okay"}
1827
1828        # but extra keys included via unknown=INCLUDE are not transformed into nested dicts
1829        data = MySchema().load({"foo": "hi", "bar.baz": "okay", "alpha.beta": "woah!"})
1830        assert data == {"foo": "hi", "bar": "okay", "alpha.beta": "woah!"}
1831
1832
1833validators_gen = (func for func in [lambda x: x <= 24, lambda x: 18 <= x])
1834
1835validators_gen_float = (func for func in [lambda f: f <= 4.1, lambda f: f >= 1.0])
1836
1837validators_gen_str = (
1838    func for func in [lambda n: len(n) == 3, lambda n: n[1].lower() == "o"]
1839)
1840
1841
1842class TestValidation:
1843    def test_integer_with_validator(self):
1844        field = fields.Integer(validate=lambda x: 18 <= x <= 24)
1845        out = field.deserialize("20")
1846        assert out == 20
1847        with pytest.raises(ValidationError):
1848            field.deserialize(25)
1849
1850    @pytest.mark.parametrize(
1851        "field",
1852        [
1853            fields.Integer(validate=[lambda x: x <= 24, lambda x: 18 <= x]),
1854            fields.Integer(validate=(lambda x: x <= 24, lambda x: 18 <= x)),
1855            fields.Integer(validate=validators_gen),
1856        ],
1857    )
1858    def test_integer_with_validators(self, field):
1859        out = field.deserialize("20")
1860        assert out == 20
1861        with pytest.raises(ValidationError):
1862            field.deserialize(25)
1863
1864    @pytest.mark.parametrize(
1865        "field",
1866        [
1867            fields.Float(validate=[lambda f: f <= 4.1, lambda f: f >= 1.0]),
1868            fields.Float(validate=(lambda f: f <= 4.1, lambda f: f >= 1.0)),
1869            fields.Float(validate=validators_gen_float),
1870        ],
1871    )
1872    def test_float_with_validators(self, field):
1873        assert field.deserialize(3.14)
1874        with pytest.raises(ValidationError):
1875            field.deserialize(4.2)
1876
1877    def test_string_validator(self):
1878        field = fields.String(validate=lambda n: len(n) == 3)
1879        assert field.deserialize("Joe") == "Joe"
1880        with pytest.raises(ValidationError):
1881            field.deserialize("joseph")
1882
1883    def test_function_validator(self):
1884        field = fields.Function(
1885            lambda d: d.name.upper(), validate=lambda n: len(n) == 3
1886        )
1887        assert field.deserialize("joe")
1888        with pytest.raises(ValidationError):
1889            field.deserialize("joseph")
1890
1891    @pytest.mark.parametrize(
1892        "field",
1893        [
1894            fields.Function(
1895                lambda d: d.name.upper(),
1896                validate=[lambda n: len(n) == 3, lambda n: n[1].lower() == "o"],
1897            ),
1898            fields.Function(
1899                lambda d: d.name.upper(),
1900                validate=(lambda n: len(n) == 3, lambda n: n[1].lower() == "o"),
1901            ),
1902            fields.Function(lambda d: d.name.upper(), validate=validators_gen_str),
1903        ],
1904    )
1905    def test_function_validators(self, field):
1906        assert field.deserialize("joe")
1907        with pytest.raises(ValidationError):
1908            field.deserialize("joseph")
1909
1910    def test_method_validator(self):
1911        class MethodSerializer(Schema):
1912            name = fields.Method(
1913                "get_name", deserialize="get_name", validate=lambda n: len(n) == 3
1914            )
1915
1916            def get_name(self, val):
1917                return val.upper()
1918
1919        assert MethodSerializer().load({"name": "joe"})
1920        with pytest.raises(ValidationError, match="Invalid value."):
1921            MethodSerializer().load({"name": "joseph"})
1922
1923    # Regression test for https://github.com/marshmallow-code/marshmallow/issues/269
1924    def test_nested_data_is_stored_when_validation_fails(self):
1925        class SchemaA(Schema):
1926            x = fields.Integer()
1927            y = fields.Integer(validate=lambda n: n > 0)
1928            z = fields.Integer()
1929
1930        class SchemaB(Schema):
1931            w = fields.Integer()
1932            n = fields.Nested(SchemaA)
1933
1934        sch = SchemaB()
1935
1936        with pytest.raises(ValidationError) as excinfo:
1937            sch.load({"w": 90, "n": {"x": 90, "y": 89, "z": None}})
1938        data, errors = excinfo.value.valid_data, excinfo.value.messages
1939        assert "z" in errors["n"]
1940        assert data == {"w": 90, "n": {"x": 90, "y": 89}}
1941
1942        with pytest.raises(ValidationError) as excinfo:
1943            sch.load({"w": 90, "n": {"x": 90, "y": -1, "z": 180}})
1944        data, errors = excinfo.value.valid_data, excinfo.value.messages
1945        assert "y" in errors["n"]
1946        assert data == {"w": 90, "n": {"x": 90, "z": 180}}
1947
1948    def test_false_value_validation(self):
1949        class Sch(Schema):
1950            lamb = fields.Raw(validate=lambda x: x is False)
1951            equal = fields.Raw(validate=Equal(False))
1952
1953        errors = Sch().validate({"lamb": False, "equal": False})
1954        assert not errors
1955        errors = Sch().validate({"lamb": True, "equal": True})
1956        assert "lamb" in errors
1957        assert errors["lamb"] == ["Invalid value."]
1958        assert "equal" in errors
1959        assert errors["equal"] == ["Must be equal to False."]
1960
1961    def test_nested_partial_load(self):
1962        class SchemaA(Schema):
1963            x = fields.Integer(required=True)
1964            y = fields.Integer()
1965
1966        class SchemaB(Schema):
1967            z = fields.Nested(SchemaA)
1968
1969        b_dict = {"z": {"y": 42}}
1970        # Partial loading shouldn't generate any errors.
1971        result = SchemaB().load(b_dict, partial=True)
1972        assert result["z"]["y"] == 42
1973        # Non partial loading should complain about missing values.
1974        with pytest.raises(ValidationError) as excinfo:
1975            SchemaB().load(b_dict)
1976        data, errors = excinfo.value.valid_data, excinfo.value.messages
1977        assert data["z"]["y"] == 42
1978        assert "z" in errors
1979        assert "x" in errors["z"]
1980
1981    def test_deeply_nested_partial_load(self):
1982        class SchemaC(Schema):
1983            x = fields.Integer(required=True)
1984            y = fields.Integer()
1985
1986        class SchemaB(Schema):
1987            c = fields.Nested(SchemaC)
1988
1989        class SchemaA(Schema):
1990            b = fields.Nested(SchemaB)
1991
1992        a_dict = {"b": {"c": {"y": 42}}}
1993        # Partial loading shouldn't generate any errors.
1994        result = SchemaA().load(a_dict, partial=True)
1995        assert result["b"]["c"]["y"] == 42
1996        # Non partial loading should complain about missing values.
1997        with pytest.raises(ValidationError) as excinfo:
1998            SchemaA().load(a_dict)
1999        data, errors = excinfo.value.valid_data, excinfo.value.messages
2000        assert data["b"]["c"]["y"] == 42
2001        assert "b" in errors
2002        assert "c" in errors["b"]
2003        assert "x" in errors["b"]["c"]
2004
2005    def test_nested_partial_tuple(self):
2006        class SchemaA(Schema):
2007            x = fields.Integer(required=True)
2008            y = fields.Integer(required=True)
2009
2010        class SchemaB(Schema):
2011            z = fields.Nested(SchemaA)
2012
2013        b_dict = {"z": {"y": 42}}
2014        # If we ignore the missing z.x, z.y should still load.
2015        result = SchemaB().load(b_dict, partial=("z.x",))
2016        assert result["z"]["y"] == 42
2017        # If we ignore a missing z.y we should get a validation error.
2018        with pytest.raises(ValidationError):
2019            SchemaB().load(b_dict, partial=("z.y",))
2020
2021
2022@pytest.mark.parametrize("FieldClass", ALL_FIELDS)
2023def test_required_field_failure(FieldClass):  # noqa
2024    class RequireSchema(Schema):
2025        age = FieldClass(required=True)
2026
2027    user_data = {"name": "Phil"}
2028    with pytest.raises(ValidationError) as excinfo:
2029        RequireSchema().load(user_data)
2030    errors = excinfo.value.messages
2031    assert "Missing data for required field." in errors["age"]
2032
2033
2034@pytest.mark.parametrize(
2035    "message",
2036    [
2037        "My custom required message",
2038        {"error": "something", "code": 400},
2039        ["first error", "second error"],
2040    ],
2041)
2042def test_required_message_can_be_changed(message):
2043    class RequireSchema(Schema):
2044        age = fields.Integer(required=True, error_messages={"required": message})
2045
2046    user_data = {"name": "Phil"}
2047    with pytest.raises(ValidationError) as excinfo:
2048        RequireSchema().load(user_data)
2049    errors = excinfo.value.messages
2050    expected = [message] if isinstance(message, str) else message
2051    assert expected == errors["age"]
2052
2053
2054@pytest.mark.parametrize("unknown", (EXCLUDE, INCLUDE, RAISE))
2055@pytest.mark.parametrize("data", [True, False, 42, None, []])
2056def test_deserialize_raises_exception_if_input_type_is_incorrect(data, unknown):
2057    class MySchema(Schema):
2058        foo = fields.Field()
2059        bar = fields.Field()
2060
2061    with pytest.raises(ValidationError, match="Invalid input type.") as excinfo:
2062        MySchema(unknown=unknown).load(data)
2063    exc = excinfo.value
2064    assert list(exc.messages.keys()) == ["_schema"]
2065