|
1 | 1 | import re
|
2 | 2 | import sys
|
| 3 | +from decimal import Decimal |
3 | 4 | from enum import Enum, IntEnum, IntFlag
|
4 | 5 |
|
5 | 6 | import pytest
|
@@ -344,3 +345,145 @@ class ColorEnum(IntEnum):
|
344 | 345 |
|
345 | 346 | assert v.validate_python(ColorEnum.GREEN) is ColorEnum.GREEN
|
346 | 347 | assert v.validate_python(1 << 63) is ColorEnum.GREEN
|
| 348 | + |
| 349 | + |
| 350 | +@pytest.mark.parametrize( |
| 351 | + 'value', |
| 352 | + [-1, 0, 1], |
| 353 | +) |
| 354 | +def test_enum_int_validation_should_succeed_for_decimal(value: int): |
| 355 | + class MyEnum(Enum): |
| 356 | + VALUE = value |
| 357 | + |
| 358 | + class MyIntEnum(IntEnum): |
| 359 | + VALUE = value |
| 360 | + |
| 361 | + v = SchemaValidator( |
| 362 | + core_schema.with_default_schema( |
| 363 | + schema=core_schema.enum_schema(MyEnum, list(MyEnum.__members__.values())), |
| 364 | + default=MyEnum.VALUE, |
| 365 | + ) |
| 366 | + ) |
| 367 | + |
| 368 | + v_int = SchemaValidator( |
| 369 | + core_schema.with_default_schema( |
| 370 | + schema=core_schema.enum_schema(MyIntEnum, list(MyIntEnum.__members__.values())), |
| 371 | + default=MyIntEnum.VALUE, |
| 372 | + ) |
| 373 | + ) |
| 374 | + |
| 375 | + assert v.validate_python(Decimal(value)) is MyEnum.VALUE |
| 376 | + assert v.validate_python(Decimal(float(value))) is MyEnum.VALUE |
| 377 | + assert v_int.validate_python(Decimal(value)) is MyIntEnum.VALUE |
| 378 | + assert v_int.validate_python(Decimal(float(value))) is MyIntEnum.VALUE |
| 379 | + |
| 380 | + |
| 381 | +@pytest.mark.skipif( |
| 382 | + sys.version_info >= (3, 13), |
| 383 | + reason='Python 3.13+ enum initialization is different, see https://github.com/python/cpython/blob/ec610069637d56101896803a70d418a89afe0b4b/Lib/enum.py#L1159-L1163', |
| 384 | +) |
| 385 | +def test_enum_int_validation_should_succeed_for_custom_type(): |
| 386 | + class AnyWrapper: |
| 387 | + def __init__(self, value): |
| 388 | + self.value = value |
| 389 | + |
| 390 | + def __eq__(self, other: object) -> bool: |
| 391 | + return self.value == other |
| 392 | + |
| 393 | + class MyEnum(Enum): |
| 394 | + VALUE = 999 |
| 395 | + SECOND_VALUE = 1000000 |
| 396 | + THIRD_VALUE = 'Py03' |
| 397 | + |
| 398 | + v = SchemaValidator( |
| 399 | + core_schema.with_default_schema( |
| 400 | + schema=core_schema.enum_schema(MyEnum, list(MyEnum.__members__.values())), |
| 401 | + default=MyEnum.VALUE, |
| 402 | + ) |
| 403 | + ) |
| 404 | + |
| 405 | + assert v.validate_python(AnyWrapper(999)) is MyEnum.VALUE |
| 406 | + assert v.validate_python(AnyWrapper(1000000)) is MyEnum.SECOND_VALUE |
| 407 | + assert v.validate_python(AnyWrapper('Py03')) is MyEnum.THIRD_VALUE |
| 408 | + |
| 409 | + |
| 410 | +def test_enum_str_validation_should_fail_for_decimal_when_expecting_str_value(): |
| 411 | + class MyEnum(Enum): |
| 412 | + VALUE = '1' |
| 413 | + |
| 414 | + v = SchemaValidator( |
| 415 | + core_schema.with_default_schema( |
| 416 | + schema=core_schema.enum_schema(MyEnum, list(MyEnum.__members__.values())), |
| 417 | + default=MyEnum.VALUE, |
| 418 | + ) |
| 419 | + ) |
| 420 | + |
| 421 | + with pytest.raises(ValidationError): |
| 422 | + v.validate_python(Decimal(1)) |
| 423 | + |
| 424 | + |
| 425 | +def test_enum_int_validation_should_fail_for_incorrect_decimal_value(): |
| 426 | + class MyEnum(Enum): |
| 427 | + VALUE = 1 |
| 428 | + |
| 429 | + v = SchemaValidator( |
| 430 | + core_schema.with_default_schema( |
| 431 | + schema=core_schema.enum_schema(MyEnum, list(MyEnum.__members__.values())), |
| 432 | + default=MyEnum.VALUE, |
| 433 | + ) |
| 434 | + ) |
| 435 | + |
| 436 | + with pytest.raises(ValidationError): |
| 437 | + v.validate_python(Decimal(2)) |
| 438 | + |
| 439 | + with pytest.raises(ValidationError): |
| 440 | + v.validate_python((1, 2)) |
| 441 | + |
| 442 | + with pytest.raises(ValidationError): |
| 443 | + v.validate_python(Decimal(1.1)) |
| 444 | + |
| 445 | + |
| 446 | +def test_enum_int_validation_should_fail_for_plain_type_without_eq_checking(): |
| 447 | + class MyEnum(Enum): |
| 448 | + VALUE = 1 |
| 449 | + |
| 450 | + class MyClass: |
| 451 | + def __init__(self, value): |
| 452 | + self.value = value |
| 453 | + |
| 454 | + v = SchemaValidator( |
| 455 | + core_schema.with_default_schema( |
| 456 | + schema=core_schema.enum_schema(MyEnum, list(MyEnum.__members__.values())), |
| 457 | + default=MyEnum.VALUE, |
| 458 | + ) |
| 459 | + ) |
| 460 | + |
| 461 | + with pytest.raises(ValidationError): |
| 462 | + v.validate_python(MyClass(1)) |
| 463 | + |
| 464 | + |
| 465 | +def support_custom_new_method() -> None: |
| 466 | + """Demonstrates support for custom new methods, as well as conceptually, multi-value enums without dependency on a 3rd party lib for testing.""" |
| 467 | + |
| 468 | + class Animal(Enum): |
| 469 | + CAT = 'cat', 'meow' |
| 470 | + DOG = 'dog', 'woof' |
| 471 | + |
| 472 | + def __new__(cls, species: str, sound: str): |
| 473 | + obj = object.__new__(cls) |
| 474 | + |
| 475 | + obj._value_ = species |
| 476 | + obj._all_values = (species, sound) |
| 477 | + |
| 478 | + obj.species = species |
| 479 | + obj.sound = sound |
| 480 | + |
| 481 | + cls._value2member_map_[sound] = obj |
| 482 | + |
| 483 | + return obj |
| 484 | + |
| 485 | + v = SchemaValidator(core_schema.enum_schema(Animal, list(Animal.__members__.values()))) |
| 486 | + assert v.validate_python('cat') is Animal.CAT |
| 487 | + assert v.validate_python('meow') is Animal.CAT |
| 488 | + assert v.validate_python('dog') is Animal.DOG |
| 489 | + assert v.validate_python('woof') is Animal.DOG |
0 commit comments