Skip to content

Commit 2d74640

Browse files
authored
refactor(parser): Schema datatypes validation by pydantic [#478]. Thanks @mtovts!
1 parent 75c69f0 commit 2d74640

File tree

7 files changed

+104
-61
lines changed

7 files changed

+104
-61
lines changed

openapi_python_client/parser/properties/__init__.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -535,9 +535,9 @@ def _property_from_data(
535535
return build_union_property(
536536
data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config
537537
)
538-
if data.type == "string":
538+
if data.type == oai.DataType.STRING:
539539
return _string_based_property(name=name, required=required, data=data, config=config), schemas
540-
if data.type == "number":
540+
if data.type == oai.DataType.NUMBER:
541541
return (
542542
FloatProperty(
543543
name=name,
@@ -548,7 +548,7 @@ def _property_from_data(
548548
),
549549
schemas,
550550
)
551-
if data.type == "integer":
551+
if data.type == oai.DataType.INTEGER:
552552
return (
553553
IntProperty(
554554
name=name,
@@ -559,7 +559,7 @@ def _property_from_data(
559559
),
560560
schemas,
561561
)
562-
if data.type == "boolean":
562+
if data.type == oai.DataType.BOOLEAN:
563563
return (
564564
BooleanProperty(
565565
name=name,
@@ -570,26 +570,24 @@ def _property_from_data(
570570
),
571571
schemas,
572572
)
573-
if data.type == "array":
573+
if data.type == oai.DataType.ARRAY:
574574
return build_list_property(
575575
data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config
576576
)
577-
if data.type == "object" or data.allOf:
577+
if data.type == oai.DataType.OBJECT or data.allOf:
578578
return build_model_property(
579579
data=data, name=name, schemas=schemas, required=required, parent_name=parent_name, config=config
580580
)
581-
if not data.type:
582-
return (
583-
AnyProperty(
584-
name=name,
585-
required=required,
586-
nullable=False,
587-
default=None,
588-
python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix),
589-
),
590-
schemas,
591-
)
592-
return PropertyError(data=data, detail=f"unknown type {data.type}"), schemas
581+
return (
582+
AnyProperty(
583+
name=name,
584+
required=required,
585+
nullable=False,
586+
default=None,
587+
python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix),
588+
),
589+
schemas,
590+
)
593591

594592

595593
def property_from_data(

openapi_python_client/schema/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"Operation",
55
"Parameter",
66
"ParameterLocation",
7+
"DataType",
78
"PathItem",
89
"Reference",
910
"RequestBody",
@@ -13,6 +14,7 @@
1314
]
1415

1516

17+
from .data_type import DataType
1618
from .openapi_schema_pydantic import (
1719
MediaType,
1820
OpenAPI,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from enum import Enum
2+
3+
4+
class DataType(str, Enum):
5+
"""The data type of a schema is defined by the type keyword
6+
7+
References:
8+
- https://swagger.io/docs/specification/data-models/data-types/
9+
"""
10+
11+
STRING = "string"
12+
NUMBER = "number"
13+
INTEGER = "integer"
14+
BOOLEAN = "boolean"
15+
ARRAY = "array"
16+
OBJECT = "object"

openapi_python_client/schema/openapi_schema_pydantic/schema.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pydantic import BaseModel, Field
44

5+
from ..data_type import DataType
56
from .discriminator import Discriminator
67
from .external_documentation import ExternalDocumentation
78
from .reference import Reference
@@ -35,7 +36,7 @@ class Schema(BaseModel):
3536
minProperties: Optional[int] = Field(default=None, ge=0)
3637
required: Optional[List[str]] = Field(default=None, min_items=1)
3738
enum: Optional[List[Any]] = Field(default=None, min_items=1)
38-
type: Optional[str] = None
39+
type: Optional[DataType] = Field(default=None)
3940
allOf: Optional[List[Union[Reference, "Schema"]]] = None
4041
oneOf: List[Union[Reference, "Schema"]] = []
4142
anyOf: List[Union[Reference, "Schema"]] = []

tests/test_parser/test_properties/test_init.py

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -605,21 +605,6 @@ def test_property_from_data_union_of_one_element(self, mocker, model_property_fa
605605
assert prop == attr.evolve(existing_model, name=name, required=required, nullable=nullable, python_name=name)
606606
build_union_property.assert_not_called()
607607

608-
def test_property_from_data_unsupported_type(self, mocker):
609-
name = mocker.MagicMock()
610-
required = mocker.MagicMock()
611-
data = oai.Schema.construct(type=mocker.MagicMock())
612-
613-
from openapi_python_client.parser.errors import PropertyError
614-
from openapi_python_client.parser.properties import Schemas, property_from_data
615-
616-
assert property_from_data(
617-
name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock()
618-
) == (
619-
PropertyError(data=data, detail=f"unknown type {data.type}"),
620-
Schemas(),
621-
)
622-
623608
def test_property_from_data_no_valid_props_in_data(self):
624609
from openapi_python_client.parser.properties import AnyProperty, Schemas, property_from_data
625610

@@ -744,19 +729,19 @@ def test_property_from_data_union(
744729
assert p == expected
745730
assert s == Schemas()
746731

747-
def test_property_from_data_union_bad_type(self, mocker):
732+
def test_build_union_property_invalid_property(self, mocker):
748733
name = "bad_union"
749734
required = mocker.MagicMock()
750-
data = oai.Schema(anyOf=[{"type": "garbage"}])
735+
reference = oai.Reference.construct(ref="#/components/schema/NotExist")
736+
data = oai.Schema(anyOf=[reference])
751737
mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
752738

753-
from openapi_python_client.parser.properties import Schemas, property_from_data
739+
from openapi_python_client.parser.properties import Schemas, build_union_property
754740

755-
p, s = property_from_data(
741+
p, s = build_union_property(
756742
name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock()
757743
)
758-
759-
assert p == PropertyError(detail=f"Invalid property in union {name}", data=oai.Schema(type="garbage"))
744+
assert p == PropertyError(detail=f"Invalid property in union {name}", data=reference)
760745

761746

762747
class TestStringBasedProperty:

tests/test_parser/test_properties/test_model_property.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -140,41 +140,33 @@ def test_model_name_conflict(self):
140140
assert new_schemas == schemas
141141
assert err == PropertyError(detail='Attempted to generate duplicate models with name "OtherModel"', data=data)
142142

143-
def test_bad_props_return_error(self):
143+
def test_model_bad_properties(self):
144144
from openapi_python_client.parser.properties import Schemas, build_model_property
145145

146146
data = oai.Schema(
147147
properties={
148-
"bad": oai.Schema(type="not_real"),
148+
"bad": oai.Reference.construct(ref="#/components/schema/NotExist"),
149149
},
150150
)
151-
schemas = Schemas()
152-
153-
err, new_schemas = build_model_property(
154-
data=data, name="prop", schemas=schemas, required=True, parent_name=None, config=Config()
155-
)
156-
157-
assert new_schemas == schemas
158-
assert err == PropertyError(detail="unknown type not_real", data=oai.Schema(type="not_real"))
151+
result = build_model_property(
152+
data=data, name="prop", schemas=Schemas(), required=True, parent_name="parent", config=Config()
153+
)[0]
154+
assert isinstance(result, PropertyError)
159155

160-
def test_bad_additional_props_return_error(self):
161-
from openapi_python_client.parser.properties import Config, Schemas, build_model_property
156+
def test_model_bad_additional_properties(self):
157+
from openapi_python_client.parser.properties import Schemas, build_model_property
162158

163159
additional_properties = oai.Schema(
164160
type="object",
165161
properties={
166-
"bad": oai.Schema(type="not_real"),
162+
"bad": oai.Reference(ref="#/components/schemas/not_exist"),
167163
},
168164
)
169165
data = oai.Schema(additionalProperties=additional_properties)
170-
schemas = Schemas()
171-
172-
err, new_schemas = build_model_property(
173-
data=data, name="prop", schemas=schemas, required=True, parent_name=None, config=Config()
174-
)
175-
176-
assert new_schemas == schemas
177-
assert err == PropertyError(detail="unknown type not_real", data=oai.Schema(type="not_real"))
166+
result = build_model_property(
167+
data=data, name="prop", schemas=Schemas(), required=True, parent_name="parent", config=Config()
168+
)[0]
169+
assert isinstance(result, PropertyError)
178170

179171

180172
class TestProcessProperties:
@@ -198,6 +190,20 @@ def test_conflicting_properties_different_types(
198190

199191
assert isinstance(result, PropertyError)
200192

193+
def test_process_properties_reference_not_exist(self):
194+
from openapi_python_client.parser.properties import Schemas
195+
from openapi_python_client.parser.properties.model_property import _process_properties
196+
197+
data = oai.Schema(
198+
properties={
199+
"bad": oai.Reference.construct(ref="#/components/schema/NotExist"),
200+
},
201+
)
202+
203+
result = _process_properties(data=data, class_name="", schemas=Schemas(), config=Config())
204+
205+
assert isinstance(result, PropertyError)
206+
201207
def test_invalid_reference(self, model_property_factory):
202208
from openapi_python_client.parser.properties import Schemas
203209
from openapi_python_client.parser.properties.model_property import _process_properties

tests/test_schema/test_data_type.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import pytest
2+
3+
import openapi_python_client.schema as oai
4+
5+
6+
class TestDataType:
7+
def test_schema_bad_types(self):
8+
import pydantic
9+
10+
with pytest.raises(pydantic.ValidationError):
11+
oai.Schema(type="bad_type")
12+
13+
with pytest.raises(pydantic.ValidationError):
14+
oai.Schema(anyOf=[{"type": "garbage"}])
15+
16+
with pytest.raises(pydantic.ValidationError):
17+
oai.Schema(
18+
properties={
19+
"bad": oai.Schema(type="not_real"),
20+
},
21+
)
22+
23+
@pytest.mark.parametrize(
24+
"type_",
25+
(
26+
"string",
27+
"number",
28+
"integer",
29+
"boolean",
30+
"array",
31+
"object",
32+
),
33+
)
34+
def test_schema_happy(self, type_):
35+
assert oai.Schema(type=type_).type == type_

0 commit comments

Comments
 (0)