voluptuous-openapi 0.2.0__tar.gz → 0.3.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.2.0/voluptuous_openapi.egg-info → voluptuous_openapi-0.3.0}/PKG-INFO +1 -1
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/pyproject.toml +1 -1
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/tests/test_lib.py +186 -0
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/tests/test_validation.py +67 -0
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/voluptuous_openapi/__init__.py +84 -16
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0/voluptuous_openapi.egg-info}/PKG-INFO +1 -1
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/LICENSE +0 -0
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/README.md +0 -0
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/setup.cfg +0 -0
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/voluptuous_openapi.egg-info/SOURCES.txt +0 -0
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/voluptuous_openapi.egg-info/dependency_links.txt +0 -0
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/voluptuous_openapi.egg-info/requires.txt +0 -0
- {voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/voluptuous_openapi.egg-info/top_level.txt +0 -0
|
@@ -410,6 +410,11 @@ def test_key_any():
|
|
|
410
410
|
},
|
|
411
411
|
"required": [],
|
|
412
412
|
"type": "object",
|
|
413
|
+
"anyOf": [
|
|
414
|
+
{"required": ["hours"]},
|
|
415
|
+
{"required": ["minutes"]},
|
|
416
|
+
{"required": ["seconds"]},
|
|
417
|
+
],
|
|
413
418
|
} == convert(
|
|
414
419
|
{
|
|
415
420
|
vol.Required(vol.Any("hours", "minutes", "seconds")): int,
|
|
@@ -830,3 +835,184 @@ def test_convert_to_voluptuous_nullable_number_with_range():
|
|
|
830
835
|
|
|
831
836
|
with pytest.raises(vol.Invalid):
|
|
832
837
|
validator(101.0) # too high
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
TEST_TASK_ITEM = {"content": "a task ", "description": "a description"}
|
|
841
|
+
TASK_SCHEMA = {
|
|
842
|
+
"type": "object",
|
|
843
|
+
"properties": {
|
|
844
|
+
"tasks": {
|
|
845
|
+
"type": "array",
|
|
846
|
+
"items": {
|
|
847
|
+
"type": "object",
|
|
848
|
+
"properties": {
|
|
849
|
+
"content": {
|
|
850
|
+
"type": "string",
|
|
851
|
+
"description": "The content of the task to create.",
|
|
852
|
+
},
|
|
853
|
+
"description": {
|
|
854
|
+
"type": "string",
|
|
855
|
+
"description": "The description of the task.",
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
"required": ["content"],
|
|
859
|
+
"additionalProperties": False,
|
|
860
|
+
},
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
@pytest.mark.parametrize(
|
|
867
|
+
"extra_tasks_data, input_data",
|
|
868
|
+
[
|
|
869
|
+
({"minItems": 1, "maxItems": 2}, {"tasks": [TEST_TASK_ITEM]}),
|
|
870
|
+
({"minItems": 1, "maxItems": 2}, {"tasks": [TEST_TASK_ITEM, TEST_TASK_ITEM]}),
|
|
871
|
+
({"minItems": 1}, {"tasks": [TEST_TASK_ITEM]}),
|
|
872
|
+
({"maxItems": 2}, {"tasks": [TEST_TASK_ITEM, TEST_TASK_ITEM]}),
|
|
873
|
+
],
|
|
874
|
+
)
|
|
875
|
+
def test_with_min_max_items_success(extra_tasks_data, input_data):
|
|
876
|
+
task_schema = TASK_SCHEMA.copy()
|
|
877
|
+
task_schema["properties"]["tasks"].update(extra_tasks_data)
|
|
878
|
+
|
|
879
|
+
validator = convert_to_voluptuous(task_schema)
|
|
880
|
+
|
|
881
|
+
validator(input_data)
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
@pytest.mark.parametrize(
|
|
885
|
+
"extra_tasks_data, input_data",
|
|
886
|
+
[
|
|
887
|
+
({"minItems": 1, "maxItems": 2}, {"tasks": []}),
|
|
888
|
+
(
|
|
889
|
+
{"minItems": 1, "maxItems": 2},
|
|
890
|
+
{"tasks": [TEST_TASK_ITEM, TEST_TASK_ITEM, TEST_TASK_ITEM]},
|
|
891
|
+
),
|
|
892
|
+
({"minItems": 1}, {"tasks": []}),
|
|
893
|
+
({"maxItems": 2}, {"tasks": [TEST_TASK_ITEM, TEST_TASK_ITEM, TEST_TASK_ITEM]}),
|
|
894
|
+
],
|
|
895
|
+
)
|
|
896
|
+
def test_with_min_max_items_fails_validation(extra_tasks_data, input_data):
|
|
897
|
+
task_schema = TASK_SCHEMA.copy()
|
|
898
|
+
task_schema["properties"]["tasks"].update(extra_tasks_data)
|
|
899
|
+
|
|
900
|
+
validator = convert_to_voluptuous(task_schema)
|
|
901
|
+
|
|
902
|
+
with pytest.raises(vol.Invalid):
|
|
903
|
+
validator(input_data)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
def test_required_any_of():
|
|
907
|
+
"""Test schemas with Required(Any(...)) constraints."""
|
|
908
|
+
assert {
|
|
909
|
+
"properties": {
|
|
910
|
+
"color": {"type": "string"},
|
|
911
|
+
"temperature": {"type": "integer"},
|
|
912
|
+
"brightness": {"type": "integer"},
|
|
913
|
+
},
|
|
914
|
+
"required": [],
|
|
915
|
+
"type": "object",
|
|
916
|
+
"anyOf": [
|
|
917
|
+
{"required": ["color"]},
|
|
918
|
+
{"required": ["temperature"]},
|
|
919
|
+
{"required": ["brightness"]},
|
|
920
|
+
],
|
|
921
|
+
} == convert(
|
|
922
|
+
{
|
|
923
|
+
vol.Required(vol.Any("color", "temperature", "brightness")): object,
|
|
924
|
+
vol.Optional("color"): str,
|
|
925
|
+
vol.Optional("temperature"): int,
|
|
926
|
+
vol.Optional("brightness"): int,
|
|
927
|
+
}
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
assert {
|
|
931
|
+
"properties": {
|
|
932
|
+
"color": {"type": "string"},
|
|
933
|
+
"temperature": {"type": "integer"},
|
|
934
|
+
"brightness": {"type": "integer"},
|
|
935
|
+
"mode": {"type": "string"},
|
|
936
|
+
"preset": {"type": "string"},
|
|
937
|
+
},
|
|
938
|
+
"required": [],
|
|
939
|
+
"type": "object",
|
|
940
|
+
"anyOf": [
|
|
941
|
+
{"required": ["color", "mode"]},
|
|
942
|
+
{"required": ["color", "preset"]},
|
|
943
|
+
{"required": ["temperature", "mode"]},
|
|
944
|
+
{"required": ["temperature", "preset"]},
|
|
945
|
+
{"required": ["brightness", "mode"]},
|
|
946
|
+
{"required": ["brightness", "preset"]},
|
|
947
|
+
],
|
|
948
|
+
} == convert(
|
|
949
|
+
{
|
|
950
|
+
vol.Required(vol.Any("color", "temperature", "brightness")): object,
|
|
951
|
+
vol.Required(vol.Any("mode", "preset")): str,
|
|
952
|
+
vol.Optional("color"): str,
|
|
953
|
+
vol.Optional("temperature"): int,
|
|
954
|
+
vol.Optional("brightness"): int,
|
|
955
|
+
vol.Optional("mode"): str,
|
|
956
|
+
vol.Optional("preset"): str,
|
|
957
|
+
}
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
assert {
|
|
961
|
+
"properties": {
|
|
962
|
+
"entity_id": {"type": "string"},
|
|
963
|
+
"color": {"type": "string"},
|
|
964
|
+
"temperature": {"type": "integer"},
|
|
965
|
+
},
|
|
966
|
+
"required": ["entity_id"],
|
|
967
|
+
"type": "object",
|
|
968
|
+
"anyOf": [{"required": ["color"]}, {"required": ["temperature"]}],
|
|
969
|
+
} == convert(
|
|
970
|
+
{
|
|
971
|
+
vol.Required("entity_id"): str,
|
|
972
|
+
vol.Required(vol.Any("color", "temperature")): str,
|
|
973
|
+
vol.Optional("color"): str,
|
|
974
|
+
vol.Optional("temperature"): int,
|
|
975
|
+
}
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
def test_required_any_of_description():
|
|
980
|
+
"""Test that the description is preserved in a Required(Any(...)) constraint."""
|
|
981
|
+
assert {
|
|
982
|
+
"properties": {
|
|
983
|
+
"color": {"type": "string", "description": "Light appearance"},
|
|
984
|
+
"temperature": {"type": "string", "description": "Light appearance"},
|
|
985
|
+
},
|
|
986
|
+
"required": [],
|
|
987
|
+
"type": "object",
|
|
988
|
+
"anyOf": [{"required": ["color"]}, {"required": ["temperature"]}],
|
|
989
|
+
} == convert(
|
|
990
|
+
{
|
|
991
|
+
vol.Required(
|
|
992
|
+
vol.Any("color", "temperature"), description="Light appearance"
|
|
993
|
+
): str,
|
|
994
|
+
}
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def test_required_any_of_inner_description():
|
|
999
|
+
"""Test that inner descriptions are preferred in a Required(Any(...)) constraint."""
|
|
1000
|
+
assert {
|
|
1001
|
+
"properties": {
|
|
1002
|
+
"color": {"type": "string", "description": "Inner color description"},
|
|
1003
|
+
"temperature": {"type": "string", "description": "Outer description"},
|
|
1004
|
+
},
|
|
1005
|
+
"required": [],
|
|
1006
|
+
"type": "object",
|
|
1007
|
+
"anyOf": [{"required": ["color"]}, {"required": ["temperature"]}],
|
|
1008
|
+
} == convert(
|
|
1009
|
+
{
|
|
1010
|
+
vol.Required(
|
|
1011
|
+
vol.Any(
|
|
1012
|
+
vol.Optional("color", description="Inner color description"),
|
|
1013
|
+
"temperature",
|
|
1014
|
+
),
|
|
1015
|
+
description="Outer description",
|
|
1016
|
+
): str,
|
|
1017
|
+
}
|
|
1018
|
+
)
|
|
@@ -576,3 +576,70 @@ def test_maybe(validator: Validator) -> None:
|
|
|
576
576
|
|
|
577
577
|
with pytest.raises(InvalidFormat):
|
|
578
578
|
validator({"key": "value"})
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
@pytest.mark.parametrize(
|
|
582
|
+
"validator",
|
|
583
|
+
generate_validators(
|
|
584
|
+
{
|
|
585
|
+
"type": "object",
|
|
586
|
+
"properties": {
|
|
587
|
+
"color": {"type": "string"},
|
|
588
|
+
"temperature": {"type": "integer"},
|
|
589
|
+
"brightness": {
|
|
590
|
+
"type": "integer",
|
|
591
|
+
"minimum": 0,
|
|
592
|
+
"maximum": 100,
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
"anyOf": [
|
|
596
|
+
{"required": ["color"]},
|
|
597
|
+
{"required": ["temperature"]},
|
|
598
|
+
{"required": ["brightness"]},
|
|
599
|
+
],
|
|
600
|
+
},
|
|
601
|
+
vol.Schema(
|
|
602
|
+
{
|
|
603
|
+
vol.Required(vol.Any("color", "temperature", "brightness")): object,
|
|
604
|
+
vol.Optional("color"): str,
|
|
605
|
+
vol.Optional("temperature"): int,
|
|
606
|
+
vol.Optional("brightness"): vol.All(int, vol.Range(min=0, max=100)),
|
|
607
|
+
}
|
|
608
|
+
),
|
|
609
|
+
),
|
|
610
|
+
ids=TEST_IDS,
|
|
611
|
+
)
|
|
612
|
+
def test_any_of_constraint(validator: Validator) -> None:
|
|
613
|
+
"""Test anyOf constraint for requiring at least one of multiple properties."""
|
|
614
|
+
# Test valid cases
|
|
615
|
+
validator({"color": "red"})
|
|
616
|
+
validator({"temperature": 20})
|
|
617
|
+
validator({"brightness": 80})
|
|
618
|
+
validator({"brightness": 0})
|
|
619
|
+
validator({"brightness": 100})
|
|
620
|
+
validator({"color": "blue", "temperature": 25})
|
|
621
|
+
validator({"color": "green", "brightness": 100})
|
|
622
|
+
validator({"temperature": 22, "brightness": 90})
|
|
623
|
+
validator({"color": "purple", "temperature": 21, "brightness": 70})
|
|
624
|
+
|
|
625
|
+
# Test invalid cases
|
|
626
|
+
with pytest.raises(InvalidFormat):
|
|
627
|
+
validator({}) # Missing all required properties
|
|
628
|
+
|
|
629
|
+
with pytest.raises(InvalidFormat):
|
|
630
|
+
validator({"other_field": "value"}) # Missing all required properties
|
|
631
|
+
|
|
632
|
+
with pytest.raises(InvalidFormat):
|
|
633
|
+
validator({"brightness": -1}) # Out of range
|
|
634
|
+
|
|
635
|
+
with pytest.raises(InvalidFormat):
|
|
636
|
+
validator({"brightness": 101}) # Out of range
|
|
637
|
+
|
|
638
|
+
with pytest.raises(InvalidFormat):
|
|
639
|
+
validator({"brightness": "abc"}) # Wrong type
|
|
640
|
+
|
|
641
|
+
with pytest.raises(InvalidFormat):
|
|
642
|
+
validator({"color": 123}) # Wrong type
|
|
643
|
+
|
|
644
|
+
with pytest.raises(InvalidFormat):
|
|
645
|
+
validator({"temperature": "abc"}) # Wrong type
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from collections.abc import Callable, Mapping, Sequence
|
|
4
4
|
from inspect import signature
|
|
5
5
|
from enum import Enum, StrEnum
|
|
6
|
+
import itertools
|
|
6
7
|
import re
|
|
7
8
|
from typing import Any, TypeVar, Union, get_args, get_origin, get_type_hints
|
|
8
9
|
from types import NoneType, UnionType
|
|
@@ -24,8 +25,6 @@ UNSUPPORTED = object()
|
|
|
24
25
|
OPENAPI_UNSUPPORTED_KEYWORDS = {
|
|
25
26
|
"allOf",
|
|
26
27
|
"multipleOf",
|
|
27
|
-
"minItems",
|
|
28
|
-
"maxItems",
|
|
29
28
|
"uniqueItems",
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -88,6 +87,9 @@ def convert(
|
|
|
88
87
|
if isinstance(schema, Mapping):
|
|
89
88
|
properties = {}
|
|
90
89
|
required = []
|
|
90
|
+
any_of_constraint_groups: list[list[str]] = (
|
|
91
|
+
[]
|
|
92
|
+
) # List of lists, each containing candidate keys for a Required(Any(...))
|
|
91
93
|
|
|
92
94
|
for key, value in schema.items():
|
|
93
95
|
description = None
|
|
@@ -101,22 +103,57 @@ def convert(
|
|
|
101
103
|
if description:
|
|
102
104
|
pval["description"] = key.description
|
|
103
105
|
|
|
104
|
-
if isinstance(key,
|
|
106
|
+
if isinstance(key, vol.Required):
|
|
107
|
+
if not isinstance(key.schema, vol.Any):
|
|
108
|
+
required.append(str(key.schema))
|
|
109
|
+
if key.default is not vol.UNDEFINED:
|
|
110
|
+
pval["default"] = key.default()
|
|
111
|
+
elif isinstance(key, vol.Optional):
|
|
105
112
|
if key.default is not vol.UNDEFINED:
|
|
106
113
|
pval["default"] = key.default()
|
|
107
114
|
|
|
108
115
|
pval = ensure_default(pval)
|
|
109
116
|
|
|
110
117
|
if isinstance(pkey, vol.Any):
|
|
111
|
-
for
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
# Handle Required(Any(...)) pattern for anyOf constraints
|
|
119
|
+
if isinstance(key, vol.Required):
|
|
120
|
+
# Extract candidate keys and their descriptions from Any validator
|
|
121
|
+
candidate_items = []
|
|
122
|
+
for val_item in pkey.validators:
|
|
123
|
+
item_key = ""
|
|
124
|
+
item_desc = None
|
|
125
|
+
if isinstance(val_item, vol.Marker):
|
|
126
|
+
item_key = str(val_item.schema)
|
|
127
|
+
item_desc = val_item.description
|
|
116
128
|
else:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
item_key = str(val_item)
|
|
130
|
+
candidate_items.append({"key": item_key, "desc": item_desc})
|
|
131
|
+
|
|
132
|
+
candidate_keys = [item["key"] for item in candidate_items]
|
|
133
|
+
any_of_constraint_groups.append(candidate_keys)
|
|
134
|
+
|
|
135
|
+
# If the value is not a wildcard, create properties for each
|
|
136
|
+
# candidate key, preserving any descriptions.
|
|
137
|
+
if value is not object:
|
|
138
|
+
for item in candidate_items:
|
|
139
|
+
prop_schema = pval.copy()
|
|
140
|
+
final_description = item["desc"] or description
|
|
141
|
+
if final_description:
|
|
142
|
+
prop_schema["description"] = final_description
|
|
143
|
+
properties[item["key"]] = prop_schema
|
|
144
|
+
else:
|
|
145
|
+
# Handle Optional(Any(...)) - expand to individual properties
|
|
146
|
+
for val_item in pkey.validators:
|
|
147
|
+
if isinstance(val_item, vol.Marker):
|
|
148
|
+
if val_item.description:
|
|
149
|
+
properties[str(val_item.schema)] = pval.copy()
|
|
150
|
+
properties[str(val_item.schema)][
|
|
151
|
+
"description"
|
|
152
|
+
] = val_item.description
|
|
153
|
+
else:
|
|
154
|
+
properties[str(val_item)] = pval
|
|
155
|
+
else:
|
|
156
|
+
properties[str(val_item)] = pval
|
|
120
157
|
elif isinstance(pkey, str):
|
|
121
158
|
properties[pkey] = pval
|
|
122
159
|
else:
|
|
@@ -126,15 +163,21 @@ def convert(
|
|
|
126
163
|
if additional_properties is None:
|
|
127
164
|
additional_properties = pval
|
|
128
165
|
|
|
129
|
-
if isinstance(key, vol.Required) and not isinstance(pkey, vol.Any):
|
|
130
|
-
required.append(str(pkey))
|
|
131
|
-
|
|
132
166
|
val = {"type": "object"}
|
|
133
167
|
if properties or not additional_properties:
|
|
134
168
|
val["properties"] = properties
|
|
135
169
|
val["required"] = required
|
|
136
170
|
if additional_properties:
|
|
137
171
|
val["additionalProperties"] = additional_properties
|
|
172
|
+
|
|
173
|
+
# Generate anyOf constraints from the Cartesian product of constraint groups
|
|
174
|
+
if any_of_constraint_groups:
|
|
175
|
+
# Generate all combinations (Cartesian product) of the constraint groups
|
|
176
|
+
val["anyOf"] = [
|
|
177
|
+
{"required": list(combination)}
|
|
178
|
+
for combination in itertools.product(*any_of_constraint_groups)
|
|
179
|
+
]
|
|
180
|
+
|
|
138
181
|
return val
|
|
139
182
|
|
|
140
183
|
if isinstance(schema, vol.All):
|
|
@@ -426,7 +469,16 @@ def convert_to_voluptuous(schema: dict) -> vol.Schema:
|
|
|
426
469
|
if (any_of := schema.get("anyOf")) is not None:
|
|
427
470
|
if not isinstance(any_of, list):
|
|
428
471
|
raise ValueError("Invalid schema, anyOf should be a list")
|
|
429
|
-
|
|
472
|
+
# If the anyOf is a list of required properties, it's a constraint on an
|
|
473
|
+
# object and should be handled by the object validator.
|
|
474
|
+
is_required_constraint = all(
|
|
475
|
+
list(s.keys()) == ["required"] and isinstance(s["required"], list)
|
|
476
|
+
for s in any_of
|
|
477
|
+
)
|
|
478
|
+
if not (is_required_constraint and schema.get("type") == "object"):
|
|
479
|
+
return vol.Any(
|
|
480
|
+
*[convert_to_voluptuous(sub_schema) for sub_schema in any_of]
|
|
481
|
+
)
|
|
430
482
|
|
|
431
483
|
if (schema_type := schema.get("type")) is None:
|
|
432
484
|
raise ValueError("Invalid schema, missing type")
|
|
@@ -491,6 +543,13 @@ def convert_to_voluptuous(schema: dict) -> vol.Schema:
|
|
|
491
543
|
key_type = vol.Optional
|
|
492
544
|
properties[key_type(key, description=description)] = value_type
|
|
493
545
|
|
|
546
|
+
if (any_of := schema.get("anyOf")) is not None:
|
|
547
|
+
any_of_keys = [
|
|
548
|
+
key for sub_schema in any_of for key in sub_schema.get("required", [])
|
|
549
|
+
]
|
|
550
|
+
if any_of_keys:
|
|
551
|
+
properties[vol.Required(vol.Any(*any_of_keys))] = object
|
|
552
|
+
|
|
494
553
|
validator = None
|
|
495
554
|
if schema.get("additionalProperties") is True:
|
|
496
555
|
validator = vol.Schema(properties, extra=vol.ALLOW_EXTRA)
|
|
@@ -504,7 +563,16 @@ def convert_to_voluptuous(schema: dict) -> vol.Schema:
|
|
|
504
563
|
return validator
|
|
505
564
|
|
|
506
565
|
if schema_type == "array":
|
|
507
|
-
|
|
566
|
+
item_validator = convert_to_voluptuous(schema["items"])
|
|
567
|
+
min_items = schema.get("minItems")
|
|
568
|
+
max_items = schema.get("maxItems")
|
|
569
|
+
|
|
570
|
+
if min_items is not None or max_items is not None:
|
|
571
|
+
validator = vol.Schema(
|
|
572
|
+
vol.All([item_validator], vol.Length(min=min_items, max=max_items))
|
|
573
|
+
)
|
|
574
|
+
else:
|
|
575
|
+
validator = vol.Schema([item_validator])
|
|
508
576
|
|
|
509
577
|
# Handle OpenAPI 3.0 nullable property
|
|
510
578
|
if schema.get("nullable") is True:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/voluptuous_openapi.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/voluptuous_openapi.egg-info/requires.txt
RENAMED
|
File without changes
|
{voluptuous_openapi-0.2.0 → voluptuous_openapi-0.3.0}/voluptuous_openapi.egg-info/top_level.txt
RENAMED
|
File without changes
|