Use spec objects rather than dictionaries

This commit is contained in:
Correl Roush 2021-02-26 23:03:33 -05:00
parent 7543654e7c
commit 3a622e0306
4 changed files with 140 additions and 70 deletions

View file

@ -1,6 +1,7 @@
import json import json
import unittest.mock import unittest.mock
from openapi_core import create_spec # type: ignore
from openapi_core.exceptions import OpenAPIError # type: ignore from openapi_core.exceptions import OpenAPIError # type: ignore
import tornado.httpclient # type: ignore import tornado.httpclient # type: ignore
import tornado.web # type: ignore import tornado.web # type: ignore
@ -10,56 +11,60 @@ from tornado_openapi3.handler import OpenAPIRequestHandler
class ResourceHandler(OpenAPIRequestHandler): class ResourceHandler(OpenAPIRequestHandler):
spec = { spec = create_spec(
"openapi": "3.0.0", {
"info": { "openapi": "3.0.0",
"title": "Test API", "info": {
"version": "1.0.0", "title": "Test API",
}, "version": "1.0.0",
"components": { },
"schemas": { "components": {
"resource": { "schemas": {
"type": "object", "resource": {
"properties": {"name": {"type": "string"}}, "type": "object",
"required": ["name"], "properties": {"name": {"type": "string"}},
"required": ["name"],
},
},
"securitySchemes": {
"basicAuth": {
"type": "http",
"scheme": "bearer",
}
}, },
}, },
"securitySchemes": { "security": [{"basicAuth": []}],
"basicAuth": { "paths": {
"type": "http", "/resource": {
"scheme": "bearer", "post": {
} "requestBody": {
}, "required": True,
},
"security": [{"basicAuth": []}],
"paths": {
"/resource": {
"post": {
"requestBody": {
"required": True,
"content": {
"application/vnd.example.resource+json": {
"schema": {"$ref": "#/components/schemas/resource"},
}
},
},
"responses": {
"200": {
"description": "Success",
"content": { "content": {
"application/vnd.example.resource+json": { "application/vnd.example.resource+json": {
"schema": {"$ref": "#/components/schemas/resource"}, "schema": {"$ref": "#/components/schemas/resource"},
} }
}, },
}, },
"401": { "responses": {
"description": "Missing or invalid credentials", "200": {
"description": "Success",
"content": {
"application/vnd.example.resource+json": {
"schema": {
"$ref": "#/components/schemas/resource"
},
}
},
},
"401": {
"description": "Missing or invalid credentials",
},
}, },
}, }
} }
} },
}, }
} )
custom_media_type_deserializers = { custom_media_type_deserializers = {
"application/vnd.example.resource+json": json.loads, "application/vnd.example.resource+json": json.loads,
} }
@ -75,6 +80,51 @@ class ResourceHandler(OpenAPIRequestHandler):
) )
class DefaultSchemaTest(tornado.testing.AsyncHTTPTestCase):
def get_app(self) -> tornado.web.Application:
test = self
class RequestHandler(OpenAPIRequestHandler):
async def prepare(self) -> None:
with test.assertRaises(NotImplementedError):
self.spec
async def get(self) -> None:
...
return tornado.web.Application(
[
(r"/", RequestHandler),
]
)
def test_schema_must_be_implemented(self) -> None:
response = self.fetch("/")
self.assertEqual(200, response.code)
class DefaultDeserializers(tornado.testing.AsyncHTTPTestCase):
def get_app(self) -> tornado.web.Application:
test = self
class RequestHandler(OpenAPIRequestHandler):
async def prepare(self) -> None:
test.assertEqual(dict(), self.custom_media_type_deserializers)
async def get(self) -> None:
...
return tornado.web.Application(
[
(r"/", RequestHandler),
]
)
def test_schema_must_be_implemented(self) -> None:
response = self.fetch("/")
self.assertEqual(200, response.code)
class RequestHandlerTests(tornado.testing.AsyncHTTPTestCase): class RequestHandlerTests(tornado.testing.AsyncHTTPTestCase):
def get_app(self) -> tornado.web.Application: def get_app(self) -> tornado.web.Application:
return tornado.web.Application( return tornado.web.Application(

View file

@ -1,5 +1,6 @@
import json import json
from openapi_core import create_spec # type: ignore
from openapi_core.schema.responses.exceptions import InvalidResponse # type: ignore from openapi_core.schema.responses.exceptions import InvalidResponse # type: ignore
import tornado.web # type: ignore import tornado.web # type: ignore
@ -8,29 +9,48 @@ from tornado_openapi3.testing import AsyncOpenAPITestCase
def spec(responses: dict = dict()) -> dict: def spec(responses: dict = dict()) -> dict:
return { if not responses:
"openapi": "3.0.0", responses = {"200": {"description": "Success"}}
"info": { return create_spec(
"title": "Test API", {
"version": "1.0.0", "openapi": "3.0.0",
}, "info": {
"components": { "title": "Test API",
"schemas": { "version": "1.0.0",
"resource": { },
"type": "object", "components": {
"properties": {"name": {"type": "string"}}, "schemas": {
"required": ["name"], "resource": {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
},
}, },
}, },
}, "paths": {
"paths": { "/resource": {
"/resource": { "get": {
"get": { "responses": responses,
"responses": responses, }
} }
} },
}, }
} )
class TestTestCase(AsyncOpenAPITestCase):
def setUp(self) -> None:
...
def tearDown(self) -> None:
...
def test_schema_must_be_implemented(self) -> None:
with self.assertRaises(NotImplementedError):
self.spec
def test_no_custom_media_type_deserializers(self) -> None:
self.assertEqual(dict(), self.custom_media_type_deserializers)
class BaseTestCase(AsyncOpenAPITestCase): class BaseTestCase(AsyncOpenAPITestCase):

View file

@ -1,9 +1,9 @@
import asyncio import asyncio
import logging import logging
from openapi_core import create_spec # type: ignore
from openapi_core.exceptions import OpenAPIError # type: ignore from openapi_core.exceptions import OpenAPIError # type: ignore
from openapi_core.deserializing.exceptions import DeserializeError # type: ignore from openapi_core.deserializing.exceptions import DeserializeError # type: ignore
from openapi_core.schema.specs.models import Spec # type: ignore
from openapi_core.schema.media_types.exceptions import ( # type: ignore from openapi_core.schema.media_types.exceptions import ( # type: ignore
InvalidContentType, InvalidContentType,
) )
@ -22,14 +22,14 @@ logger = logging.getLogger(__name__)
class OpenAPIRequestHandler(tornado.web.RequestHandler): class OpenAPIRequestHandler(tornado.web.RequestHandler):
@property @property
def spec(self) -> dict: def spec(self) -> Spec:
"""The OpenAPI 3 specification as a Python dictionary. """The OpenAPI 3 specification.
Override this in your request handlers to load or define your OpenAPI 3 Override this in your request handlers to load or define your OpenAPI 3
spec. spec.
""" """
return dict() raise NotImplementedError()
@property @property
def custom_media_type_deserializers(self) -> dict: def custom_media_type_deserializers(self) -> dict:
@ -82,7 +82,7 @@ class OpenAPIRequestHandler(tornado.web.RequestHandler):
await maybe_coro await maybe_coro
validator = RequestValidator( validator = RequestValidator(
create_spec(self.spec), self.spec,
custom_media_type_deserializers=self.custom_media_type_deserializers, custom_media_type_deserializers=self.custom_media_type_deserializers,
) )
result = validator.validate(self.request) result = validator.validate(self.request)

View file

@ -3,20 +3,20 @@ from typing import Any
import tornado.httpclient # type: ignore import tornado.httpclient # type: ignore
import tornado.testing # type: ignore import tornado.testing # type: ignore
from openapi_core import create_spec # type: ignore from openapi_core.schema.specs.models import Spec # type: ignore
from tornado_openapi3.responses import ResponseValidator from tornado_openapi3.responses import ResponseValidator
class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase): class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase):
@property @property
def spec(self) -> dict: def spec(self) -> Spec:
"""The OpenAPI 3 specification as a Python dictionary. """The OpenAPI 3 specification.
Override this in your request handlers to load or define your OpenAPI 3 Override this in your request handlers to load or define your OpenAPI 3
spec. spec.
""" """
return dict() raise NotImplementedError()
@property @property
def custom_media_type_deserializers(self) -> dict: def custom_media_type_deserializers(self) -> dict:
@ -38,7 +38,7 @@ class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase):
""" """
super().setUp() super().setUp()
self.validator = ResponseValidator( self.validator = ResponseValidator(
create_spec(self.spec), self.spec,
custom_media_type_deserializers=self.custom_media_type_deserializers, custom_media_type_deserializers=self.custom_media_type_deserializers,
) )