From cdff9df25d10bace4ab9fbf29c146afdc003f927 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Thu, 23 Jul 2020 12:08:57 -0400 Subject: [PATCH 1/2] Switch OpenAPI parsing to use openapi-schema-pydantic --- CHANGELOG.md | 5 + openapi_python_client/__init__.py | 8 +- .../openapi_parser/__init__.py | 4 +- .../openapi_parser/openapi.py | 142 ++++++----- .../openapi_parser/properties.py | 63 ++--- .../openapi_parser/responses.py | 33 +-- poetry.lock | 20 +- pyproject.toml | 1 + tests/test___init__.py | 18 +- tests/test_openapi_parser/test_openapi.py | 234 +++++++++++------- tests/test_openapi_parser/test_properties.py | 152 +++++------- tests/test_openapi_parser/test_responses.py | 78 ++++-- 12 files changed, 420 insertions(+), 338 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48153fca9..79485d252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.5.0 - Unreleased +### Internal Changes +- Switched OpenAPI document parsing to use + [openapi-schema-pydantic](https://github.com/kuimono/openapi-schema-pydantic/pull/1) (#103) + ## 0.4.2 - 2020-06-13 ### Additions - Support for responses with no content (#63 & #66). Thanks @acgray! diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index ef41e6a15..eb01fcd74 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -14,7 +14,7 @@ from openapi_python_client import utils -from .openapi_parser import OpenAPI, import_string_from_reference +from .openapi_parser import GeneratorData, import_string_from_reference from .openapi_parser.errors import MultipleParseError if sys.version_info.minor == 7: # version did not exist in 3.7, need to use a backport @@ -28,7 +28,7 @@ def _get_project_for_url_or_path(url: Optional[str], path: Optional[Path]) -> _Project: data_dict = _get_json(url=url, path=path) - openapi = OpenAPI.from_dict(data_dict) + openapi = GeneratorData.from_dict(data_dict) return _Project(openapi=openapi) @@ -72,8 +72,8 @@ def _get_json(*, url: Optional[str], path: Optional[Path]) -> Dict[str, Any]: class _Project: TEMPLATE_FILTERS = {"snakecase": utils.snake_case} - def __init__(self, *, openapi: OpenAPI) -> None: - self.openapi: OpenAPI = openapi + def __init__(self, *, openapi: GeneratorData) -> None: + self.openapi: GeneratorData = openapi self.env: Environment = Environment(loader=PackageLoader(__package__), trim_blocks=True, lstrip_blocks=True) self.project_name: str = f"{openapi.title.replace(' ', '-').lower()}-client" diff --git a/openapi_python_client/openapi_parser/__init__.py b/openapi_python_client/openapi_parser/__init__.py index 0eff43778..65fd27df9 100644 --- a/openapi_python_client/openapi_parser/__init__.py +++ b/openapi_python_client/openapi_parser/__init__.py @@ -1,5 +1,5 @@ """ Classes representing the data in the OpenAPI schema """ -__all__ = ["OpenAPI", "import_string_from_reference"] +__all__ = ["GeneratorData", "import_string_from_reference"] -from .openapi import OpenAPI, import_string_from_reference +from .openapi import GeneratorData, import_string_from_reference diff --git a/openapi_python_client/openapi_parser/openapi.py b/openapi_python_client/openapi_parser/openapi.py index 0be09bfc2..e57b05692 100644 --- a/openapi_python_client/openapi_parser/openapi.py +++ b/openapi_python_client/openapi_parser/openapi.py @@ -2,12 +2,14 @@ from dataclasses import dataclass, field from enum import Enum -from typing import Any, Dict, List, Optional, Set +from typing import Any, Dict, List, Optional, Set, Union + +import openapi_schema_pydantic as oai from .errors import ParseError -from .properties import EnumProperty, Property, property_from_dict +from .properties import EnumProperty, Property, property_from_data from .reference import Reference -from .responses import ListRefResponse, RefResponse, Response, response_from_dict +from .responses import ListRefResponse, RefResponse, Response, response_from_data class ParameterLocation(str, Enum): @@ -32,16 +34,21 @@ class EndpointCollection: parse_errors: List[ParseError] = field(default_factory=list) @staticmethod - def from_dict(d: Dict[str, Dict[str, Dict[str, Any]]]) -> Dict[str, EndpointCollection]: + def from_data(*, data: Dict[str, oai.PathItem]) -> Dict[str, EndpointCollection]: """ Parse the openapi paths data to get EndpointCollections by tag """ endpoints_by_tag: Dict[str, EndpointCollection] = {} - for path, path_data in d.items(): - for method, method_data in path_data.items(): - tag = method_data.get("tags", ["default"])[0] + methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"] + + for path, path_data in data.items(): + for method in methods: + operation: Optional[oai.Operation] = getattr(path_data, method) + if operation is None: + continue + tag = (operation.tags or ["default"])[0] collection = endpoints_by_tag.setdefault(tag, EndpointCollection(tag=tag)) try: - endpoint = Endpoint.from_data(data=method_data, path=path, method=method, tag=tag) + endpoint = Endpoint.from_data(data=operation, path=path, method=method, tag=tag) collection.endpoints.append(endpoint) collection.relative_imports.update(endpoint.relative_imports) except ParseError as e: @@ -72,40 +79,40 @@ class Endpoint: multipart_body_reference: Optional[Reference] = None @staticmethod - def parse_request_form_body(body: Dict[str, Any]) -> Optional[Reference]: + def parse_request_form_body(body: oai.RequestBody) -> Optional[Reference]: """ Return form_body_reference """ - body_content = body["content"] + body_content = body.content form_body = body_content.get("application/x-www-form-urlencoded") - if form_body: - return Reference.from_ref(form_body["schema"]["$ref"]) + if form_body is not None and isinstance(form_body.media_type_schema, oai.Reference): + return Reference.from_ref(form_body.media_type_schema.ref) return None @staticmethod - def parse_multipart_body(body: Dict[str, Any]) -> Optional[Reference]: + def parse_multipart_body(body: oai.RequestBody) -> Optional[Reference]: """ Return form_body_reference """ - body_content = body["content"] - body = body_content.get("multipart/form-data") - if body: - return Reference.from_ref(body["schema"]["$ref"]) + body_content = body.content + json_body = body_content.get("multipart/form-data") + if json_body is not None and isinstance(json_body.media_type_schema, oai.Reference): + return Reference.from_ref(json_body.media_type_schema.ref) return None @staticmethod - def parse_request_json_body(body: Dict[str, Any]) -> Optional[Property]: + def parse_request_json_body(body: oai.RequestBody) -> Optional[Property]: """ Return json_body """ - body_content = body["content"] + body_content = body.content json_body = body_content.get("application/json") - if json_body: - return property_from_dict("json_body", required=True, data=json_body["schema"]) + if json_body is not None and json_body.media_type_schema is not None: + return property_from_data("json_body", required=True, data=json_body.media_type_schema) return None - def _add_body(self, data: Dict[str, Any]) -> None: + def _add_body(self, data: oai.Operation) -> None: """ Adds form or JSON body to Endpoint if included in data """ - if "requestBody" not in data: + if data.requestBody is None or isinstance(data.requestBody, oai.Reference): return - self.form_body_reference = Endpoint.parse_request_form_body(data["requestBody"]) - self.json_body = Endpoint.parse_request_json_body(data["requestBody"]) - self.multipart_body_reference = Endpoint.parse_multipart_body(data["requestBody"]) + self.form_body_reference = Endpoint.parse_request_form_body(data.requestBody) + self.json_body = Endpoint.parse_request_json_body(data.requestBody) + self.multipart_body_reference = Endpoint.parse_multipart_body(data.requestBody) if self.form_body_reference: self.relative_imports.add(import_string_from_reference(self.form_body_reference, prefix="..models")) @@ -114,41 +121,46 @@ def _add_body(self, data: Dict[str, Any]) -> None: if self.json_body is not None: self.relative_imports.update(self.json_body.get_imports(prefix="..models")) - def _add_responses(self, data: Dict[str, Any]) -> None: - for code, response_dict in data["responses"].items(): - response = response_from_dict(status_code=int(code), data=response_dict) + def _add_responses(self, data: oai.Responses) -> None: + for code, response_data in data.items(): + response = response_from_data(status_code=int(code), data=response_data) if isinstance(response, (RefResponse, ListRefResponse)): self.relative_imports.add(import_string_from_reference(response.reference, prefix="..models")) self.responses.append(response) - def _add_parameters(self, data: Dict[str, Any]) -> None: - for param_dict in data.get("parameters", []): - prop = property_from_dict( - name=param_dict["name"], required=param_dict["required"], data=param_dict["schema"] - ) + def _add_parameters(self, data: oai.Operation) -> None: + if data.parameters is None: + return + for param in data.parameters: + if isinstance(param, oai.Reference) or param.param_schema is None: + continue + prop = property_from_data(name=param.name, required=param.required, data=param.param_schema) self.relative_imports.update(prop.get_imports(prefix="..models")) - if param_dict["in"] == ParameterLocation.QUERY: + if param.param_in == ParameterLocation.QUERY: self.query_parameters.append(prop) - elif param_dict["in"] == ParameterLocation.PATH: + elif param.param_in == ParameterLocation.PATH: self.path_parameters.append(prop) else: - raise ValueError(f"Don't know where to put this parameter: {param_dict}") + raise ValueError(f"Don't know where to put this parameter: {param.dict()}") @staticmethod - def from_data(*, data: Dict[str, Any], path: str, method: str, tag: str) -> Endpoint: + def from_data(*, data: oai.Operation, path: str, method: str, tag: str) -> Endpoint: """ Construct an endpoint from the OpenAPI data """ + if data.operationId is None: + raise ParseError(data=data, message="Path operations with operationId are not yet supported") + endpoint = Endpoint( path=path, method=method, - description=data.get("description"), - name=data["operationId"], - requires_security=bool(data.get("security")), + description=data.description, + name=data.operationId, + requires_security=bool(data.security), tag=tag, ) endpoint._add_parameters(data) - endpoint._add_responses(data) + endpoint._add_responses(data.responses) endpoint._add_body(data) return endpoint @@ -169,24 +181,26 @@ class Schema: relative_imports: Set[str] @staticmethod - def from_dict(d: Dict[str, Any], name: str) -> Schema: - """ A single Schema from its dict representation + def from_data(*, data: Union[oai.Reference, oai.Schema], name: str) -> Schema: + """ A single Schema from its OAI data Args: - d: Dict representation of the schema + data: Data of a single Schema name: Name by which the schema is referenced, such as a model name. Used to infer the type name if a `title` property is not available. """ - required_set = set(d.get("required", [])) + if isinstance(data, oai.Reference): + raise ParseError("Reference schemas are not supported.") + required_set = set(data.required or []) required_properties: List[Property] = [] optional_properties: List[Property] = [] relative_imports: Set[str] = set() - ref = Reference.from_ref(d.get("title", name)) + ref = Reference.from_ref(data.title or name) - for key, value in d.get("properties", {}).items(): + for key, value in (data.properties or {}).items(): required = key in required_set - p = property_from_dict(name=key, required=required, data=value) + p = property_from_data(name=key, required=required, data=value) if required: required_properties.append(p) else: @@ -198,23 +212,23 @@ def from_dict(d: Dict[str, Any], name: str) -> Schema: required_properties=required_properties, optional_properties=optional_properties, relative_imports=relative_imports, - description=d.get("description", ""), + description=data.description or "", ) return schema @staticmethod - def dict(d: Dict[str, Dict[str, Any]]) -> Dict[str, Schema]: + def build(*, schemas: Dict[str, Union[oai.Reference, oai.Schema]]) -> Dict[str, Schema]: """ Get a list of Schemas from an OpenAPI dict """ result = {} - for name, data in d.items(): - s = Schema.from_dict(data, name=name) + for name, data in schemas.items(): + s = Schema.from_data(data=data, name=name) result[s.reference.class_name] = s return result @dataclass -class OpenAPI: - """ Top level OpenAPI document """ +class GeneratorData: + """ All the data needed to generate a client """ title: str description: Optional[str] @@ -224,16 +238,20 @@ class OpenAPI: enums: Dict[str, EnumProperty] @staticmethod - def from_dict(d: Dict[str, Dict[str, Any]]) -> OpenAPI: + def from_dict(d: Dict[str, Dict[str, Any]]) -> GeneratorData: """ Create an OpenAPI from dict """ - schemas = Schema.dict(d["components"]["schemas"]) - endpoint_collections_by_tag = EndpointCollection.from_dict(d["paths"]) + openapi = oai.OpenAPI.parse_obj(d) + if openapi.components is None or openapi.components.schemas is None: + schemas = {} + else: + schemas = Schema.build(schemas=openapi.components.schemas) + endpoint_collections_by_tag = EndpointCollection.from_data(data=openapi.paths) enums = EnumProperty.get_all_enums() - return OpenAPI( - title=d["info"]["title"], - description=d["info"].get("description"), - version=d["info"]["version"], + return GeneratorData( + title=openapi.info.title, + description=openapi.info.description, + version=openapi.info.version, endpoint_collections_by_tag=endpoint_collections_by_tag, schemas=schemas, enums=enums, diff --git a/openapi_python_client/openapi_parser/properties.py b/openapi_python_client/openapi_parser/properties.py index 7ea22147f..5fb0fed4b 100644 --- a/openapi_python_client/openapi_parser/properties.py +++ b/openapi_python_client/openapi_parser/properties.py @@ -3,8 +3,9 @@ from dataclasses import InitVar, dataclass, field from typing import Any, ClassVar, Dict, Generic, List, Optional, Set, TypeVar, Union -from openapi_python_client import utils +import openapi_schema_pydantic as oai +from .. import utils from .errors import ParseError from .reference import Reference @@ -357,54 +358,54 @@ def get_imports(self, *, prefix: str) -> Set[str]: def _string_based_property( - name: str, required: bool, data: Dict[str, Any] + name: str, required: bool, data: oai.Schema ) -> Union[StringProperty, DateProperty, DateTimeProperty, FileProperty]: """ Construct a Property from the type "string" """ - string_format = data.get("format") + string_format = data.schema_format if string_format == "date-time": - return DateTimeProperty(name=name, required=required, default=data.get("default")) + return DateTimeProperty(name=name, required=required, default=data.default) elif string_format == "date": - return DateProperty(name=name, required=required, default=data.get("default")) + return DateProperty(name=name, required=required, default=data.default) elif string_format == "binary": - return FileProperty(name=name, required=required, default=data.get("default")) + return FileProperty(name=name, required=required, default=data.default) else: - return StringProperty(name=name, default=data.get("default"), required=required, pattern=data.get("pattern")) + return StringProperty(name=name, default=data.default, required=required, pattern=data.pattern) -def property_from_dict(name: str, required: bool, data: Dict[str, Any]) -> Property: +def property_from_data(name: str, required: bool, data: Union[oai.Reference, oai.Schema]) -> Property: """ Generate a Property from the OpenAPI dictionary representation of it """ - if "enum" in data: + if isinstance(data, oai.Reference): + return RefProperty(name=name, required=required, reference=Reference.from_ref(data.ref), default=None) + if data.enum: return EnumProperty( name=name, required=required, - values=EnumProperty.values_from_list(data["enum"]), - title=data.get("title", name), - default=data.get("default"), + values=EnumProperty.values_from_list(data.enum), + title=data.title or name, + default=data.default, ) - if "$ref" in data: - return RefProperty(name=name, required=required, reference=Reference.from_ref(data["$ref"]), default=None) - if "anyOf" in data: + if data.anyOf: sub_properties: List[Property] = [] - for sub_prop_data in data["anyOf"]: - sub_properties.append(property_from_dict(name=name, required=required, data=sub_prop_data)) - return UnionProperty(name=name, required=required, default=data.get("default"), inner_properties=sub_properties) - if "type" not in data: + for sub_prop_data in data.anyOf: + sub_properties.append(property_from_data(name=name, required=required, data=sub_prop_data)) + return UnionProperty(name=name, required=required, default=data.default, inner_properties=sub_properties) + if not data.type: raise ParseError(data) - if data["type"] == "string": + if data.type == "string": return _string_based_property(name=name, required=required, data=data) - elif data["type"] == "number": - return FloatProperty(name=name, default=data.get("default"), required=required) - elif data["type"] == "integer": - return IntProperty(name=name, default=data.get("default"), required=required) - elif data["type"] == "boolean": - return BooleanProperty(name=name, required=required, default=data.get("default")) - elif data["type"] == "array": + elif data.type == "number": + return FloatProperty(name=name, default=data.default, required=required) + elif data.type == "integer": + return IntProperty(name=name, default=data.default, required=required) + elif data.type == "boolean": + return BooleanProperty(name=name, required=required, default=data.default) + elif data.type == "array" and data.items: return ListProperty( name=name, required=required, - default=data.get("default"), - inner_property=property_from_dict(name=f"{name}_item", required=True, data=data["items"]), + default=data.default, + inner_property=property_from_data(name=f"{name}_item", required=True, data=data.items), ) - elif data["type"] == "object": - return DictProperty(name=name, required=required, default=data.get("default")) + elif data.type == "object": + return DictProperty(name=name, required=required, default=data.default) raise ParseError(data) diff --git a/openapi_python_client/openapi_parser/responses.py b/openapi_python_client/openapi_parser/responses.py index f9f373ea3..89359af46 100644 --- a/openapi_python_client/openapi_parser/responses.py +++ b/openapi_python_client/openapi_parser/responses.py @@ -1,5 +1,7 @@ from dataclasses import InitVar, dataclass, field -from typing import Any, Dict +from typing import Union + +import openapi_schema_pydantic as oai from .errors import ParseError from .reference import Reference @@ -77,28 +79,29 @@ def constructor(self) -> str: } -def response_from_dict(*, status_code: int, data: Dict[str, Any]) -> Response: +def response_from_data(*, status_code: int, data: Union[oai.Response, oai.Reference]) -> Response: """ Generate a Response from the OpenAPI dictionary representation of it """ - if "content" not in data: + + if isinstance(data, oai.Reference) or data.content is None: return Response(status_code=status_code) - content = data["content"] + content = data.content + schema_data = None if "application/json" in content: - content_type = "application/json" + schema_data = data.content["application/json"].media_type_schema elif "text/html" in content: - content_type = "text/html" - else: - raise ParseError(data, message=f"Unsupported content_type {content}") + schema_data = data.content["text/html"].media_type_schema - schema_data = data["content"][content_type]["schema"] + if schema_data is None: + raise ParseError(data, message=f"Unsupported content_type {content}") - if "$ref" in schema_data: - return RefResponse(status_code=status_code, reference=Reference.from_ref(schema_data["$ref"]),) - response_type = schema_data.get("type") + if isinstance(schema_data, oai.Reference): + return RefResponse(status_code=status_code, reference=Reference.from_ref(schema_data.ref),) + response_type = schema_data.type if response_type is None: return Response(status_code=status_code) - if response_type == "array": - return ListRefResponse(status_code=status_code, reference=Reference.from_ref(schema_data["items"]["$ref"]),) + if response_type == "array" and isinstance(schema_data.items, oai.Reference): + return ListRefResponse(status_code=status_code, reference=Reference.from_ref(schema_data.items.ref),) if response_type in openapi_types_to_python_type_strings: return BasicResponse(status_code=status_code, openapi_type=response_type) - raise ParseError(data, message=f"Unrecognized type {schema_data['type']}") + raise ParseError(data, message=f"Unrecognized type {schema_data.type}") diff --git a/poetry.lock b/poetry.lock index b571c0d7c..073fa4ed3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -316,6 +316,21 @@ optional = false python-versions = "*" version = "0.4.3" +[[package]] +category = "main" +description = "OpenAPI (v3) specification schema as pydantic class" +name = "openapi-schema-pydantic" +optional = false +python-versions = ">=3.7" +version = "1.1.0" + +[package.dependencies] +pydantic = ">=1.5.1" + +[package.source] +reference = "8479a267cf7699bd08196cc4d8724475e627277b" +type = "git" +url = "https://github.com/dbanty/openapi-schema-pydantic" [[package]] category = "dev" description = "Core utilities for Python packages" @@ -369,7 +384,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.6.0" [[package]] -category = "dev" +category = "main" description = "Data validation and settings management using python 3.6 type hinting" name = "pydantic" optional = false @@ -653,7 +668,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "3cde9f5cefa40a9df6be41884093442ad82887de44484c7c114cbf58b237fc88" +content-hash = "aaa20a2a0a9837258d711708e706cae9fec85bc5887d884f7bf8d344f73b651d" lock-version = "1.0" python-versions = "^3.7" @@ -845,6 +860,7 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +openapi-schema-pydantic = [] packaging = [ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, diff --git a/pyproject.toml b/pyproject.toml index df4c0a25e..9087bda6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ black = "^19.10b0" isort = "^5.0.5" pyyaml = "^5.3.1" importlib_metadata = {version = "^1.6.0", python = "==3.7"} +openapi-schema-pydantic = {git = "https://github.com/dbanty/openapi-schema-pydantic"} [tool.poetry.scripts] openapi-python-client = "openapi_python_client.cli:app" diff --git a/tests/test___init__.py b/tests/test___init__.py index e2c2422f6..c3d191803 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -8,7 +8,7 @@ def test__get_project_for_url_or_path(mocker): data_dict = mocker.MagicMock() _get_json = mocker.patch("openapi_python_client._get_json", return_value=data_dict) openapi = mocker.MagicMock() - from_dict = mocker.patch("openapi_python_client.openapi_parser.OpenAPI.from_dict", return_value=openapi) + from_dict = mocker.patch("openapi_python_client.openapi_parser.GeneratorData.from_dict", return_value=openapi) _Project = mocker.patch("openapi_python_client._Project") url = mocker.MagicMock() path = mocker.MagicMock() @@ -257,9 +257,9 @@ def test__build_metadata(self, mocker): git_ignore_path.write_text.assert_called_once_with(git_ignore_template.render()) def test__build_models(self, mocker): - from openapi_python_client import OpenAPI, _Project + from openapi_python_client import GeneratorData, _Project - openapi = mocker.MagicMock(autospec=OpenAPI, title="My Test API") + openapi = mocker.MagicMock(autospec=GeneratorData, title="My Test API") schema_1 = mocker.MagicMock() schema_2 = mocker.MagicMock() openapi.schemas = {"1": schema_1, "2": schema_2} @@ -348,9 +348,9 @@ def test__build_api(self, mocker): from jinja2 import Template - from openapi_python_client import OpenAPI, _Project + from openapi_python_client import GeneratorData, _Project - openapi = mocker.MagicMock(autospec=OpenAPI, title="My Test API") + openapi = mocker.MagicMock(autospec=GeneratorData, title="My Test API") tag_1 = mocker.MagicMock(autospec=str) tag_2 = mocker.MagicMock(autospec=str) collection_1 = mocker.MagicMock() @@ -439,10 +439,10 @@ def test__build_api(self, mocker): def test__reformat(mocker): import subprocess - from openapi_python_client import OpenAPI, _Project + from openapi_python_client import GeneratorData, _Project sub_run = mocker.patch("subprocess.run") - openapi = mocker.MagicMock(autospec=OpenAPI, title="My Test API") + openapi = mocker.MagicMock(autospec=GeneratorData, title="My Test API") project = _Project(openapi=openapi) project.project_dir = mocker.MagicMock(autospec=pathlib.Path) @@ -459,11 +459,11 @@ def test__reformat(mocker): def test__raise_errors(mocker): - from openapi_python_client import MultipleParseError, OpenAPI, _Project + from openapi_python_client import GeneratorData, MultipleParseError, _Project from openapi_python_client.openapi_parser.openapi import EndpointCollection openapi = mocker.MagicMock( - autospec=OpenAPI, + autospec=GeneratorData, title="My Test API", endpoint_collections_by_tag={ "default": mocker.MagicMock(autospec=EndpointCollection, parse_errors=[1]), diff --git a/tests/test_openapi_parser/test_openapi.py b/tests/test_openapi_parser/test_openapi.py index 38371f9e0..2745eb2ae 100644 --- a/tests/test_openapi_parser/test_openapi.py +++ b/tests/test_openapi_parser/test_openapi.py @@ -1,87 +1,95 @@ +import openapi_schema_pydantic as oai import pytest +from openapi_python_client.openapi_parser.errors import ParseError + MODULE_NAME = "openapi_python_client.openapi_parser.openapi" -class TestOpenAPI: +class TestGeneratorData: def test_from_dict(self, mocker): Schema = mocker.patch(f"{MODULE_NAME}.Schema") - schemas = mocker.MagicMock() - Schema.dict.return_value = schemas EndpointCollection = mocker.patch(f"{MODULE_NAME}.EndpointCollection") - endpoint_collections_by_tag = mocker.MagicMock() - EndpointCollection.from_dict.return_value = endpoint_collections_by_tag - in_dict = { - "components": {"schemas": mocker.MagicMock()}, - "paths": mocker.MagicMock(), - "info": {"title": mocker.MagicMock(), "description": mocker.MagicMock(), "version": mocker.MagicMock()}, - } + OpenAPI = mocker.patch(f"{MODULE_NAME}.oai.OpenAPI") + openapi = OpenAPI.parse_obj.return_value + + in_dict = mocker.MagicMock() get_all_enums = mocker.patch(f"{MODULE_NAME}.EnumProperty.get_all_enums") - from openapi_python_client.openapi_parser.openapi import OpenAPI + from openapi_python_client.openapi_parser.openapi import GeneratorData - openapi = OpenAPI.from_dict(in_dict) + generator_data = GeneratorData.from_dict(in_dict) - Schema.dict.assert_called_once_with(in_dict["components"]["schemas"]) - EndpointCollection.from_dict.assert_called_once_with(in_dict["paths"]) + OpenAPI.parse_obj.assert_called_once_with(in_dict) + Schema.build.assert_called_once_with(schemas=openapi.components.schemas) + EndpointCollection.from_data.assert_called_once_with(data=openapi.paths) get_all_enums.assert_called_once_with() - assert openapi == OpenAPI( - title=in_dict["info"]["title"], - description=in_dict["info"]["description"], - version=in_dict["info"]["version"], - endpoint_collections_by_tag=endpoint_collections_by_tag, - schemas=schemas, + assert generator_data == GeneratorData( + title=openapi.info.title, + description=openapi.info.description, + version=openapi.info.version, + endpoint_collections_by_tag=EndpointCollection.from_data.return_value, + schemas=Schema.build.return_value, enums=get_all_enums.return_value, ) + # Test no components + openapi.components = None + Schema.build.reset_mock() + + generator_data = GeneratorData.from_dict(in_dict) + + Schema.build.assert_not_called() + assert generator_data.schemas == {} + class TestSchema: - def test_dict(self, mocker): - from_dict = mocker.patch(f"{MODULE_NAME}.Schema.from_dict") + def test_build(self, mocker): + from_data = mocker.patch(f"{MODULE_NAME}.Schema.from_data") in_data = {1: mocker.MagicMock(), 2: mocker.MagicMock()} schema_1 = mocker.MagicMock() schema_2 = mocker.MagicMock() - from_dict.side_effect = [schema_1, schema_2] + from_data.side_effect = [schema_1, schema_2] from openapi_python_client.openapi_parser.openapi import Schema - result = Schema.dict(in_data) + result = Schema.build(schemas=in_data) - from_dict.assert_has_calls([mocker.call(value, name=name) for (name, value) in in_data.items()]) + from_data.assert_has_calls([mocker.call(data=value, name=name) for (name, value) in in_data.items()]) assert result == { schema_1.reference.class_name: schema_1, schema_2.reference.class_name: schema_2, } - def test_from_dict(self, mocker): + def test_from_data(self, mocker): from openapi_python_client.openapi_parser.properties import Property - in_data = { - "title": mocker.MagicMock(), - "description": mocker.MagicMock(), - "required": ["RequiredEnum"], - "properties": {"RequiredEnum": mocker.MagicMock(), "OptionalDateTime": mocker.MagicMock(),}, - } + in_data = oai.Schema.construct( + title=mocker.MagicMock(), + description=mocker.MagicMock(), + required=["RequiredEnum"], + properties={"RequiredEnum": mocker.MagicMock(), "OptionalDateTime": mocker.MagicMock(),}, + ) required_property = mocker.MagicMock(autospec=Property) required_imports = mocker.MagicMock() required_property.get_imports.return_value = {required_imports} optional_property = mocker.MagicMock(autospec=Property) optional_imports = mocker.MagicMock() optional_property.get_imports.return_value = {optional_imports} - property_from_dict = mocker.patch( - f"{MODULE_NAME}.property_from_dict", side_effect=[required_property, optional_property], + property_from_data = mocker.patch( + f"{MODULE_NAME}.property_from_data", side_effect=[required_property, optional_property], ) from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") from openapi_python_client.openapi_parser.openapi import Schema - result = Schema.from_dict(in_data, name=mocker.MagicMock()) + result = Schema.from_data(data=in_data, name=mocker.MagicMock()) - from_ref.assert_called_once_with(in_data["title"]) - property_from_dict.assert_has_calls( + from_ref.assert_called_once_with(in_data.title) + property_from_data.assert_has_calls( [ - mocker.call(name="RequiredEnum", required=True, data=in_data["properties"]["RequiredEnum"]), - mocker.call(name="OptionalDateTime", required=False, data=in_data["properties"]["OptionalDateTime"]), + mocker.call(name="RequiredEnum", required=True, data=in_data.properties["RequiredEnum"]), + mocker.call(name="OptionalDateTime", required=False, data=in_data.properties["OptionalDateTime"]), ] ) required_property.get_imports.assert_called_once_with(prefix="") @@ -91,14 +99,26 @@ def test_from_dict(self, mocker): required_properties=[required_property], optional_properties=[optional_property], relative_imports={required_imports, optional_imports,}, - description=in_data["description"], + description=in_data.description, ) + def test_from_data_parse_error_on_reference(self): + from openapi_python_client.openapi_parser.openapi import Schema + + with pytest.raises(ParseError): + Schema.from_data(data=oai.Reference.construct(), name="") + class TestEndpoint: def test_parse_request_form_body(self, mocker): ref = mocker.MagicMock() - body = {"content": {"application/x-www-form-urlencoded": {"schema": {"$ref": ref}}}} + body = oai.RequestBody.construct( + content={ + "application/x-www-form-urlencoded": oai.MediaType.construct( + media_type_schema=oai.Reference.construct(ref=ref) + ) + } + ) from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") from openapi_python_client.openapi_parser.openapi import Endpoint @@ -109,7 +129,7 @@ def test_parse_request_form_body(self, mocker): assert result == from_ref() def test_parse_request_form_body_no_data(self): - body = {"content": {}} + body = oai.RequestBody.construct(content={}) from openapi_python_client.openapi_parser.openapi import Endpoint @@ -119,7 +139,9 @@ def test_parse_request_form_body_no_data(self): def test_parse_multipart_body(self, mocker): ref = mocker.MagicMock() - body = {"content": {"multipart/form-data": {"schema": {"$ref": ref}}}} + body = oai.RequestBody.construct( + content={"multipart/form-data": oai.MediaType.construct(media_type_schema=oai.Reference.construct(ref=ref))} + ) from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") from openapi_python_client.openapi_parser.openapi import Endpoint @@ -130,7 +152,7 @@ def test_parse_multipart_body(self, mocker): assert result == from_ref() def test_parse_multipart_body_no_data(self): - body = {"content": {}} + body = oai.RequestBody.construct(content={}) from openapi_python_client.openapi_parser.openapi import Endpoint @@ -140,18 +162,20 @@ def test_parse_multipart_body_no_data(self): def test_parse_request_json_body(self, mocker): schema = mocker.MagicMock() - body = {"content": {"application/json": {"schema": schema}}} - property_from_dict = mocker.patch(f"{MODULE_NAME}.property_from_dict") + body = oai.RequestBody.construct( + content={"application/json": oai.MediaType.construct(media_type_schema=schema)} + ) + property_from_data = mocker.patch(f"{MODULE_NAME}.property_from_data") from openapi_python_client.openapi_parser.openapi import Endpoint result = Endpoint.parse_request_json_body(body) - property_from_dict.assert_called_once_with("json_body", required=True, data=schema) - assert result == property_from_dict() + property_from_data.assert_called_once_with("json_body", required=True, data=schema) + assert result == property_from_data() def test_parse_request_json_body_no_data(self): - body = {"content": {}} + body = oai.RequestBody.construct(content={}) from openapi_python_client.openapi_parser.openapi import Endpoint @@ -173,7 +197,7 @@ def test_add_body_no_data(self, mocker): relative_imports={"import_3"}, ) - endpoint._add_body({}) + endpoint._add_body(oai.Operation.construct()) parse_request_form_body.assert_not_called() @@ -209,7 +233,7 @@ def test_add_body_happy(self, mocker): relative_imports={"import_3"}, ) - endpoint._add_body({"requestBody": request_body}) + endpoint._add_body(oai.Operation.construct(requestBody=request_body)) parse_request_form_body.assert_called_once_with(request_body) parse_request_json_body.assert_called_once_with(request_body) @@ -231,7 +255,10 @@ def test__add_responses(self, mocker): response_1_data = mocker.MagicMock() response_2_data = mocker.MagicMock() - data = {"responses": {"200": response_1_data, "404": response_2_data,}} + data = { + "200": response_1_data, + "404": response_2_data, + } endpoint = Endpoint( path="path", method="method", @@ -245,14 +272,14 @@ def test__add_responses(self, mocker): ref_2 = Reference.from_ref(ref="ref_2") response_1 = RefResponse(status_code=200, reference=ref_1) response_2 = RefResponse(status_code=404, reference=ref_2) - response_from_dict = mocker.patch(f"{MODULE_NAME}.response_from_dict", side_effect=[response_1, response_2]) + response_from_data = mocker.patch(f"{MODULE_NAME}.response_from_data", side_effect=[response_1, response_2]) import_string_from_reference = mocker.patch( f"{MODULE_NAME}.import_string_from_reference", side_effect=["import_1", "import_2"] ) endpoint._add_responses(data) - response_from_dict.assert_has_calls( + response_from_data.assert_has_calls( [mocker.call(status_code=200, data=response_1_data), mocker.call(status_code=404, data=response_2_data),] ) import_string_from_reference.assert_has_calls( @@ -267,7 +294,7 @@ def test__add_parameters_handles_no_params(self): endpoint = Endpoint( path="path", method="method", description=None, name="name", requires_security=False, tag="tag", ) - endpoint._add_parameters({}) # Just checking there's no exception here + endpoint._add_parameters(oai.Operation.construct()) # Just checking there's no exception here def test__add_parameters_fail_loudly_when_location_not_supported(self, mocker): from openapi_python_client.openapi_parser.openapi import Endpoint @@ -275,11 +302,17 @@ def test__add_parameters_fail_loudly_when_location_not_supported(self, mocker): endpoint = Endpoint( path="path", method="method", description=None, name="name", requires_security=False, tag="tag", ) - mocker.patch(f"{MODULE_NAME}.property_from_dict") + mocker.patch(f"{MODULE_NAME}.property_from_data") with pytest.raises(ValueError): endpoint._add_parameters( - {"parameters": [{"name": "test", "required": True, "schema": mocker.MagicMock(), "in": "cookie"}]} + oai.Operation.construct( + parameters=[ + oai.Parameter.construct( + name="test", required=True, param_schema=mocker.MagicMock(), param_in="cookie" + ) + ] + ) ) def test__add_parameters_happy(self, mocker): @@ -301,19 +334,25 @@ def test__add_parameters_happy(self, mocker): query_prop = mocker.MagicMock(autospec=Property) query_prop_import = mocker.MagicMock() query_prop.get_imports = mocker.MagicMock(return_value={query_prop_import}) - property_from_dict = mocker.patch(f"{MODULE_NAME}.property_from_dict", side_effect=[path_prop, query_prop]) + property_from_data = mocker.patch(f"{MODULE_NAME}.property_from_data", side_effect=[path_prop, query_prop]) path_schema = mocker.MagicMock() query_schema = mocker.MagicMock() - data = { - "parameters": [ - {"name": "path_prop_name", "required": True, "schema": path_schema, "in": "path"}, - {"name": "query_prop_name", "required": False, "schema": query_schema, "in": "query"}, + data = oai.Operation.construct( + parameters=[ + oai.Parameter.construct( + name="path_prop_name", required=True, param_schema=path_schema, param_in="path" + ), + oai.Parameter.construct( + name="query_prop_name", required=False, param_schema=query_schema, param_in="query" + ), + oai.Reference.construct(), # Should be ignored + oai.Parameter.construct(), # Should be ignored ] - } + ) endpoint._add_parameters(data) - property_from_dict.assert_has_calls( + property_from_data.assert_has_calls( [ mocker.call(name="path_prop_name", required=True, data=path_schema), mocker.call(name="query_prop_name", required=False, data=query_schema), @@ -337,31 +376,36 @@ def test_from_data(self, mocker): _add_parameters = mocker.patch.object(Endpoint, "_add_parameters") _add_responses = mocker.patch.object(Endpoint, "_add_responses") _add_body = mocker.patch.object(Endpoint, "_add_body") - data = { - "description": mocker.MagicMock(), - "operationId": mocker.MagicMock(), - "security": {"blah": "bloo"}, - } + data = oai.Operation.construct( + description=mocker.MagicMock(), + operationId=mocker.MagicMock(), + security={"blah": "bloo"}, + responses=mocker.MagicMock(), + ) endpoint = Endpoint.from_data(data=data, path=path, method=method, tag="default") assert endpoint.path == path assert endpoint.method == method - assert endpoint.description == data["description"] - assert endpoint.name == data["operationId"] + assert endpoint.description == data.description + assert endpoint.name == data.operationId assert endpoint.requires_security assert endpoint.tag == "default" _add_parameters.assert_called_once_with(data) - _add_responses.assert_called_once_with(data) + _add_responses.assert_called_once_with(data.responses) _add_body.assert_called_once_with(data) - del data["security"] + data.security = None endpoint = Endpoint.from_data(data=data, path=path, method=method, tag="a") assert not endpoint.requires_security assert endpoint.tag == "a" + data.operationId = None + with pytest.raises(ParseError): + Endpoint.from_data(data=data, path=path, method=method, tag="a") + class TestImportStringFromReference: def test_import_string_from_reference_no_prefix(self, mocker): @@ -385,15 +429,15 @@ def test_import_string_from_reference_with_prefix(self, mocker): class TestEndpointCollection: - def test_from_dict(self, mocker): + def test_from_data(self, mocker): from openapi_python_client.openapi_parser.openapi import Endpoint, EndpointCollection - data_1 = {} - data_2 = {"tags": ["tag_2", "tag_3"]} - data_3 = {} + path_1_put = oai.Operation.construct() + path_1_post = oai.Operation.construct(tags=["tag_2", "tag_3"]) + path_2_get = oai.Operation.construct() data = { - "path_1": {"method_1": data_1, "method_2": data_2}, - "path_2": {"method_1": data_3}, + "path_1": oai.PathItem.construct(post=path_1_post, put=path_1_put), + "path_2": oai.PathItem.construct(get=path_2_get), } endpoint_1 = mocker.MagicMock(autospec=Endpoint, tag="default", relative_imports={"1", "2"}) endpoint_2 = mocker.MagicMock(autospec=Endpoint, tag="tag_2", relative_imports={"2"}) @@ -402,14 +446,14 @@ def test_from_dict(self, mocker): Endpoint, "from_data", side_effect=[endpoint_1, endpoint_2, endpoint_3] ) - result = EndpointCollection.from_dict(data) + result = EndpointCollection.from_data(data=data) endpoint_from_data.assert_has_calls( [ - mocker.call(data=data_1, path="path_1", method="method_1", tag="default"), - mocker.call(data=data_2, path="path_1", method="method_2", tag="tag_2"), - mocker.call(data=data_3, path="path_2", method="method_1", tag="default"), - ] + mocker.call(data=path_1_put, path="path_1", method="put", tag="default"), + mocker.call(data=path_1_post, path="path_1", method="post", tag="tag_2"), + mocker.call(data=path_2_get, path="path_2", method="get", tag="default"), + ], ) assert result == { "default": EndpointCollection( @@ -418,28 +462,28 @@ def test_from_dict(self, mocker): "tag_2": EndpointCollection("tag_2", endpoints=[endpoint_2], relative_imports={"2"}), } - def test_from_dict_errors(self, mocker): + def test_from_data_errors(self, mocker): from openapi_python_client.openapi_parser.openapi import Endpoint, EndpointCollection, ParseError - data_1 = {} - data_2 = {"tags": ["tag_2", "tag_3"]} - data_3 = {} + path_1_put = oai.Operation.construct() + path_1_post = oai.Operation.construct(tags=["tag_2", "tag_3"]) + path_2_get = oai.Operation.construct() data = { - "path_1": {"method_1": data_1, "method_2": data_2}, - "path_2": {"method_1": data_3}, + "path_1": oai.PathItem.construct(post=path_1_post, put=path_1_put), + "path_2": oai.PathItem.construct(get=path_2_get), } endpoint_from_data = mocker.patch.object( Endpoint, "from_data", side_effect=[ParseError("1"), ParseError("2"), ParseError("3")] ) - result = EndpointCollection.from_dict(data) + result = EndpointCollection.from_data(data=data) endpoint_from_data.assert_has_calls( [ - mocker.call(data=data_1, path="path_1", method="method_1", tag="default"), - mocker.call(data=data_2, path="path_1", method="method_2", tag="tag_2"), - mocker.call(data=data_3, path="path_2", method="method_1", tag="default"), - ] + mocker.call(data=path_1_put, path="path_1", method="put", tag="default"), + mocker.call(data=path_1_post, path="path_1", method="post", tag="tag_2"), + mocker.call(data=path_2_get, path="path_2", method="get", tag="default"), + ], ) assert result["default"].parse_errors[0].data == "1" assert result["default"].parse_errors[1].data == "3" diff --git a/tests/test_openapi_parser/test_properties.py b/tests/test_openapi_parser/test_properties.py index 0f8367a45..30c052c7d 100644 --- a/tests/test_openapi_parser/test_properties.py +++ b/tests/test_openapi_parser/test_properties.py @@ -1,3 +1,4 @@ +import openapi_schema_pydantic as oai import pytest MODULE_NAME = "openapi_python_client.openapi_parser.properties" @@ -380,64 +381,56 @@ def test_get_imports(self, mocker): } -class TestPropertyFromDict: - def test_property_from_dict_enum(self, mocker): +class TestPropertyFromData: + def test_property_from_data_enum(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "enum": mocker.MagicMock(), - } + data = mocker.MagicMock(title=None) EnumProperty = mocker.patch(f"{MODULE_NAME}.EnumProperty") - from openapi_python_client.openapi_parser.properties import property_from_dict + from openapi_python_client.openapi_parser.properties import property_from_data - p = property_from_dict(name=name, required=required, data=data) + p = property_from_data(name=name, required=required, data=data) - EnumProperty.values_from_list.assert_called_once_with(data["enum"]) + EnumProperty.values_from_list.assert_called_once_with(data.enum) EnumProperty.assert_called_once_with( - name=name, required=required, values=EnumProperty.values_from_list(), default=None, title=name + name=name, required=required, values=EnumProperty.values_from_list(), default=data.default, title=name ) assert p == EnumProperty() EnumProperty.reset_mock() - title = mocker.MagicMock() - data["default"] = mocker.MagicMock() - data["title"] = title + data.title = mocker.MagicMock() - property_from_dict( + property_from_data( name=name, required=required, data=data, ) EnumProperty.assert_called_once_with( - name=name, required=required, values=EnumProperty.values_from_list(), default=data["default"], title=title + name=name, required=required, values=EnumProperty.values_from_list(), default=data.default, title=data.title ) - def test_property_from_dict_ref(self, mocker): + def test_property_from_data_ref(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "$ref": mocker.MagicMock(), - } + data = oai.Reference.construct(ref=mocker.MagicMock()) from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") RefProperty = mocker.patch(f"{MODULE_NAME}.RefProperty") - from openapi_python_client.openapi_parser.properties import property_from_dict + from openapi_python_client.openapi_parser.properties import property_from_data - p = property_from_dict(name=name, required=required, data=data) + p = property_from_data(name=name, required=required, data=data) - from_ref.assert_called_once_with(data["$ref"]) + from_ref.assert_called_once_with(data.ref) RefProperty.assert_called_once_with(name=name, required=required, reference=from_ref(), default=None) assert p == RefProperty() - def test_property_from_dict_string(self, mocker): + def test_property_from_data_string(self, mocker): _string_based_property = mocker.patch(f"{MODULE_NAME}._string_based_property") name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "type": "string", - } - from openapi_python_client.openapi_parser.properties import property_from_dict + data = oai.Schema.construct(type="string") + from openapi_python_client.openapi_parser.properties import property_from_data - p = property_from_dict(name=name, required=required, data=data) + p = property_from_data(name=name, required=required, data=data) assert p == _string_based_property.return_value _string_based_property.assert_called_once_with(name=name, required=required, data=data) @@ -451,43 +444,38 @@ def test_property_from_dict_string(self, mocker): ("object", "DictProperty"), ], ) - def test_property_from_dict_simple_types(self, mocker, openapi_type, python_type): + def test_property_from_data_simple_types(self, mocker, openapi_type, python_type): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "type": openapi_type, - } + data = oai.Schema.construct(type=openapi_type) clazz = mocker.patch(f"{MODULE_NAME}.{python_type}") - from openapi_python_client.openapi_parser.properties import property_from_dict + from openapi_python_client.openapi_parser.properties import property_from_data - p = property_from_dict(name=name, required=required, data=data) + p = property_from_data(name=name, required=required, data=data) clazz.assert_called_once_with(name=name, required=required, default=None) assert p == clazz() # Test optional values clazz.reset_mock() - data["default"] = mocker.MagicMock() + data.default = mocker.MagicMock() - property_from_dict( + property_from_data( name=name, required=required, data=data, ) - clazz.assert_called_once_with(name=name, required=required, default=data["default"]) + clazz.assert_called_once_with(name=name, required=required, default=data.default) - def test_property_from_dict_array(self, mocker): + def test_property_from_data_array(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "type": "array", - "items": {"type": "number", "default": "0.0"}, - } + data = oai.Schema(type="array", items={"type": "number", "default": "0.0"},) ListProperty = mocker.patch(f"{MODULE_NAME}.ListProperty") FloatProperty = mocker.patch(f"{MODULE_NAME}.FloatProperty") - from openapi_python_client.openapi_parser.properties import property_from_dict + from openapi_python_client.openapi_parser.properties import property_from_data - p = property_from_dict(name=name, required=required, data=data) + p = property_from_data(name=name, required=required, data=data) FloatProperty.assert_called_once_with(name=f"{name}_item", required=True, default="0.0") ListProperty.assert_called_once_with( @@ -495,19 +483,17 @@ def test_property_from_dict_array(self, mocker): ) assert p == ListProperty.return_value - def test_property_from_dict_union(self, mocker): + def test_property_from_data_union(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "anyOf": [{"type": "number", "default": "0.0"}, {"type": "integer", "default": "0"},], - } + data = oai.Schema(anyOf=[{"type": "number", "default": "0.0"}, {"type": "integer", "default": "0"},]) UnionProperty = mocker.patch(f"{MODULE_NAME}.UnionProperty") FloatProperty = mocker.patch(f"{MODULE_NAME}.FloatProperty") IntProperty = mocker.patch(f"{MODULE_NAME}.IntProperty") - from openapi_python_client.openapi_parser.properties import property_from_dict + from openapi_python_client.openapi_parser.properties import property_from_data - p = property_from_dict(name=name, required=required, data=data) + p = property_from_data(name=name, required=required, data=data) FloatProperty.assert_called_once_with(name=name, required=required, default="0.0") IntProperty.assert_called_once_with(name=name, required=required, default="0") @@ -519,34 +505,30 @@ def test_property_from_dict_union(self, mocker): ) assert p == UnionProperty.return_value - def test_property_from_dict_unsupported_type(self, mocker): + def test_property_from_data_unsupported_type(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "type": mocker.MagicMock(), - } + data = oai.Schema.construct(type=mocker.MagicMock()) from openapi_python_client.openapi_parser.errors import ParseError - from openapi_python_client.openapi_parser.properties import property_from_dict + from openapi_python_client.openapi_parser.properties import property_from_data with pytest.raises(ParseError): - property_from_dict(name=name, required=required, data=data) + property_from_data(name=name, required=required, data=data) - def test_property_from_dict_no_valid_props_in_data(self): + def test_property_from_data_no_valid_props_in_data(self): from openapi_python_client.openapi_parser.errors import ParseError - from openapi_python_client.openapi_parser.properties import property_from_dict + from openapi_python_client.openapi_parser.properties import property_from_data with pytest.raises(ParseError): - property_from_dict(name="blah", required=True, data={}) + property_from_data(name="blah", required=True, data=oai.Schema()) class TestStringBasedProperty: def test__string_based_property_no_format(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "type": "string", - } + data = oai.Schema.construct(type="string") StringProperty = mocker.patch(f"{MODULE_NAME}.StringProperty") from openapi_python_client.openapi_parser.properties import _string_based_property @@ -558,23 +540,18 @@ def test__string_based_property_no_format(self, mocker): # Test optional values StringProperty.reset_mock() - data["default"] = mocker.MagicMock() - data["pattern"] = mocker.MagicMock() + data.default = mocker.MagicMock() + data.pattern = mocker.MagicMock() _string_based_property( name=name, required=required, data=data, ) - StringProperty.assert_called_once_with( - name=name, required=required, pattern=data["pattern"], default=data["default"] - ) + StringProperty.assert_called_once_with(name=name, required=required, pattern=data.pattern, default=data.default) def test__string_based_property_datetime_format(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "type": "string", - "format": "date-time", - } + data = oai.Schema.construct(type="string", schema_format="date-time") DateTimeProperty = mocker.patch(f"{MODULE_NAME}.DateTimeProperty") from openapi_python_client.openapi_parser.properties import _string_based_property @@ -586,20 +563,17 @@ def test__string_based_property_datetime_format(self, mocker): # Test optional values DateTimeProperty.reset_mock() - data["default"] = mocker.MagicMock() + data.default = mocker.MagicMock() _string_based_property( name=name, required=required, data=data, ) - DateTimeProperty.assert_called_once_with(name=name, required=required, default=data["default"]) + DateTimeProperty.assert_called_once_with(name=name, required=required, default=data.default) def test__string_based_property_date_format(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "type": "string", - "format": "date", - } + data = oai.Schema.construct(type="string", schema_format="date") DateProperty = mocker.patch(f"{MODULE_NAME}.DateProperty") from openapi_python_client.openapi_parser.properties import _string_based_property @@ -610,20 +584,17 @@ def test__string_based_property_date_format(self, mocker): # Test optional values DateProperty.reset_mock() - data["default"] = mocker.MagicMock() + data.default = mocker.MagicMock() _string_based_property( name=name, required=required, data=data, ) - DateProperty.assert_called_once_with(name=name, required=required, default=data["default"]) + DateProperty.assert_called_once_with(name=name, required=required, default=data.default) def test__string_based_property_binary_format(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "type": "string", - "format": "binary", - } + data = oai.Schema.construct(type="string", schema_format="binary") FileProperty = mocker.patch(f"{MODULE_NAME}.FileProperty") from openapi_python_client.openapi_parser.properties import _string_based_property @@ -634,20 +605,17 @@ def test__string_based_property_binary_format(self, mocker): # Test optional values FileProperty.reset_mock() - data["default"] = mocker.MagicMock() + data.default = mocker.MagicMock() _string_based_property( name=name, required=required, data=data, ) - FileProperty.assert_called_once_with(name=name, required=required, default=data["default"]) + FileProperty.assert_called_once_with(name=name, required=required, default=data.default) def test__string_based_property_unsupported_format(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() - data = { - "type": "string", - "format": mocker.MagicMock(), - } + data = oai.Schema.construct(type="string", schema_format=mocker.MagicMock()) StringProperty = mocker.patch(f"{MODULE_NAME}.StringProperty") from openapi_python_client.openapi_parser.properties import _string_based_property @@ -659,12 +627,10 @@ def test__string_based_property_unsupported_format(self, mocker): # Test optional values StringProperty.reset_mock() - data["default"] = mocker.MagicMock() - data["pattern"] = mocker.MagicMock() + data.default = mocker.MagicMock() + data.pattern = mocker.MagicMock() _string_based_property( name=name, required=required, data=data, ) - StringProperty.assert_called_once_with( - name=name, required=required, pattern=data["pattern"], default=data["default"] - ) + StringProperty.assert_called_once_with(name=name, required=required, pattern=data.pattern, default=data.default) diff --git a/tests/test_openapi_parser/test_responses.py b/tests/test_openapi_parser/test_responses.py index 0dc4f3e44..4e7ddeb14 100644 --- a/tests/test_openapi_parser/test_responses.py +++ b/tests/test_openapi_parser/test_responses.py @@ -1,3 +1,4 @@ +import openapi_schema_pydantic as oai import pytest MODULE_NAME = "openapi_python_client.openapi_parser.responses" @@ -94,76 +95,103 @@ def test_constructor(self): assert r.constructor() == "bool(response.text)" -class TestResponseFromDict: - def test_response_from_dict_no_content(self, mocker): - from openapi_python_client.openapi_parser.responses import response_from_dict +class TestResponseFromData: + def test_response_from_data_no_content(self, mocker): + from openapi_python_client.openapi_parser.responses import response_from_data Response = mocker.patch(f"{MODULE_NAME}.Response") status_code = mocker.MagicMock(autospec=int) - response = response_from_dict(status_code=status_code, data={}) + response = response_from_data(status_code=status_code, data=oai.Response.construct()) Response.assert_called_once_with(status_code=status_code) assert response == Response() - def test_response_from_dict_unsupported_content_type(self): - from openapi_python_client.openapi_parser.responses import response_from_dict + def test_response_from_data_unsupported_content_type(self): + from openapi_python_client.openapi_parser.responses import response_from_data with pytest.raises(ValueError): - response_from_dict(status_code=200, data={"content": {"not/real": {}}}) + response_from_data(status_code=200, data=oai.Response.construct(content={"not/real": {}})) - def test_response_from_dict_ref(self, mocker): + def test_response_from_data_ref(self, mocker): ref = mocker.MagicMock() status_code = mocker.MagicMock(autospec=int) - data = {"content": {"application/json": {"schema": {"$ref": ref}}}} + data = oai.Response.construct( + content={"application/json": oai.MediaType.construct(media_type_schema=oai.Reference.construct(ref=ref))} + ) from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") RefResponse = mocker.patch(f"{MODULE_NAME}.RefResponse") - from openapi_python_client.openapi_parser.responses import response_from_dict + from openapi_python_client.openapi_parser.responses import response_from_data - response = response_from_dict(status_code=status_code, data=data) + response = response_from_data(status_code=status_code, data=data) from_ref.assert_called_once_with(ref) RefResponse.assert_called_once_with(status_code=status_code, reference=from_ref()) assert response == RefResponse() - def test_response_from_dict_empty(self, mocker): + def test_response_from_data_empty(self, mocker): status_code = mocker.MagicMock(autospec=int) - data = {"content": {"application/json": {"schema": {}}}} + data = oai.Response.construct() Response = mocker.patch(f"{MODULE_NAME}.Response") - from openapi_python_client.openapi_parser.responses import response_from_dict + from openapi_python_client.openapi_parser.responses import response_from_data - response = response_from_dict(status_code=status_code, data=data) + response = response_from_data(status_code=status_code, data=data) Response.assert_called_once_with(status_code=status_code) assert response == Response() - def test_response_from_dict_array(self, mocker): + def test_response_from_data_no_response_type(self, mocker): + status_code = mocker.MagicMock(autospec=int) + data = oai.Response.construct( + content={"application/json": oai.MediaType.construct(media_type_schema=oai.Schema.construct(type=None))} + ) + Response = mocker.patch(f"{MODULE_NAME}.Response") + from openapi_python_client.openapi_parser.responses import response_from_data + + response = response_from_data(status_code=status_code, data=data) + + Response.assert_called_once_with(status_code=status_code) + assert response == Response() + + def test_response_from_data_array(self, mocker): ref = mocker.MagicMock() status_code = mocker.MagicMock(autospec=int) - data = {"content": {"application/json": {"schema": {"type": "array", "items": {"$ref": ref}}}}} + data = oai.Response.construct( + content={ + "application/json": oai.MediaType.construct( + media_type_schema=oai.Schema.construct(type="array", items=oai.Reference.construct(ref=ref)) + ) + } + ) from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") ListRefResponse = mocker.patch(f"{MODULE_NAME}.ListRefResponse") - from openapi_python_client.openapi_parser.responses import response_from_dict + from openapi_python_client.openapi_parser.responses import response_from_data - response = response_from_dict(status_code=status_code, data=data) + response = response_from_data(status_code=status_code, data=data) from_ref.assert_called_once_with(ref) ListRefResponse.assert_called_once_with(status_code=status_code, reference=from_ref()) assert response == ListRefResponse() - def test_response_from_dict_basic(self, mocker): + def test_response_from_data_basic(self, mocker): status_code = mocker.MagicMock(autospec=int) - data = {"content": {"text/html": {"schema": {"type": "string"}}}} + data = oai.Response.construct( + content={"text/html": oai.MediaType.construct(media_type_schema=oai.Schema.construct(type="string"))} + ) BasicResponse = mocker.patch(f"{MODULE_NAME}.BasicResponse") - from openapi_python_client.openapi_parser.responses import response_from_dict + from openapi_python_client.openapi_parser.responses import response_from_data - response = response_from_dict(status_code=status_code, data=data) + response = response_from_data(status_code=status_code, data=data) BasicResponse.assert_called_once_with(status_code=status_code, openapi_type="string") assert response == BasicResponse.return_value def test_response_from_dict_unsupported_type(self): - from openapi_python_client.openapi_parser.responses import response_from_dict + from openapi_python_client.openapi_parser.responses import response_from_data + + data = oai.Response.construct( + content={"text/html": oai.MediaType.construct(media_type_schema=oai.Schema.construct(type="BLAH"))} + ) with pytest.raises(ValueError): - response_from_dict(status_code=200, data={"content": {"application/json": {"schema": {"type": "blah"}}}}) + response_from_data(status_code=200, data=data) From 2c31cf90e1d4729cee291f9eefbf300098ac45ed Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Thu, 23 Jul 2020 12:39:00 -0400 Subject: [PATCH 2/2] Rename Schema class to Model to disambiguate with OpenAPI term --- CHANGELOG.md | 2 ++ openapi_python_client/__init__.py | 8 ++--- .../openapi_parser/openapi.py | 24 +++++++-------- openapi_python_client/templates/model.pyi | 20 ++++++------- tests/test___init__.py | 24 +++++++-------- tests/test_openapi_parser/test_openapi.py | 30 +++++++++---------- 6 files changed, 55 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79485d252..1eca76093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + ## 0.5.0 - Unreleased ### Internal Changes - Switched OpenAPI document parsing to use [openapi-schema-pydantic](https://github.com/kuimono/openapi-schema-pydantic/pull/1) (#103) + ## 0.4.2 - 2020-06-13 ### Additions - Support for responses with no content (#63 & #66). Thanks @acgray! diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index eb01fcd74..627c34c7a 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -170,10 +170,10 @@ def _build_models(self) -> None: types_path.write_text(types_template.render()) model_template = self.env.get_template("model.pyi") - for schema in self.openapi.schemas.values(): - module_path = models_dir / f"{schema.reference.module_name}.py" - module_path.write_text(model_template.render(schema=schema)) - imports.append(import_string_from_reference(schema.reference)) + for model in self.openapi.models.values(): + module_path = models_dir / f"{model.reference.module_name}.py" + module_path.write_text(model_template.render(model=model)) + imports.append(import_string_from_reference(model.reference)) # Generate enums enum_template = self.env.get_template("enum.pyi") diff --git a/openapi_python_client/openapi_parser/openapi.py b/openapi_python_client/openapi_parser/openapi.py index e57b05692..4ed1a388a 100644 --- a/openapi_python_client/openapi_parser/openapi.py +++ b/openapi_python_client/openapi_parser/openapi.py @@ -167,9 +167,9 @@ def from_data(*, data: oai.Operation, path: str, method: str, tag: str) -> Endpo @dataclass -class Schema: +class Model: """ - Describes a schema, AKA data model used in requests. + A data model used by the API- usually a Schema with type "object". These will all be converted to dataclasses in the client """ @@ -181,8 +181,8 @@ class Schema: relative_imports: Set[str] @staticmethod - def from_data(*, data: Union[oai.Reference, oai.Schema], name: str) -> Schema: - """ A single Schema from its OAI data + def from_data(*, data: Union[oai.Reference, oai.Schema], name: str) -> Model: + """ A single Model from its OAI data Args: data: Data of a single Schema @@ -207,21 +207,21 @@ def from_data(*, data: Union[oai.Reference, oai.Schema], name: str) -> Schema: optional_properties.append(p) relative_imports.update(p.get_imports(prefix="")) - schema = Schema( + model = Model( reference=ref, required_properties=required_properties, optional_properties=optional_properties, relative_imports=relative_imports, description=data.description or "", ) - return schema + return model @staticmethod - def build(*, schemas: Dict[str, Union[oai.Reference, oai.Schema]]) -> Dict[str, Schema]: + def build(*, schemas: Dict[str, Union[oai.Reference, oai.Schema]]) -> Dict[str, Model]: """ Get a list of Schemas from an OpenAPI dict """ result = {} for name, data in schemas.items(): - s = Schema.from_data(data=data, name=name) + s = Model.from_data(data=data, name=name) result[s.reference.class_name] = s return result @@ -233,7 +233,7 @@ class GeneratorData: title: str description: Optional[str] version: str - schemas: Dict[str, Schema] + models: Dict[str, Model] endpoint_collections_by_tag: Dict[str, EndpointCollection] enums: Dict[str, EnumProperty] @@ -242,9 +242,9 @@ def from_dict(d: Dict[str, Dict[str, Any]]) -> GeneratorData: """ Create an OpenAPI from dict """ openapi = oai.OpenAPI.parse_obj(d) if openapi.components is None or openapi.components.schemas is None: - schemas = {} + models = {} else: - schemas = Schema.build(schemas=openapi.components.schemas) + models = Model.build(schemas=openapi.components.schemas) endpoint_collections_by_tag = EndpointCollection.from_data(data=openapi.paths) enums = EnumProperty.get_all_enums() @@ -253,6 +253,6 @@ def from_dict(d: Dict[str, Dict[str, Any]]) -> GeneratorData: description=openapi.info.description, version=openapi.info.version, endpoint_collections_by_tag=endpoint_collections_by_tag, - schemas=schemas, + models=models, enums=enums, ) diff --git a/openapi_python_client/templates/model.pyi b/openapi_python_client/templates/model.pyi index 9fb1824ca..34da14d96 100644 --- a/openapi_python_client/templates/model.pyi +++ b/openapi_python_client/templates/model.pyi @@ -3,20 +3,20 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any, Dict -{% for relative in schema.relative_imports %} +{% for relative in model.relative_imports %} {{ relative }} {% endfor %} @dataclass -class {{ schema.reference.class_name }}: - """ {{ schema.description }} """ - {% for property in schema.required_properties + schema.optional_properties %} +class {{ model.reference.class_name }}: + """ {{ model.description }} """ + {% for property in model.required_properties + model.optional_properties %} {{ property.to_string() }} {% endfor %} def to_dict(self) -> Dict[str, Any]: - {% for property in schema.required_properties + schema.optional_properties %} + {% for property in model.required_properties + model.optional_properties %} {% if property.template %} {% from "property_templates/" + property.template import transform %} {{ transform(property, "self." + property.python_name, property.python_name) | indent(8) }} @@ -26,14 +26,14 @@ class {{ schema.reference.class_name }}: {% endfor %} return { - {% for property in schema.required_properties + schema.optional_properties %} + {% for property in model.required_properties + model.optional_properties %} "{{ property.name }}": {{ property.python_name }}, {% endfor %} } @staticmethod - def from_dict(d: Dict[str, Any]) -> {{ schema.reference.class_name }}: -{% for property in schema.required_properties + schema.optional_properties %} + def from_dict(d: Dict[str, Any]) -> {{ model.reference.class_name }}: +{% for property in model.required_properties + model.optional_properties %} {% if property.required %} {% set property_source = 'd["' + property.name + '"]' %} {% else %} @@ -47,8 +47,8 @@ class {{ schema.reference.class_name }}: {% endif %} {% endfor %} - return {{ schema.reference.class_name }}( -{% for property in schema.required_properties + schema.optional_properties %} + return {{ model.reference.class_name }}( +{% for property in model.required_properties + model.optional_properties %} {{ property.python_name }}={{ property.python_name }}, {% endfor %} ) diff --git a/tests/test___init__.py b/tests/test___init__.py index c3d191803..5686263d9 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -260,9 +260,9 @@ def test__build_models(self, mocker): from openapi_python_client import GeneratorData, _Project openapi = mocker.MagicMock(autospec=GeneratorData, title="My Test API") - schema_1 = mocker.MagicMock() - schema_2 = mocker.MagicMock() - openapi.schemas = {"1": schema_1, "2": schema_2} + model_1 = mocker.MagicMock() + model_2 = mocker.MagicMock() + openapi.models = {"1": model_1, "2": model_2} enum_1 = mocker.MagicMock() enum_2 = mocker.MagicMock() openapi.enums = {"1": enum_1, "2": enum_2} @@ -271,15 +271,15 @@ def test__build_models(self, mocker): models_init = mocker.MagicMock() types_py = mocker.MagicMock() models_dir = mocker.MagicMock() - schema_1_module_path = mocker.MagicMock() - schema_2_module_path = mocker.MagicMock() + model_1_module_path = mocker.MagicMock() + model_2_module_path = mocker.MagicMock() enum_1_module_path = mocker.MagicMock() enum_2_module_path = mocker.MagicMock() module_paths = { "__init__.py": models_init, "types.py": types_py, - f"{schema_1.reference.module_name}.py": schema_1_module_path, - f"{schema_2.reference.module_name}.py": schema_2_module_path, + f"{model_1.reference.module_name}.py": model_1_module_path, + f"{model_2.reference.module_name}.py": model_2_module_path, f"{enum_1.reference.module_name}.py": enum_1_module_path, f"{enum_2.reference.module_name}.py": enum_2_module_path, } @@ -327,13 +327,13 @@ def models_dir_get(x): models_dir.mkdir.assert_called_once() models_dir.__truediv__.assert_has_calls([mocker.call(key) for key in module_paths]) project.env.get_template.assert_has_calls([mocker.call(key) for key in templates]) - model_template.render.assert_has_calls([mocker.call(schema=schema_1), mocker.call(schema=schema_2)]) - schema_1_module_path.write_text.assert_called_once_with(model_render_1) - schema_2_module_path.write_text.assert_called_once_with(model_render_2) + model_template.render.assert_has_calls([mocker.call(model=model_1), mocker.call(model=model_2)]) + model_1_module_path.write_text.assert_called_once_with(model_render_1) + model_2_module_path.write_text.assert_called_once_with(model_render_2) import_string_from_reference.assert_has_calls( [ - mocker.call(schema_1.reference), - mocker.call(schema_2.reference), + mocker.call(model_1.reference), + mocker.call(model_2.reference), mocker.call(enum_1.reference), mocker.call(enum_2.reference), ] diff --git a/tests/test_openapi_parser/test_openapi.py b/tests/test_openapi_parser/test_openapi.py index 2745eb2ae..c53a3658f 100644 --- a/tests/test_openapi_parser/test_openapi.py +++ b/tests/test_openapi_parser/test_openapi.py @@ -8,7 +8,7 @@ class TestGeneratorData: def test_from_dict(self, mocker): - Schema = mocker.patch(f"{MODULE_NAME}.Schema") + Model = mocker.patch(f"{MODULE_NAME}.Model") EndpointCollection = mocker.patch(f"{MODULE_NAME}.EndpointCollection") OpenAPI = mocker.patch(f"{MODULE_NAME}.oai.OpenAPI") openapi = OpenAPI.parse_obj.return_value @@ -21,7 +21,7 @@ def test_from_dict(self, mocker): generator_data = GeneratorData.from_dict(in_dict) OpenAPI.parse_obj.assert_called_once_with(in_dict) - Schema.build.assert_called_once_with(schemas=openapi.components.schemas) + Model.build.assert_called_once_with(schemas=openapi.components.schemas) EndpointCollection.from_data.assert_called_once_with(data=openapi.paths) get_all_enums.assert_called_once_with() assert generator_data == GeneratorData( @@ -29,31 +29,31 @@ def test_from_dict(self, mocker): description=openapi.info.description, version=openapi.info.version, endpoint_collections_by_tag=EndpointCollection.from_data.return_value, - schemas=Schema.build.return_value, + models=Model.build.return_value, enums=get_all_enums.return_value, ) # Test no components openapi.components = None - Schema.build.reset_mock() + Model.build.reset_mock() generator_data = GeneratorData.from_dict(in_dict) - Schema.build.assert_not_called() - assert generator_data.schemas == {} + Model.build.assert_not_called() + assert generator_data.models == {} -class TestSchema: +class TestModel: def test_build(self, mocker): - from_data = mocker.patch(f"{MODULE_NAME}.Schema.from_data") + from_data = mocker.patch(f"{MODULE_NAME}.Model.from_data") in_data = {1: mocker.MagicMock(), 2: mocker.MagicMock()} schema_1 = mocker.MagicMock() schema_2 = mocker.MagicMock() from_data.side_effect = [schema_1, schema_2] - from openapi_python_client.openapi_parser.openapi import Schema + from openapi_python_client.openapi_parser.openapi import Model - result = Schema.build(schemas=in_data) + result = Model.build(schemas=in_data) from_data.assert_has_calls([mocker.call(data=value, name=name) for (name, value) in in_data.items()]) assert result == { @@ -81,9 +81,9 @@ def test_from_data(self, mocker): ) from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") - from openapi_python_client.openapi_parser.openapi import Schema + from openapi_python_client.openapi_parser.openapi import Model - result = Schema.from_data(data=in_data, name=mocker.MagicMock()) + result = Model.from_data(data=in_data, name=mocker.MagicMock()) from_ref.assert_called_once_with(in_data.title) property_from_data.assert_has_calls( @@ -94,7 +94,7 @@ def test_from_data(self, mocker): ) required_property.get_imports.assert_called_once_with(prefix="") optional_property.get_imports.assert_called_once_with(prefix="") - assert result == Schema( + assert result == Model( reference=from_ref(), required_properties=[required_property], optional_properties=[optional_property], @@ -103,10 +103,10 @@ def test_from_data(self, mocker): ) def test_from_data_parse_error_on_reference(self): - from openapi_python_client.openapi_parser.openapi import Schema + from openapi_python_client.openapi_parser.openapi import Model with pytest.raises(ParseError): - Schema.from_data(data=oai.Reference.construct(), name="") + Model.from_data(data=oai.Reference.construct(), name="") class TestEndpoint: