Fix wrong link in OAS components with nested pydantic.BaseModel
This commit is contained in:
parent
f2b16a46b5
commit
25fcac18ec
@ -1,5 +1,5 @@
|
||||
from .view import PydanticView
|
||||
|
||||
__version__ = "1.6.0"
|
||||
__version__ = "1.6.1"
|
||||
|
||||
__all__ = ("PydanticView", "__version__")
|
||||
|
@ -68,7 +68,7 @@ class BodyGetter(AbstractInjector):
|
||||
except JSONDecodeError:
|
||||
raise HTTPBadRequest(
|
||||
text='{"error": "Malformed JSON"}', content_type="application/json"
|
||||
)
|
||||
) from None
|
||||
|
||||
kwargs_view[self.arg_name] = self.model(**body)
|
||||
|
||||
|
@ -293,6 +293,15 @@ class Servers:
|
||||
return Server(spec)
|
||||
|
||||
|
||||
class Components:
|
||||
def __init__(self, spec: dict):
|
||||
self._spec = spec.setdefault("components", {})
|
||||
|
||||
@property
|
||||
def schemas(self) -> dict:
|
||||
return self._spec.setdefault("schemas", {})
|
||||
|
||||
|
||||
class OpenApiSpec3:
|
||||
def __init__(self):
|
||||
self._spec = {"openapi": "3.0.0"}
|
||||
@ -309,6 +318,10 @@ class OpenApiSpec3:
|
||||
def paths(self) -> Paths:
|
||||
return Paths(self._spec)
|
||||
|
||||
@property
|
||||
def components(self) -> Components:
|
||||
return Components(self._spec)
|
||||
|
||||
@property
|
||||
def spec(self):
|
||||
return self._spec
|
||||
|
@ -1,9 +1,7 @@
|
||||
import typing
|
||||
from datetime import date, datetime
|
||||
from inspect import getdoc
|
||||
from itertools import count
|
||||
from typing import List, Type
|
||||
from uuid import UUID
|
||||
|
||||
from aiohttp.web import Response, json_response
|
||||
from aiohttp.web_app import Application
|
||||
@ -16,16 +14,6 @@ from ..utils import is_pydantic_base_model
|
||||
from ..view import PydanticView, is_pydantic_view
|
||||
from .typing import is_status_code_type
|
||||
|
||||
JSON_SCHEMA_TYPES = {
|
||||
float: {"type": "number"},
|
||||
str: {"type": "string"},
|
||||
int: {"type": "integer"},
|
||||
UUID: {"type": "string", "format": "uuid"},
|
||||
bool: {"type": "boolean"},
|
||||
datetime: {"type": "string", "format": "date-time"},
|
||||
date: {"type": "string", "format": "date"},
|
||||
}
|
||||
|
||||
|
||||
def _handle_optional(type_):
|
||||
"""
|
||||
@ -48,13 +36,16 @@ class _OASResponseBuilder:
|
||||
generate the OAS operation response.
|
||||
"""
|
||||
|
||||
def __init__(self, oas_operation):
|
||||
def __init__(self, oas: OpenApiSpec3, oas_operation):
|
||||
self._oas_operation = oas_operation
|
||||
self._oas = oas
|
||||
|
||||
@staticmethod
|
||||
def _handle_pydantic_base_model(obj):
|
||||
def _handle_pydantic_base_model(self, obj):
|
||||
if is_pydantic_base_model(obj):
|
||||
return obj.schema()
|
||||
response_schema = obj.schema(ref_template="#/components/schemas/{model}")
|
||||
if def_sub_schemas := response_schema.get("definitions", None):
|
||||
self._oas.components.schemas.update(def_sub_schemas)
|
||||
return response_schema
|
||||
return {}
|
||||
|
||||
def _handle_list(self, obj):
|
||||
@ -89,7 +80,7 @@ class _OASResponseBuilder:
|
||||
|
||||
|
||||
def _add_http_method_to_oas(
|
||||
oas_path: PathItem, http_method: str, view: Type[PydanticView]
|
||||
oas: OpenApiSpec3, oas_path: PathItem, http_method: str, view: Type[PydanticView]
|
||||
):
|
||||
http_method = http_method.lower()
|
||||
oas_operation: OperationObject = getattr(oas_path, http_method)
|
||||
@ -102,8 +93,14 @@ def _add_http_method_to_oas(
|
||||
oas_operation.description = description
|
||||
|
||||
if body_args:
|
||||
body_schema = next(iter(body_args.values())).schema(
|
||||
ref_template="#/components/schemas/{model}"
|
||||
)
|
||||
if def_sub_schemas := body_schema.get("definitions", None):
|
||||
oas.components.schemas.update(def_sub_schemas)
|
||||
|
||||
oas_operation.request_body.content = {
|
||||
"application/json": {"schema": next(iter(body_args.values())).schema()}
|
||||
"application/json": {"schema": body_schema}
|
||||
}
|
||||
|
||||
indexes = count()
|
||||
@ -122,14 +119,15 @@ def _add_http_method_to_oas(
|
||||
if name in defaults:
|
||||
attrs["__root__"] = defaults[name]
|
||||
|
||||
oas_operation.parameters[i].schema = type(
|
||||
name, (BaseModel,), attrs
|
||||
).schema()
|
||||
oas_operation.parameters[i].schema = type(name, (BaseModel,), attrs).schema(
|
||||
ref_template="#/components/schemas/{model}"
|
||||
)
|
||||
|
||||
oas_operation.parameters[i].required = optional_type is None
|
||||
|
||||
return_type = handler.__annotations__.get("return")
|
||||
if return_type is not None:
|
||||
_OASResponseBuilder(oas_operation).build(return_type)
|
||||
_OASResponseBuilder(oas, oas_operation).build(return_type)
|
||||
|
||||
|
||||
def generate_oas(apps: List[Application]) -> dict:
|
||||
@ -148,9 +146,9 @@ def generate_oas(apps: List[Application]) -> dict:
|
||||
path = oas.paths[info.get("path", info.get("formatter"))]
|
||||
if resource_route.method == "*":
|
||||
for method_name in view.allowed_methods:
|
||||
_add_http_method_to_oas(path, method_name, view)
|
||||
_add_http_method_to_oas(oas, path, method_name, view)
|
||||
else:
|
||||
_add_http_method_to_oas(path, resource_route.method, view)
|
||||
_add_http_method_to_oas(oas, path, resource_route.method, view)
|
||||
|
||||
return oas.spec
|
||||
|
||||
|
@ -1,10 +1,17 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
|
||||
class Friend(BaseModel):
|
||||
name: str
|
||||
age: str
|
||||
|
||||
|
||||
class Pet(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
age: int
|
||||
friends: Friend
|
||||
|
||||
|
||||
class Error(BaseModel):
|
||||
|
@ -31,7 +31,7 @@ packages = find:
|
||||
python_requires = >=3.8
|
||||
install_requires =
|
||||
aiohttp
|
||||
pydantic
|
||||
pydantic>=1.7
|
||||
swagger-ui-bundle
|
||||
|
||||
[options.extras_require]
|
||||
|
@ -1,3 +1,4 @@
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
@ -9,9 +10,21 @@ from aiohttp_pydantic import PydanticView, oas
|
||||
from aiohttp_pydantic.oas.typing import r200, r201, r204, r404
|
||||
|
||||
|
||||
class Color(str, Enum):
|
||||
RED = "red"
|
||||
GREEN = "green"
|
||||
PINK = "pink"
|
||||
|
||||
|
||||
class Toy(BaseModel):
|
||||
name: str
|
||||
color: Color
|
||||
|
||||
|
||||
class Pet(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
toys: List[Toy]
|
||||
|
||||
|
||||
class PetCollectionView(PydanticView):
|
||||
@ -53,6 +66,26 @@ async def generated_oas(aiohttp_client, loop) -> web.Application:
|
||||
return await response.json()
|
||||
|
||||
|
||||
async def test_generated_oas_should_have_components_schemas(generated_oas):
|
||||
assert generated_oas["components"]["schemas"] == {
|
||||
"Color": {
|
||||
"description": "An enumeration.",
|
||||
"enum": ["red", "green", "pink"],
|
||||
"title": "Color",
|
||||
"type": "string",
|
||||
},
|
||||
"Toy": {
|
||||
"properties": {
|
||||
"color": {"$ref": "#/components/schemas/Color"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
},
|
||||
"required": ["name", "color"],
|
||||
"title": "Toy",
|
||||
"type": "object",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def test_generated_oas_should_have_pets_paths(generated_oas):
|
||||
assert "/pets" in generated_oas["paths"]
|
||||
|
||||
@ -77,7 +110,7 @@ async def test_pets_route_should_have_get_method(generated_oas):
|
||||
"in": "header",
|
||||
"name": "promo",
|
||||
"required": False,
|
||||
"schema": {"title": "promo", "format": "uuid", "type": "string"},
|
||||
"schema": {"format": "uuid", "title": "promo", "type": "string"},
|
||||
},
|
||||
],
|
||||
"responses": {
|
||||
@ -86,11 +119,35 @@ async def test_pets_route_should_have_get_method(generated_oas):
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"definitions": {
|
||||
"Color": {
|
||||
"description": "An enumeration.",
|
||||
"enum": ["red", "green", "pink"],
|
||||
"title": "Color",
|
||||
"type": "string",
|
||||
},
|
||||
"Toy": {
|
||||
"properties": {
|
||||
"color": {
|
||||
"$ref": "#/components/schemas/Color"
|
||||
},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
},
|
||||
"required": ["name", "color"],
|
||||
"title": "Toy",
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
"properties": {
|
||||
"id": {"title": "Id", "type": "integer"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"toys": {
|
||||
"items": {"$ref": "#/components/schemas/Toy"},
|
||||
"title": "Toys",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"required": ["id", "name"],
|
||||
"required": ["id", "name", "toys"],
|
||||
"title": "Pet",
|
||||
"type": "object",
|
||||
},
|
||||
@ -110,13 +167,35 @@ async def test_pets_route_should_have_post_method(generated_oas):
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Pet",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"Color": {
|
||||
"description": "An enumeration.",
|
||||
"enum": ["red", "green", "pink"],
|
||||
"title": "Color",
|
||||
"type": "string",
|
||||
},
|
||||
"Toy": {
|
||||
"properties": {
|
||||
"color": {"$ref": "#/components/schemas/Color"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
},
|
||||
"required": ["name", "color"],
|
||||
"title": "Toy",
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
"properties": {
|
||||
"id": {"title": "Id", "type": "integer"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"toys": {
|
||||
"items": {"$ref": "#/components/schemas/Toy"},
|
||||
"title": "Toys",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"required": ["id", "name"],
|
||||
"required": ["id", "name", "toys"],
|
||||
"title": "Pet",
|
||||
"type": "object",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,13 +205,35 @@ async def test_pets_route_should_have_post_method(generated_oas):
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Pet",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"Color": {
|
||||
"description": "An enumeration.",
|
||||
"enum": ["red", "green", "pink"],
|
||||
"title": "Color",
|
||||
"type": "string",
|
||||
},
|
||||
"Toy": {
|
||||
"properties": {
|
||||
"color": {"$ref": "#/components/schemas/Color"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
},
|
||||
"required": ["name", "color"],
|
||||
"title": "Toy",
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
"properties": {
|
||||
"id": {"title": "Id", "type": "integer"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"toys": {
|
||||
"items": {"$ref": "#/components/schemas/Toy"},
|
||||
"title": "Toys",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"required": ["id", "name"],
|
||||
"required": ["id", "name", "toys"],
|
||||
"title": "Pet",
|
||||
"type": "object",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -174,11 +275,33 @@ async def test_pets_id_route_should_have_get_method(generated_oas):
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"definitions": {
|
||||
"Color": {
|
||||
"description": "An enumeration.",
|
||||
"enum": ["red", "green", "pink"],
|
||||
"title": "Color",
|
||||
"type": "string",
|
||||
},
|
||||
"Toy": {
|
||||
"properties": {
|
||||
"color": {"$ref": "#/components/schemas/Color"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
},
|
||||
"required": ["name", "color"],
|
||||
"title": "Toy",
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
"properties": {
|
||||
"id": {"title": "Id", "type": "integer"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"toys": {
|
||||
"items": {"$ref": "#/components/schemas/Toy"},
|
||||
"title": "Toys",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"required": ["id", "name"],
|
||||
"required": ["id", "name", "toys"],
|
||||
"title": "Pet",
|
||||
"type": "object",
|
||||
}
|
||||
@ -204,11 +327,33 @@ async def test_pets_id_route_should_have_put_method(generated_oas):
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"definitions": {
|
||||
"Color": {
|
||||
"description": "An enumeration.",
|
||||
"enum": ["red", "green", "pink"],
|
||||
"title": "Color",
|
||||
"type": "string",
|
||||
},
|
||||
"Toy": {
|
||||
"properties": {
|
||||
"color": {"$ref": "#/components/schemas/Color"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
},
|
||||
"required": ["name", "color"],
|
||||
"title": "Toy",
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
"properties": {
|
||||
"id": {"title": "Id", "type": "integer"},
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"toys": {
|
||||
"items": {"$ref": "#/components/schemas/Toy"},
|
||||
"title": "Toys",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"required": ["id", "name"],
|
||||
"required": ["id", "name", "toys"],
|
||||
"title": "Pet",
|
||||
"type": "object",
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user