From f1264205e665fc3fc00aed6f45cec19e65d369d0 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Tue, 17 Aug 2021 20:47:28 +0300 Subject: [PATCH 1/8] Add `DataType` for Schema type validation --- .../parser/properties/__init__.py | 35 +++++++++---------- openapi_python_client/schema/__init__.py | 2 ++ openapi_python_client/schema/data_type.py | 16 +++++++++ .../schema/openapi_schema_pydantic/schema.py | 5 +-- 4 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 openapi_python_client/schema/data_type.py diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index b1701959f..b580d03f0 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -17,6 +17,7 @@ from ... import Config from ... import schema as oai from ... import utils +from ...schema import DataType from ..errors import ParseError, PropertyError, ValidationError from .converter import convert, convert_chain from .enum_property import EnumProperty @@ -535,9 +536,9 @@ def _property_from_data( return build_union_property( data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config ) - if data.type == "string": + if data.type == DataType.STRING: return _string_based_property(name=name, required=required, data=data, config=config), schemas - if data.type == "number": + if data.type == DataType.NUMBER: return ( FloatProperty( name=name, @@ -548,7 +549,7 @@ def _property_from_data( ), schemas, ) - if data.type == "integer": + if data.type == DataType.INTEGER: return ( IntProperty( name=name, @@ -559,7 +560,7 @@ def _property_from_data( ), schemas, ) - if data.type == "boolean": + if data.type == DataType.BOOLEAN: return ( BooleanProperty( name=name, @@ -570,26 +571,24 @@ def _property_from_data( ), schemas, ) - if data.type == "array": + if data.type == DataType.ARRAY: return build_list_property( data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config ) - if data.type == "object" or data.allOf: + if data.type == DataType.OBJECT or data.allOf: return build_model_property( data=data, name=name, schemas=schemas, required=required, parent_name=parent_name, config=config ) - if not data.type: - return ( - AnyProperty( - name=name, - required=required, - nullable=False, - default=None, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), - ), - schemas, - ) - return PropertyError(data=data, detail=f"unknown type {data.type}"), schemas + return ( + AnyProperty( + name=name, + required=required, + nullable=False, + default=None, + python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + ), + schemas, + ) def property_from_data( diff --git a/openapi_python_client/schema/__init__.py b/openapi_python_client/schema/__init__.py index b27693d77..151fe298e 100644 --- a/openapi_python_client/schema/__init__.py +++ b/openapi_python_client/schema/__init__.py @@ -4,6 +4,7 @@ "Operation", "Parameter", "ParameterLocation", + "DataType", "PathItem", "Reference", "RequestBody", @@ -13,6 +14,7 @@ ] +from .data_type import DataType from .openapi_schema_pydantic import ( MediaType, OpenAPI, diff --git a/openapi_python_client/schema/data_type.py b/openapi_python_client/schema/data_type.py new file mode 100644 index 000000000..ee597000a --- /dev/null +++ b/openapi_python_client/schema/data_type.py @@ -0,0 +1,16 @@ +from enum import Enum + + +class DataType(str, Enum): + """The data type of a schema is defined by the type keyword + + References: + - https://swagger.io/docs/specification/data-models/data-types/ + """ + + STRING = "string" + NUMBER = "number" + INTEGER = "integer" + BOOLEAN = "boolean" + ARRAY = "array" + OBJECT = "object" diff --git a/openapi_python_client/schema/openapi_schema_pydantic/schema.py b/openapi_python_client/schema/openapi_schema_pydantic/schema.py index bdac3cdf0..56d90c898 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/schema.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/schema.py @@ -2,6 +2,7 @@ from pydantic import BaseModel, Field +from ..data_type import DataType from .discriminator import Discriminator from .external_documentation import ExternalDocumentation from .reference import Reference @@ -16,7 +17,7 @@ class Schema(BaseModel): References: - https://swagger.io/docs/specification/data-models/ - - https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schemaObject + - https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schemaObject """ title: Optional[str] = None @@ -35,7 +36,7 @@ class Schema(BaseModel): minProperties: Optional[int] = Field(default=None, ge=0) required: Optional[List[str]] = Field(default=None, min_items=1) enum: Optional[List[Any]] = Field(default=None, min_items=1) - type: Optional[str] = None + type: Optional[DataType] = Field(default=None) allOf: Optional[List[Union[Reference, "Schema"]]] = None oneOf: List[Union[Reference, "Schema"]] = [] anyOf: List[Union[Reference, "Schema"]] = [] From ffc533f0e96e2348e1eeaea0a734f84d7039dfae Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Tue, 17 Aug 2021 20:47:49 +0300 Subject: [PATCH 2/8] Fix tests --- .../test_parser/test_properties/test_init.py | 36 ++++++------------- .../test_properties/test_model_property.py | 36 ------------------- 2 files changed, 10 insertions(+), 62 deletions(-) diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 6d0c53a85..25e9020f8 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -605,21 +605,6 @@ def test_property_from_data_union_of_one_element(self, mocker, model_property_fa assert prop == attr.evolve(existing_model, name=name, required=required, nullable=nullable, python_name=name) build_union_property.assert_not_called() - def test_property_from_data_unsupported_type(self, mocker): - name = mocker.MagicMock() - required = mocker.MagicMock() - data = oai.Schema.construct(type=mocker.MagicMock()) - - from openapi_python_client.parser.errors import PropertyError - from openapi_python_client.parser.properties import Schemas, property_from_data - - assert property_from_data( - name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() - ) == ( - PropertyError(data=data, detail=f"unknown type {data.type}"), - Schemas(), - ) - def test_property_from_data_no_valid_props_in_data(self): from openapi_python_client.parser.properties import AnyProperty, Schemas, property_from_data @@ -744,19 +729,18 @@ def test_property_from_data_union( assert p == expected assert s == Schemas() - def test_property_from_data_union_bad_type(self, mocker): - name = "bad_union" - required = mocker.MagicMock() - data = oai.Schema(anyOf=[{"type": "garbage"}]) - mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name) - - from openapi_python_client.parser.properties import Schemas, property_from_data + def test_schema_bad_type(self, mocker): + import pydantic - p, s = property_from_data( - name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() - ) + with pytest.raises(pydantic.ValidationError): + oai.Schema(anyOf=[{"type": "garbage"}]) - assert p == PropertyError(detail=f"Invalid property in union {name}", data=oai.Schema(type="garbage")) + with pytest.raises(pydantic.ValidationError): + oai.Schema( + properties={ + "bad": oai.Schema(type="not_real"), + }, + ) class TestStringBasedProperty: diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index 5f179eab2..a082eb13a 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -140,42 +140,6 @@ def test_model_name_conflict(self): assert new_schemas == schemas assert err == PropertyError(detail='Attempted to generate duplicate models with name "OtherModel"', data=data) - def test_bad_props_return_error(self): - from openapi_python_client.parser.properties import Schemas, build_model_property - - data = oai.Schema( - properties={ - "bad": oai.Schema(type="not_real"), - }, - ) - schemas = Schemas() - - err, new_schemas = build_model_property( - data=data, name="prop", schemas=schemas, required=True, parent_name=None, config=Config() - ) - - assert new_schemas == schemas - assert err == PropertyError(detail="unknown type not_real", data=oai.Schema(type="not_real")) - - def test_bad_additional_props_return_error(self): - from openapi_python_client.parser.properties import Config, Schemas, build_model_property - - additional_properties = oai.Schema( - type="object", - properties={ - "bad": oai.Schema(type="not_real"), - }, - ) - data = oai.Schema(additionalProperties=additional_properties) - schemas = Schemas() - - err, new_schemas = build_model_property( - data=data, name="prop", schemas=schemas, required=True, parent_name=None, config=Config() - ) - - assert new_schemas == schemas - assert err == PropertyError(detail="unknown type not_real", data=oai.Schema(type="not_real")) - class TestProcessProperties: def test_conflicting_properties_different_types( From c9ebb9a618a7ed609e8d76e071a995b76e690d8c Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Tue, 17 Aug 2021 21:52:25 +0300 Subject: [PATCH 3/8] Add `test_schema/test_data_type.py` --- .../test_parser/test_properties/test_init.py | 13 --------- tests/test_schema/test_data_type.py | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 tests/test_schema/test_data_type.py diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 25e9020f8..c34a5851d 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -729,19 +729,6 @@ def test_property_from_data_union( assert p == expected assert s == Schemas() - def test_schema_bad_type(self, mocker): - import pydantic - - with pytest.raises(pydantic.ValidationError): - oai.Schema(anyOf=[{"type": "garbage"}]) - - with pytest.raises(pydantic.ValidationError): - oai.Schema( - properties={ - "bad": oai.Schema(type="not_real"), - }, - ) - class TestStringBasedProperty: @pytest.mark.parametrize("nullable", (True, False)) diff --git a/tests/test_schema/test_data_type.py b/tests/test_schema/test_data_type.py new file mode 100644 index 000000000..8795194ab --- /dev/null +++ b/tests/test_schema/test_data_type.py @@ -0,0 +1,29 @@ +import pytest +import openapi_python_client.schema as oai + + +class TestDataType: + def test_schema_bad_types(self): + import pydantic + + with pytest.raises(pydantic.ValidationError): + oai.Schema(type="bad_type") + + with pytest.raises(pydantic.ValidationError): + oai.Schema(anyOf=[{"type": "garbage"}]) + + with pytest.raises(pydantic.ValidationError): + oai.Schema( + properties={ + "bad": oai.Schema(type="not_real"), + }, + ) + + @pytest.mark.parametrize( + "type_", + ( + "string", "number", "integer", "boolean", "array", "object", + ), + ) + def test_schema_happy(self, type_): + assert oai.Schema(type=type_).type == type_ From 791805a9bf6fe1b7bb8e65e2adb7c2d2ed2e3a1d Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Tue, 17 Aug 2021 22:30:57 +0300 Subject: [PATCH 4/8] Add `test_build_union_property_invalid_property` --- tests/test_parser/test_properties/test_init.py | 14 ++++++++++++++ tests/test_schema/test_data_type.py | 8 +++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index c34a5851d..8a6dd26a4 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -729,6 +729,20 @@ def test_property_from_data_union( assert p == expected assert s == Schemas() + def test_build_union_property_invalid_property(self, mocker): + name = "bad_union" + required = mocker.MagicMock() + reference = oai.Reference.construct(ref="#/components/schema/NotExist") + data = oai.Schema(anyOf=[reference]) + mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name) + + from openapi_python_client.parser.properties import Schemas, build_union_property + + p, s = build_union_property( + name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() + ) + assert p == PropertyError(detail=f"Invalid property in union {name}", data=reference) + class TestStringBasedProperty: @pytest.mark.parametrize("nullable", (True, False)) diff --git a/tests/test_schema/test_data_type.py b/tests/test_schema/test_data_type.py index 8795194ab..19aa256eb 100644 --- a/tests/test_schema/test_data_type.py +++ b/tests/test_schema/test_data_type.py @@ -1,4 +1,5 @@ import pytest + import openapi_python_client.schema as oai @@ -22,7 +23,12 @@ def test_schema_bad_types(self): @pytest.mark.parametrize( "type_", ( - "string", "number", "integer", "boolean", "array", "object", + "string", + "number", + "integer", + "boolean", + "array", + "object", ), ) def test_schema_happy(self, type_): From 2d343a95450aeea2a985b4351d2e69d79a549018 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:40:29 +0300 Subject: [PATCH 5/8] add `test_process_properties_reference_not_exist` --- .../test_properties/test_model_property.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index a082eb13a..f57da9417 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -162,6 +162,20 @@ def test_conflicting_properties_different_types( assert isinstance(result, PropertyError) + def test_process_properties_reference_not_exist(self): + from openapi_python_client.parser.properties import Schemas + from openapi_python_client.parser.properties.model_property import _process_properties + + data = oai.Schema( + properties={ + "bad": oai.Reference.construct(ref="#/components/schema/NotExist"), + }, + ) + + result = _process_properties(data=data, class_name="", schemas=Schemas(), config=Config()) + + assert isinstance(result, PropertyError) + def test_invalid_reference(self, model_property_factory): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties From 22aa3802c40594db56d25133ab1e1dc2244bdc9b Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:59:45 +0300 Subject: [PATCH 6/8] Add tests for bad properties/additional_properties --- .../test_properties/test_model_property.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index f57da9417..6e1d98166 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -140,6 +140,34 @@ def test_model_name_conflict(self): assert new_schemas == schemas assert err == PropertyError(detail='Attempted to generate duplicate models with name "OtherModel"', data=data) + def test_model_bad_properties(self): + from openapi_python_client.parser.properties import Schemas, build_model_property + + data = oai.Schema( + properties={ + "bad": oai.Reference.construct(ref="#/components/schema/NotExist"), + }, + ) + result = build_model_property( + data=data, name="prop", schemas=Schemas(), required=True, parent_name="parent", config=Config() + )[0] + assert isinstance(result, PropertyError) + + def test_model_bad_additional_properties(self): + from openapi_python_client.parser.properties import Schemas, build_model_property + + additional_properties = oai.Schema( + type="object", + properties={ + "bad": oai.Reference(ref="#/components/schemas/not_exist"), + }, + ) + data = oai.Schema(additionalProperties=additional_properties) + result = build_model_property( + data=data, name="prop", schemas=Schemas(), required=True, parent_name="parent", config=Config() + )[0] + assert isinstance(result, PropertyError) + class TestProcessProperties: def test_conflicting_properties_different_types( From e8a9722e10a6dbba896fd8e700eb949d129e20e2 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Mon, 23 Aug 2021 10:53:39 +0300 Subject: [PATCH 7/8] Return OAS version to 3.0.3 --- openapi_python_client/schema/openapi_schema_pydantic/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_python_client/schema/openapi_schema_pydantic/schema.py b/openapi_python_client/schema/openapi_schema_pydantic/schema.py index 56d90c898..d1bf5a43e 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/schema.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/schema.py @@ -17,7 +17,7 @@ class Schema(BaseModel): References: - https://swagger.io/docs/specification/data-models/ - - https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schemaObject + - https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schemaObject """ title: Optional[str] = None From 88b4b6875dbfa0ea4f3e66b0852b8a7629715853 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Mon, 23 Aug 2021 10:54:17 +0300 Subject: [PATCH 8/8] remove duplicate import --- openapi_python_client/parser/properties/__init__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index b580d03f0..22b799f04 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -17,7 +17,6 @@ from ... import Config from ... import schema as oai from ... import utils -from ...schema import DataType from ..errors import ParseError, PropertyError, ValidationError from .converter import convert, convert_chain from .enum_property import EnumProperty @@ -536,9 +535,9 @@ def _property_from_data( return build_union_property( data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config ) - if data.type == DataType.STRING: + if data.type == oai.DataType.STRING: return _string_based_property(name=name, required=required, data=data, config=config), schemas - if data.type == DataType.NUMBER: + if data.type == oai.DataType.NUMBER: return ( FloatProperty( name=name, @@ -549,7 +548,7 @@ def _property_from_data( ), schemas, ) - if data.type == DataType.INTEGER: + if data.type == oai.DataType.INTEGER: return ( IntProperty( name=name, @@ -560,7 +559,7 @@ def _property_from_data( ), schemas, ) - if data.type == DataType.BOOLEAN: + if data.type == oai.DataType.BOOLEAN: return ( BooleanProperty( name=name, @@ -571,11 +570,11 @@ def _property_from_data( ), schemas, ) - if data.type == DataType.ARRAY: + if data.type == oai.DataType.ARRAY: return build_list_property( data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config ) - if data.type == DataType.OBJECT or data.allOf: + if data.type == oai.DataType.OBJECT or data.allOf: return build_model_property( data=data, name=name, schemas=schemas, required=required, parent_name=parent_name, config=config )