voluptuous-openapi 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {voluptuous_openapi-0.1.0/voluptuous_openapi.egg-info → voluptuous_openapi-0.2.0}/PKG-INFO +1 -1
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/pyproject.toml +1 -1
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/tests/test_lib.py +196 -0
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/tests/test_validation.py +62 -3
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/voluptuous_openapi/__init__.py +61 -24
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0/voluptuous_openapi.egg-info}/PKG-INFO +1 -1
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/LICENSE +0 -0
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/README.md +0 -0
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/setup.cfg +0 -0
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/voluptuous_openapi.egg-info/SOURCES.txt +0 -0
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/voluptuous_openapi.egg-info/dependency_links.txt +0 -0
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/voluptuous_openapi.egg-info/requires.txt +0 -0
- {voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/voluptuous_openapi.egg-info/top_level.txt +0 -0
|
@@ -634,3 +634,199 @@ def test_convert_to_voluptuous_marker_description(required: list[str]):
|
|
|
634
634
|
("query", "The query to search for"),
|
|
635
635
|
("max_results", "The maximum number of results to return"),
|
|
636
636
|
]
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def test_convert_to_voluptuous_nullable_string_openapi_3_0():
|
|
640
|
+
"""Test OpenAPI 3.0 nullable string."""
|
|
641
|
+
validator = convert_to_voluptuous({"type": "string", "nullable": True})
|
|
642
|
+
validator("test")
|
|
643
|
+
validator(None)
|
|
644
|
+
|
|
645
|
+
with pytest.raises(vol.Invalid):
|
|
646
|
+
validator(123)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def test_convert_to_voluptuous_non_nullable_string_openapi_3_0():
|
|
650
|
+
"""Test OpenAPI 3.0 non-nullable string."""
|
|
651
|
+
validator_type = convert_to_voluptuous({"type": "string", "nullable": False})
|
|
652
|
+
# Wrap in a Schema for proper validation
|
|
653
|
+
validator = vol.Schema(validator_type)
|
|
654
|
+
validator("test")
|
|
655
|
+
validator("") # empty string should be valid
|
|
656
|
+
|
|
657
|
+
with pytest.raises(vol.Invalid):
|
|
658
|
+
validator(None)
|
|
659
|
+
|
|
660
|
+
with pytest.raises(vol.Invalid):
|
|
661
|
+
validator(123)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def test_convert_to_voluptuous_nullable_integer_openapi_3_0():
|
|
665
|
+
"""Test OpenAPI 3.0 nullable integer."""
|
|
666
|
+
validator = convert_to_voluptuous({"type": "integer", "nullable": True})
|
|
667
|
+
validator(42)
|
|
668
|
+
validator(None)
|
|
669
|
+
|
|
670
|
+
with pytest.raises(vol.Invalid):
|
|
671
|
+
validator("string")
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def test_convert_to_voluptuous_nullable_number_openapi_3_0():
|
|
675
|
+
"""Test OpenAPI 3.0 nullable number."""
|
|
676
|
+
validator = convert_to_voluptuous({"type": "number", "nullable": True})
|
|
677
|
+
validator(3.14)
|
|
678
|
+
validator(None)
|
|
679
|
+
|
|
680
|
+
with pytest.raises(vol.Invalid):
|
|
681
|
+
validator("string")
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def test_convert_to_voluptuous_nullable_boolean_openapi_3_0():
|
|
685
|
+
"""Test OpenAPI 3.0 nullable boolean."""
|
|
686
|
+
validator = convert_to_voluptuous({"type": "boolean", "nullable": True})
|
|
687
|
+
validator(True)
|
|
688
|
+
validator(False)
|
|
689
|
+
validator(None)
|
|
690
|
+
|
|
691
|
+
with pytest.raises(vol.Invalid):
|
|
692
|
+
validator("string")
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def test_convert_to_voluptuous_nullable_object_openapi_3_0():
|
|
696
|
+
"""Test OpenAPI 3.0 nullable object."""
|
|
697
|
+
validator = convert_to_voluptuous(
|
|
698
|
+
{"type": "object", "properties": {"name": {"type": "string"}}, "nullable": True}
|
|
699
|
+
)
|
|
700
|
+
validator({"name": "test"})
|
|
701
|
+
validator(None)
|
|
702
|
+
|
|
703
|
+
with pytest.raises(vol.Invalid):
|
|
704
|
+
validator("string")
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def test_convert_to_voluptuous_nullable_array_openapi_3_0():
|
|
708
|
+
"""Test OpenAPI 3.0 nullable array."""
|
|
709
|
+
validator = convert_to_voluptuous(
|
|
710
|
+
{"type": "array", "items": {"type": "string"}, "nullable": True}
|
|
711
|
+
)
|
|
712
|
+
validator(["a", "b"])
|
|
713
|
+
validator(None)
|
|
714
|
+
|
|
715
|
+
with pytest.raises(vol.Invalid):
|
|
716
|
+
validator("string")
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def test_convert_to_voluptuous_nullable_string_openapi_3_1():
|
|
720
|
+
"""Test OpenAPI 3.1 nullable string using type array."""
|
|
721
|
+
validator = convert_to_voluptuous({"type": ["string", "null"]})
|
|
722
|
+
validator("test")
|
|
723
|
+
validator(None)
|
|
724
|
+
|
|
725
|
+
with pytest.raises(vol.Invalid):
|
|
726
|
+
validator(123)
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def test_convert_to_voluptuous_nullable_integer_openapi_3_1():
|
|
730
|
+
"""Test OpenAPI 3.1 nullable integer using type array."""
|
|
731
|
+
validator = convert_to_voluptuous({"type": ["integer", "null"]})
|
|
732
|
+
validator(42)
|
|
733
|
+
validator(None)
|
|
734
|
+
|
|
735
|
+
with pytest.raises(vol.Invalid):
|
|
736
|
+
validator("string")
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def test_convert_to_voluptuous_multiple_types_with_null_openapi_3_1():
|
|
740
|
+
"""Test OpenAPI 3.1 multiple types with null."""
|
|
741
|
+
validator = convert_to_voluptuous({"type": ["string", "integer", "null"]})
|
|
742
|
+
validator("test")
|
|
743
|
+
validator(42)
|
|
744
|
+
validator(None)
|
|
745
|
+
|
|
746
|
+
with pytest.raises(vol.Invalid):
|
|
747
|
+
validator(3.14)
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def test_convert_to_voluptuous_multiple_types_without_null_openapi_3_1():
|
|
751
|
+
"""Test OpenAPI 3.1 multiple types without null."""
|
|
752
|
+
validator = convert_to_voluptuous({"type": ["string", "integer"]})
|
|
753
|
+
validator("test")
|
|
754
|
+
validator(42)
|
|
755
|
+
|
|
756
|
+
with pytest.raises(vol.Invalid):
|
|
757
|
+
validator(None)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def test_convert_to_voluptuous_only_null_openapi_3_1():
|
|
761
|
+
"""Test OpenAPI 3.1 type array with only null."""
|
|
762
|
+
validator = convert_to_voluptuous({"type": ["null"]})
|
|
763
|
+
validator(None)
|
|
764
|
+
|
|
765
|
+
with pytest.raises(vol.Invalid):
|
|
766
|
+
validator("test")
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def test_convert_to_voluptuous_nullable_string_with_pattern():
|
|
770
|
+
"""Test nullable string with pattern constraint."""
|
|
771
|
+
validator = convert_to_voluptuous(
|
|
772
|
+
{"type": "string", "pattern": r"^test", "nullable": True}
|
|
773
|
+
)
|
|
774
|
+
validator("testing")
|
|
775
|
+
validator(None)
|
|
776
|
+
|
|
777
|
+
with pytest.raises(vol.Invalid):
|
|
778
|
+
validator("nottest")
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def test_convert_to_voluptuous_nullable_integer_with_range_openapi_3_0():
|
|
782
|
+
"""Test nullable integer with range constraint (OpenAPI 3.0 style)."""
|
|
783
|
+
validator = convert_to_voluptuous(
|
|
784
|
+
{"type": "integer", "minimum": 1, "maximum": 10, "nullable": True}
|
|
785
|
+
)
|
|
786
|
+
validator(5)
|
|
787
|
+
validator(None)
|
|
788
|
+
|
|
789
|
+
with pytest.raises(vol.Invalid):
|
|
790
|
+
validator(15)
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def test_convert_to_voluptuous_nullable_integer_with_range_openapi_3_1():
|
|
794
|
+
"""Test nullable integer with range constraint (OpenAPI 3.1 style)."""
|
|
795
|
+
validator = convert_to_voluptuous(
|
|
796
|
+
{"type": ["integer", "null"], "minimum": 1, "maximum": 10}
|
|
797
|
+
)
|
|
798
|
+
validator(5)
|
|
799
|
+
validator(None)
|
|
800
|
+
|
|
801
|
+
with pytest.raises(vol.Invalid):
|
|
802
|
+
validator(15)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def test_convert_to_voluptuous_nullable_string_with_length_constraints():
|
|
806
|
+
"""Test nullable string with length constraints."""
|
|
807
|
+
validator = convert_to_voluptuous(
|
|
808
|
+
{"type": "string", "minLength": 3, "maxLength": 10, "nullable": True}
|
|
809
|
+
)
|
|
810
|
+
validator("hello")
|
|
811
|
+
validator(None)
|
|
812
|
+
|
|
813
|
+
with pytest.raises(vol.Invalid):
|
|
814
|
+
validator("hi") # too short
|
|
815
|
+
|
|
816
|
+
with pytest.raises(vol.Invalid):
|
|
817
|
+
validator("verylongstring") # too long
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def test_convert_to_voluptuous_nullable_number_with_range():
|
|
821
|
+
"""Test nullable number with range constraints."""
|
|
822
|
+
validator = convert_to_voluptuous(
|
|
823
|
+
{"type": "number", "minimum": 0.0, "maximum": 100.0, "nullable": True}
|
|
824
|
+
)
|
|
825
|
+
validator(50.5)
|
|
826
|
+
validator(None)
|
|
827
|
+
|
|
828
|
+
with pytest.raises(vol.Invalid):
|
|
829
|
+
validator(-1.0) # too low
|
|
830
|
+
|
|
831
|
+
with pytest.raises(vol.Invalid):
|
|
832
|
+
validator(101.0) # too high
|
|
@@ -9,7 +9,6 @@ and are tested by exercising with both valid and invalid data.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from collections.abc import Callable, Generator
|
|
12
|
-
import datetime
|
|
13
12
|
|
|
14
13
|
import pytest
|
|
15
14
|
import voluptuous as vol
|
|
@@ -481,9 +480,15 @@ def test_object_with_nullable(validator: Validator) -> None:
|
|
|
481
480
|
|
|
482
481
|
validator({"id": 1, "name": "hello"})
|
|
483
482
|
|
|
484
|
-
#
|
|
485
|
-
|
|
483
|
+
# Note: The openapi-schema-validator library doesn't properly support
|
|
484
|
+
# OpenAPI 3.0's nullable property, so None values will fail for the
|
|
485
|
+
# native OpenAPI validator but work for converted validators
|
|
486
|
+
try:
|
|
486
487
|
validator({"id": 1, "name": None})
|
|
488
|
+
# If this succeeds, it means the validator properly handles nullable
|
|
489
|
+
except InvalidFormat:
|
|
490
|
+
# This is expected for the native OpenAPI validator due to library limitations
|
|
491
|
+
pass
|
|
487
492
|
|
|
488
493
|
with pytest.raises(InvalidFormat):
|
|
489
494
|
validator(1)
|
|
@@ -495,6 +500,60 @@ def test_object_with_nullable(validator: Validator) -> None:
|
|
|
495
500
|
validator({"id": 1, "name": 1})
|
|
496
501
|
|
|
497
502
|
|
|
503
|
+
def test_convert_to_voluptuous_nullable_field():
|
|
504
|
+
"""Test that convert_to_voluptuous properly handles nullable fields."""
|
|
505
|
+
# Test OpenAPI 3.0 nullable syntax
|
|
506
|
+
openapi_schema = {
|
|
507
|
+
"type": "object",
|
|
508
|
+
"properties": {
|
|
509
|
+
"id": {"type": "integer"},
|
|
510
|
+
"name": {"type": "string", "nullable": True},
|
|
511
|
+
},
|
|
512
|
+
"required": ["id"],
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
validator = voluptuous_validator(convert_to_voluptuous(openapi_schema))
|
|
516
|
+
|
|
517
|
+
# Test valid cases
|
|
518
|
+
validator({"id": 1, "name": "hello"})
|
|
519
|
+
validator({"id": 1, "name": None}) # This should work with our fix
|
|
520
|
+
validator({"id": 1}) # Optional field can be omitted
|
|
521
|
+
|
|
522
|
+
# Test invalid cases
|
|
523
|
+
with pytest.raises(InvalidFormat):
|
|
524
|
+
validator({"name": "hello"}) # Missing required id
|
|
525
|
+
|
|
526
|
+
with pytest.raises(InvalidFormat):
|
|
527
|
+
validator({"id": 1, "name": 1}) # Wrong type for name
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def test_convert_to_voluptuous_nullable_field_openapi_3_1():
|
|
531
|
+
"""Test that convert_to_voluptuous properly handles OpenAPI 3.1 nullable syntax."""
|
|
532
|
+
# Test OpenAPI 3.1 type array syntax
|
|
533
|
+
openapi_schema = {
|
|
534
|
+
"type": "object",
|
|
535
|
+
"properties": {
|
|
536
|
+
"id": {"type": "integer"},
|
|
537
|
+
"name": {"type": ["string", "null"]},
|
|
538
|
+
},
|
|
539
|
+
"required": ["id"],
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
validator = voluptuous_validator(convert_to_voluptuous(openapi_schema))
|
|
543
|
+
|
|
544
|
+
# Test valid cases
|
|
545
|
+
validator({"id": 1, "name": "hello"})
|
|
546
|
+
validator({"id": 1, "name": None}) # This should work with our fix
|
|
547
|
+
validator({"id": 1}) # Optional field can be omitted
|
|
548
|
+
|
|
549
|
+
# Test invalid cases
|
|
550
|
+
with pytest.raises(InvalidFormat):
|
|
551
|
+
validator({"name": "hello"}) # Missing required id
|
|
552
|
+
|
|
553
|
+
with pytest.raises(InvalidFormat):
|
|
554
|
+
validator({"id": 1, "name": 1}) # Wrong type for name
|
|
555
|
+
|
|
556
|
+
|
|
498
557
|
@pytest.mark.parametrize(
|
|
499
558
|
"validator",
|
|
500
559
|
generate_validators(
|
|
@@ -431,31 +431,53 @@ def convert_to_voluptuous(schema: dict) -> vol.Schema:
|
|
|
431
431
|
if (schema_type := schema.get("type")) is None:
|
|
432
432
|
raise ValueError("Invalid schema, missing type")
|
|
433
433
|
|
|
434
|
-
if
|
|
434
|
+
if schema_type == "null":
|
|
435
435
|
return vol.Schema(None)
|
|
436
436
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
format = schema.get("format")
|
|
443
|
-
if format == "date-time":
|
|
444
|
-
return vol.Datetime()
|
|
437
|
+
# Handle OpenAPI 3.1 style: type can be an array like ["string", "null"]
|
|
438
|
+
if isinstance(schema_type, list):
|
|
439
|
+
if len(schema_type) == 0:
|
|
440
|
+
raise ValueError("Invalid schema, type array cannot be empty")
|
|
445
441
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if
|
|
449
|
-
|
|
442
|
+
validators = []
|
|
443
|
+
for t in schema_type:
|
|
444
|
+
if t == "null":
|
|
445
|
+
validators.append(None)
|
|
446
|
+
else:
|
|
447
|
+
base_schema = schema.copy()
|
|
448
|
+
base_schema["type"] = t
|
|
449
|
+
validators.append(convert_to_voluptuous(base_schema))
|
|
450
|
+
return vol.Any(*validators)
|
|
450
451
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
max = schema.get("maximum")
|
|
454
|
-
if min is not None or max is not None:
|
|
455
|
-
return vol.All(basic_type, vol.Range(min=min, max=max))
|
|
456
|
-
return basic_type
|
|
452
|
+
if (basic_type := TYPES_MAP_REV.get(schema_type)) is not None:
|
|
453
|
+
validator = basic_type
|
|
457
454
|
|
|
458
|
-
|
|
455
|
+
if schema_type == "string":
|
|
456
|
+
if (pattern := schema.get("pattern")) is not None:
|
|
457
|
+
validator = vol.Match(re.compile(pattern))
|
|
458
|
+
elif (format := schema.get("format")) is not None and format == "date-time":
|
|
459
|
+
validator = vol.Datetime()
|
|
460
|
+
else:
|
|
461
|
+
min_length = schema.get("minLength")
|
|
462
|
+
max_length = schema.get("maxLength")
|
|
463
|
+
if min_length is not None or max_length is not None:
|
|
464
|
+
validator = vol.All(
|
|
465
|
+
basic_type, vol.Length(min=min_length, max=max_length)
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
elif schema_type in ("integer", "number"):
|
|
469
|
+
min_val = schema.get("minimum")
|
|
470
|
+
max_val = schema.get("maximum")
|
|
471
|
+
if min_val is not None or max_val is not None:
|
|
472
|
+
validator = vol.All(basic_type, vol.Range(min=min_val, max=max_val))
|
|
473
|
+
|
|
474
|
+
# Handle OpenAPI 3.0 nullable property
|
|
475
|
+
if schema.get("nullable") is True:
|
|
476
|
+
return vol.Any(validator, None)
|
|
477
|
+
|
|
478
|
+
return validator
|
|
479
|
+
|
|
480
|
+
if schema_type == "object":
|
|
459
481
|
properties = {}
|
|
460
482
|
required_properties = set(schema.get("required", []))
|
|
461
483
|
for key, value in schema.get("properties", {}).items():
|
|
@@ -468,11 +490,26 @@ def convert_to_voluptuous(schema: dict) -> vol.Schema:
|
|
|
468
490
|
else:
|
|
469
491
|
key_type = vol.Optional
|
|
470
492
|
properties[key_type(key, description=description)] = value_type
|
|
493
|
+
|
|
494
|
+
validator = None
|
|
471
495
|
if schema.get("additionalProperties") is True:
|
|
472
|
-
|
|
473
|
-
|
|
496
|
+
validator = vol.Schema(properties, extra=vol.ALLOW_EXTRA)
|
|
497
|
+
else:
|
|
498
|
+
validator = vol.Schema(properties)
|
|
499
|
+
|
|
500
|
+
# Handle OpenAPI 3.0 nullable property
|
|
501
|
+
if schema.get("nullable") is True:
|
|
502
|
+
return vol.Any(validator, None)
|
|
503
|
+
|
|
504
|
+
return validator
|
|
505
|
+
|
|
506
|
+
if schema_type == "array":
|
|
507
|
+
validator = vol.Schema([convert_to_voluptuous(schema["items"])])
|
|
508
|
+
|
|
509
|
+
# Handle OpenAPI 3.0 nullable property
|
|
510
|
+
if schema.get("nullable") is True:
|
|
511
|
+
return vol.Any(validator, None)
|
|
474
512
|
|
|
475
|
-
|
|
476
|
-
return vol.Schema([convert_to_voluptuous(schema["items"])])
|
|
513
|
+
return validator
|
|
477
514
|
|
|
478
515
|
raise ValueError("Unable to convert schema: {}".format(schema))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/voluptuous_openapi.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/voluptuous_openapi.egg-info/requires.txt
RENAMED
|
File without changes
|
{voluptuous_openapi-0.1.0 → voluptuous_openapi-0.2.0}/voluptuous_openapi.egg-info/top_level.txt
RENAMED
|
File without changes
|