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