Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
26fd6fa19f | ||
|
be944ac98e | ||
|
b896020a4f | ||
|
ba0530d6b1 | ||
|
83739c7c8e | ||
|
1dd98d2752 | ||
|
207204fe53 | ||
|
7618066b7f | ||
|
554e76ce51 | ||
|
2c51e9d929 | ||
|
ce341f8611 |
@@ -5,7 +5,7 @@ publish-pypi:
|
|||||||
stage: package
|
stage: package
|
||||||
image: python:3.10
|
image: python:3.10
|
||||||
script:
|
script:
|
||||||
- sed -i -e 's/1.12.1/${CI_COMMIT_TAG}/g' aiohttp_pydantic/__init__.py
|
- sed -i -e "s/1.12.1/${CI_COMMIT_TAG:1}/g" aiohttp_pydantic/__init__.py
|
||||||
- pip install -U setuptools wheel pip; pip install invoke
|
- pip install -U setuptools wheel pip; pip install invoke
|
||||||
- invoke upload --pypi-user ${PYPI_REPO_USER} --pypi-password ${PYPI_REPO_PASSWORD} --pypi-url ${PYPI_REPO_URL}
|
- invoke upload --pypi-user ${PYPI_REPO_USER} --pypi-password ${PYPI_REPO_PASSWORD} --pypi-url ${PYPI_REPO_URL}
|
||||||
only:
|
only:
|
||||||
|
@@ -68,7 +68,10 @@ class BodyGetter(AbstractInjector):
|
|||||||
|
|
||||||
def __init__(self, args_spec: dict, default_values: dict):
|
def __init__(self, args_spec: dict, default_values: dict):
|
||||||
self.arg_name, self.model = next(iter(args_spec.items()))
|
self.arg_name, self.model = next(iter(args_spec.items()))
|
||||||
self._expect_object = self.model.schema()["type"] == "object"
|
schema = self.model.schema()
|
||||||
|
if "type" not in schema:
|
||||||
|
schema["type"] = "object"
|
||||||
|
self._expect_object = schema["type"] == "object"
|
||||||
|
|
||||||
async def inject(self, request: BaseRequest, args_view: list, kwargs_view: dict):
|
async def inject(self, request: BaseRequest, args_view: list, kwargs_view: dict):
|
||||||
try:
|
try:
|
||||||
|
@@ -14,12 +14,13 @@ def setup(
|
|||||||
url_prefix: str = "/oas",
|
url_prefix: str = "/oas",
|
||||||
enable: bool = True,
|
enable: bool = True,
|
||||||
version_spec: Optional[str] = None,
|
version_spec: Optional[str] = None,
|
||||||
title_spec: Optional[str] = None
|
title_spec: Optional[str] = None,
|
||||||
|
custom_template: Optional[jinja2.Template] = None
|
||||||
):
|
):
|
||||||
if enable:
|
if enable:
|
||||||
oas_app = web.Application()
|
oas_app = web.Application()
|
||||||
oas_app["apps to expose"] = tuple(apps_to_expose) or (app,)
|
oas_app["apps to expose"] = tuple(apps_to_expose) or (app,)
|
||||||
oas_app["index template"] = jinja2.Template(
|
oas_app["index template"] = custom_template or jinja2.Template(
|
||||||
resources.read_text("aiohttp_pydantic.oas", "index.j2")
|
resources.read_text("aiohttp_pydantic.oas", "index.j2")
|
||||||
)
|
)
|
||||||
oas_app["version_spec"] = version_spec
|
oas_app["version_spec"] = version_spec
|
||||||
|
@@ -120,6 +120,19 @@ def tags(docstring: str) -> List[str]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def operation_id(docstring: str) -> str | None:
|
||||||
|
"""
|
||||||
|
Extract the "OperationId:" block of the docstring.
|
||||||
|
"""
|
||||||
|
iterator = LinesIterator(docstring)
|
||||||
|
for line in iterator:
|
||||||
|
if re.fullmatch("operation_?id\\s*:.*", line, re.IGNORECASE):
|
||||||
|
iterator.rewind()
|
||||||
|
return line.split(":")[1].strip(' ')
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def operation(docstring: str) -> str:
|
def operation(docstring: str) -> str:
|
||||||
"""
|
"""
|
||||||
Extract all docstring except the "Status Code:" block.
|
Extract all docstring except the "Status Code:" block.
|
||||||
@@ -127,7 +140,7 @@ def operation(docstring: str) -> str:
|
|||||||
lines = LinesIterator(docstring)
|
lines = LinesIterator(docstring)
|
||||||
ret = []
|
ret = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if re.fullmatch("status\\s+codes?\\s*:|tags\\s*:.*", line, re.IGNORECASE):
|
if re.fullmatch("status\\s+codes?\\s*:|tags\\s*:.*|operation_?id\\s*:.*", line, re.IGNORECASE):
|
||||||
lines.rewind()
|
lines.rewind()
|
||||||
for _ in _i_extract_block(lines):
|
for _ in _i_extract_block(lines):
|
||||||
pass
|
pass
|
||||||
|
@@ -1,45 +1,27 @@
|
|||||||
{# This updated file is part of swagger_ui_bundle (https://github.com/dtkav/swagger_ui_bundle) #}
|
{# This updated file is part of swagger_ui_bundle (https://github.com/bartsanchez/swagger_ui_bundle) #}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{{ title | default('Swagger UI') }}</title>
|
<title>{{ title | default('Swagger UI') }}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="{{ static_url | trim('/') }}/swagger-ui.css" >
|
<link rel="stylesheet" type="text/css" href="{{ static_url | trim('/') }}/swagger-ui.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ static_url | trim('/') }}/index.css" />
|
||||||
<link rel="icon" type="image/png" href="{{ static_url | trim('/') }}/favicon-32x32.png" sizes="32x32" />
|
<link rel="icon" type="image/png" href="{{ static_url | trim('/') }}/favicon-32x32.png" sizes="32x32" />
|
||||||
<link rel="icon" type="image/png" href="{{ static_url | trim('/') }}/favicon-16x16.png" sizes="16x16" />
|
<link rel="icon" type="image/png" href="{{ static_url | trim('/') }}/favicon-16x16.png" sizes="16x16" />
|
||||||
<style>
|
|
||||||
html
|
|
||||||
{
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: -moz-scrollbars-vertical;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*:before,
|
|
||||||
*:after
|
|
||||||
{
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
body
|
|
||||||
{
|
|
||||||
margin:0;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="swagger-ui"></div>
|
<div id="swagger-ui"></div>
|
||||||
|
<script src="{{ static_url | trim('/') }}/swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||||
<script src="{{ static_url | trim('/') }}/swagger-ui-bundle.js"> </script>
|
<script src="{{ static_url | trim('/') }}/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||||
<script src="{{ static_url | trim('/') }}/swagger-ui-standalone-preset.js"> </script>
|
|
||||||
<script>
|
<script>
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
// Begin Swagger UI call region
|
// Begin Swagger UI call region
|
||||||
const ui = SwaggerUIBundle({
|
const ui = SwaggerUIBundle({
|
||||||
url: "{{ openapi_spec_url }}",
|
url: "{{ openapi_spec_url }}",
|
||||||
|
{% if urls is defined %}
|
||||||
|
urls: {{ urls|tojson|safe }},
|
||||||
|
{% endif %}
|
||||||
validatorUrl: {{ validatorUrl | default('null') }},
|
validatorUrl: {{ validatorUrl | default('null') }},
|
||||||
{% if configUrl is defined %}
|
{% if configUrl is defined %}
|
||||||
configUrl: "{{ configUrl }}",
|
configUrl: "{{ configUrl }}",
|
||||||
@@ -54,16 +36,15 @@
|
|||||||
SwaggerUIBundle.plugins.DownloadUrl
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
],
|
],
|
||||||
layout: "StandaloneLayout"
|
layout: "StandaloneLayout"
|
||||||
})
|
});
|
||||||
{% if initOAuth is defined %}
|
{% if initOAuth is defined %}
|
||||||
ui.initOAuth(
|
ui.initOAuth(
|
||||||
{{ initOAuth|tojson|safe }}
|
{{ initOAuth|tojson|safe }}
|
||||||
)
|
)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
// End Swagger UI call region
|
// End Swagger UI call region
|
||||||
|
window.ui = ui;
|
||||||
window.ui = ui
|
};
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@@ -207,6 +207,17 @@ class OperationObject:
|
|||||||
else:
|
else:
|
||||||
self._spec.pop("tags", None)
|
self._spec.pop("tags", None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def operation_id(self) -> str | None:
|
||||||
|
return self._spec.get("operationId", None)
|
||||||
|
|
||||||
|
@operation_id.setter
|
||||||
|
def operation_id(self, operation_id: str | None) -> None:
|
||||||
|
if operation_id:
|
||||||
|
self._spec["operationId"] = operation_id
|
||||||
|
else:
|
||||||
|
self._spec.pop("operationId", None)
|
||||||
|
|
||||||
|
|
||||||
class PathItem:
|
class PathItem:
|
||||||
def __init__(self, spec: dict):
|
def __init__(self, spec: dict):
|
||||||
|
@@ -34,7 +34,8 @@ class _OASResponseBuilder:
|
|||||||
).copy()
|
).copy()
|
||||||
if def_sub_schemas := response_schema.pop("definitions", None):
|
if def_sub_schemas := response_schema.pop("definitions", None):
|
||||||
self._oas.components.schemas.update(def_sub_schemas)
|
self._oas.components.schemas.update(def_sub_schemas)
|
||||||
return response_schema
|
self._oas.components.schemas.update({response_schema['title']: response_schema})
|
||||||
|
return {'$ref': f'#/components/schemas/{response_schema["title"]}'}
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _handle_list(self, obj):
|
def _handle_list(self, obj):
|
||||||
@@ -87,6 +88,7 @@ def _add_http_method_to_oas(
|
|||||||
if description:
|
if description:
|
||||||
oas_operation.description = docstring_parser.operation(description)
|
oas_operation.description = docstring_parser.operation(description)
|
||||||
oas_operation.tags = docstring_parser.tags(description)
|
oas_operation.tags = docstring_parser.tags(description)
|
||||||
|
oas_operation.operation_id = docstring_parser.operation_id(description)
|
||||||
status_code_descriptions = docstring_parser.status_code(description)
|
status_code_descriptions = docstring_parser.status_code(description)
|
||||||
else:
|
else:
|
||||||
status_code_descriptions = {}
|
status_code_descriptions = {}
|
||||||
@@ -126,6 +128,10 @@ def _add_http_method_to_oas(
|
|||||||
ref_template="#/components/schemas/{model}"
|
ref_template="#/components/schemas/{model}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# move definitions
|
||||||
|
if def_sub_schemas := oas_operation.parameters[i].schema.pop("definitions", None):
|
||||||
|
oas.components.schemas.update(def_sub_schemas)
|
||||||
|
|
||||||
return_type = get_type_hints(handler).get("return")
|
return_type = get_type_hints(handler).get("return")
|
||||||
if return_type is not None:
|
if return_type is not None:
|
||||||
_OASResponseBuilder(oas, oas_operation, status_code_descriptions).build(
|
_OASResponseBuilder(oas, oas_operation, status_code_descriptions).build(
|
||||||
|
@@ -32,7 +32,7 @@ python_requires = >=3.8
|
|||||||
install_requires =
|
install_requires =
|
||||||
aiohttp
|
aiohttp
|
||||||
pydantic>=1.7
|
pydantic>=1.7
|
||||||
swagger-ui-bundle
|
swagger-4-ui-bundle
|
||||||
|
|
||||||
[options.extras_require]
|
[options.extras_require]
|
||||||
test =
|
test =
|
||||||
|
@@ -1,16 +1,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Optional, Union, Literal
|
from typing import List, Optional, Union, Literal, Annotated
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
from pydantic import Field
|
||||||
from pydantic.main import BaseModel
|
from pydantic.main import BaseModel
|
||||||
|
|
||||||
from aiohttp_pydantic import PydanticView, oas
|
from aiohttp_pydantic import PydanticView, oas
|
||||||
from aiohttp_pydantic.injectors import Group
|
from aiohttp_pydantic.injectors import Group
|
||||||
from aiohttp_pydantic.oas.typing import r200, r201, r204, r404
|
from aiohttp_pydantic.oas.typing import r200, r201, r204, r404, r400
|
||||||
from aiohttp_pydantic.oas.view import generate_oas
|
from aiohttp_pydantic.oas.view import generate_oas
|
||||||
|
|
||||||
|
|
||||||
@@ -20,6 +21,11 @@ class Color(str, Enum):
|
|||||||
PINK = "pink"
|
PINK = "pink"
|
||||||
|
|
||||||
|
|
||||||
|
class Lang(str, Enum):
|
||||||
|
EN = 'en'
|
||||||
|
FR = 'fr'
|
||||||
|
|
||||||
|
|
||||||
class Toy(BaseModel):
|
class Toy(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
color: Color
|
color: Color
|
||||||
@@ -27,13 +33,32 @@ class Toy(BaseModel):
|
|||||||
|
|
||||||
class Pet(BaseModel):
|
class Pet(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: Optional[str] = Field(None)
|
||||||
toys: List[Toy]
|
toys: List[Toy]
|
||||||
|
|
||||||
|
|
||||||
|
class Error(BaseModel):
|
||||||
|
code: int
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
class Cat(BaseModel):
|
||||||
|
pet_type: Literal['cat']
|
||||||
|
meows: int
|
||||||
|
|
||||||
|
|
||||||
|
class Dog(BaseModel):
|
||||||
|
pet_type: Literal['dog']
|
||||||
|
barks: float
|
||||||
|
|
||||||
|
|
||||||
|
class Animal(BaseModel):
|
||||||
|
__root__: Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]
|
||||||
|
|
||||||
|
|
||||||
class PetCollectionView(PydanticView):
|
class PetCollectionView(PydanticView):
|
||||||
async def get(
|
async def get(
|
||||||
self, format: str, name: Optional[str] = None, *, promo: Optional[UUID] = None
|
self, format: str, lang: Lang = Lang.EN, name: Optional[str] = None, *, promo: Optional[UUID] = None
|
||||||
) -> r200[List[Pet]]:
|
) -> r200[List[Pet]]:
|
||||||
"""
|
"""
|
||||||
Get a list of pets
|
Get a list of pets
|
||||||
@@ -41,6 +66,7 @@ class PetCollectionView(PydanticView):
|
|||||||
Tags: pet
|
Tags: pet
|
||||||
Status Codes:
|
Status Codes:
|
||||||
200: Successful operation
|
200: Successful operation
|
||||||
|
OperationId: createPet
|
||||||
"""
|
"""
|
||||||
return web.json_response()
|
return web.json_response()
|
||||||
|
|
||||||
@@ -51,12 +77,12 @@ class PetCollectionView(PydanticView):
|
|||||||
|
|
||||||
class PetItemView(PydanticView):
|
class PetItemView(PydanticView):
|
||||||
async def get(
|
async def get(
|
||||||
self,
|
self,
|
||||||
id: int,
|
id: int,
|
||||||
/,
|
/,
|
||||||
size: Union[int, Literal["x", "l", "s"]],
|
size: Union[int, Literal["x", "l", "s"]],
|
||||||
day: Union[int, Literal["now"]] = "now",
|
day: Union[int, Literal["now"]] = "now",
|
||||||
) -> Union[r200[Pet], r404]:
|
) -> Union[r200[Pet], r404[Error], r400[Error]]:
|
||||||
return web.json_response()
|
return web.json_response()
|
||||||
|
|
||||||
async def put(self, id: int, /, pet: Pet):
|
async def put(self, id: int, /, pet: Pet):
|
||||||
@@ -79,6 +105,11 @@ class ViewResponseReturnASimpleType(PydanticView):
|
|||||||
return web.json_response()
|
return web.json_response()
|
||||||
|
|
||||||
|
|
||||||
|
class DiscriminatedView(PydanticView):
|
||||||
|
async def post(self, /, request: Animal) -> r200[int]:
|
||||||
|
return web.json_response()
|
||||||
|
|
||||||
|
|
||||||
async def ensure_content_durability(client):
|
async def ensure_content_durability(client):
|
||||||
"""
|
"""
|
||||||
Reload the page 2 times to ensure that content is always the same
|
Reload the page 2 times to ensure that content is always the same
|
||||||
@@ -103,6 +134,7 @@ async def generated_oas(aiohttp_client, event_loop) -> web.Application:
|
|||||||
app.router.add_view("/pets", PetCollectionView)
|
app.router.add_view("/pets", PetCollectionView)
|
||||||
app.router.add_view("/pets/{id}", PetItemView)
|
app.router.add_view("/pets/{id}", PetItemView)
|
||||||
app.router.add_view("/simple-type", ViewResponseReturnASimpleType)
|
app.router.add_view("/simple-type", ViewResponseReturnASimpleType)
|
||||||
|
app.router.add_view("/animals", DiscriminatedView)
|
||||||
oas.setup(app)
|
oas.setup(app)
|
||||||
|
|
||||||
return await ensure_content_durability(await aiohttp_client(app))
|
return await ensure_content_durability(await aiohttp_client(app))
|
||||||
@@ -110,12 +142,40 @@ async def generated_oas(aiohttp_client, event_loop) -> web.Application:
|
|||||||
|
|
||||||
async def test_generated_oas_should_have_components_schemas(generated_oas):
|
async def test_generated_oas_should_have_components_schemas(generated_oas):
|
||||||
assert generated_oas["components"]["schemas"] == {
|
assert generated_oas["components"]["schemas"] == {
|
||||||
|
'Cat': {'properties': {'meows': {'title': 'Meows', 'type': 'integer'},
|
||||||
|
'pet_type': {'enum': ['cat'],
|
||||||
|
'title': 'Pet Type',
|
||||||
|
'type': 'string'}},
|
||||||
|
'required': ['pet_type', 'meows'],
|
||||||
|
'title': 'Cat',
|
||||||
|
'type': 'object'},
|
||||||
"Color": {
|
"Color": {
|
||||||
"description": "An enumeration.",
|
"description": "An enumeration.",
|
||||||
"enum": ["red", "green", "pink"],
|
"enum": ["red", "green", "pink"],
|
||||||
"title": "Color",
|
"title": "Color",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
|
'Dog': {'properties': {'barks': {'title': 'Barks', 'type': 'number'},
|
||||||
|
'pet_type': {'enum': ['dog'],
|
||||||
|
'title': 'Pet Type',
|
||||||
|
'type': 'string'}},
|
||||||
|
'required': ['pet_type', 'barks'],
|
||||||
|
'title': 'Dog',
|
||||||
|
'type': 'object'},
|
||||||
|
'Error': {
|
||||||
|
'properties': {
|
||||||
|
'code': {'title': 'Code', 'type': 'integer'},
|
||||||
|
'text': {'title': 'Text', 'type': 'string'}},
|
||||||
|
'required': ['code', 'text'],
|
||||||
|
'title': 'Error',
|
||||||
|
'type': 'object'
|
||||||
|
},
|
||||||
|
'Lang': {
|
||||||
|
'description': 'An enumeration.',
|
||||||
|
'enum': ['en', 'fr'],
|
||||||
|
'title': 'Lang',
|
||||||
|
'type': 'string'
|
||||||
|
},
|
||||||
"Toy": {
|
"Toy": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"color": {"$ref": "#/components/schemas/Color"},
|
"color": {"$ref": "#/components/schemas/Color"},
|
||||||
@@ -125,6 +185,20 @@ async def test_generated_oas_should_have_components_schemas(generated_oas):
|
|||||||
"title": "Toy",
|
"title": "Toy",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
'Pet': {
|
||||||
|
'properties': {
|
||||||
|
'id': {'title': 'Id', 'type': 'integer'},
|
||||||
|
'name': {'title': 'Name', 'type': 'string'},
|
||||||
|
'toys': {
|
||||||
|
'items': {'$ref': '#/components/schemas/Toy'},
|
||||||
|
'title': 'Toys',
|
||||||
|
'type': 'array'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'required': ['id', 'toys'],
|
||||||
|
'title': 'Pet',
|
||||||
|
'type': 'object'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -135,6 +209,7 @@ async def test_generated_oas_should_have_pets_paths(generated_oas):
|
|||||||
async def test_pets_route_should_have_get_method(generated_oas):
|
async def test_pets_route_should_have_get_method(generated_oas):
|
||||||
assert generated_oas["paths"]["/pets"]["get"] == {
|
assert generated_oas["paths"]["/pets"]["get"] == {
|
||||||
"description": "Get a list of pets",
|
"description": "Get a list of pets",
|
||||||
|
"operationId": "createPet",
|
||||||
"tags": ["pet"],
|
"tags": ["pet"],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -143,6 +218,16 @@ async def test_pets_route_should_have_get_method(generated_oas):
|
|||||||
"required": True,
|
"required": True,
|
||||||
"schema": {"title": "format", "type": "string"},
|
"schema": {"title": "format", "type": "string"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'in': 'query',
|
||||||
|
'name': 'lang',
|
||||||
|
'required': False,
|
||||||
|
'schema': {
|
||||||
|
'allOf': [{'$ref': '#/components/schemas/Lang'}],
|
||||||
|
'default': 'en',
|
||||||
|
'title': 'lang'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
@@ -162,20 +247,7 @@ async def test_pets_route_should_have_get_method(generated_oas):
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"items": {
|
"items": {'$ref': '#/components/schemas/Pet'},
|
||||||
"properties": {
|
|
||||||
"id": {"title": "Id", "type": "integer"},
|
|
||||||
"name": {"title": "Name", "type": "string"},
|
|
||||||
"toys": {
|
|
||||||
"items": {"$ref": "#/components/schemas/Toy"},
|
|
||||||
"title": "Toys",
|
|
||||||
"type": "array",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": ["id", "name", "toys"],
|
|
||||||
"title": "Pet",
|
|
||||||
"type": "object",
|
|
||||||
},
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,7 +273,7 @@ async def test_pets_route_should_have_post_method(generated_oas):
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": ["id", "name", "toys"],
|
"required": ["id", "toys"],
|
||||||
"title": "Pet",
|
"title": "Pet",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
}
|
}
|
||||||
@@ -213,20 +285,7 @@ async def test_pets_route_should_have_post_method(generated_oas):
|
|||||||
"description": "",
|
"description": "",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {'$ref': '#/components/schemas/Pet'}
|
||||||
"properties": {
|
|
||||||
"id": {"title": "Id", "type": "integer"},
|
|
||||||
"name": {"title": "Name", "type": "string"},
|
|
||||||
"toys": {
|
|
||||||
"items": {"$ref": "#/components/schemas/Toy"},
|
|
||||||
"title": "Toys",
|
|
||||||
"type": "array",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": ["id", "name", "toys"],
|
|
||||||
"title": "Pet",
|
|
||||||
"type": "object",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -285,30 +344,14 @@ async def test_pets_id_route_should_have_get_method(generated_oas):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"responses": {
|
'responses': {
|
||||||
"200": {
|
'200': {'content': {'application/json': {'schema': {'$ref': '#/components/schemas/Pet'}}},
|
||||||
"description": "",
|
'description': ''},
|
||||||
"content": {
|
'400': {'content': {'application/json': {'schema': {'$ref': '#/components/schemas/Error'}}},
|
||||||
"application/json": {
|
'description': ''},
|
||||||
"schema": {
|
'404': {'content': {'application/json': {'schema': {'$ref': '#/components/schemas/Error'}}},
|
||||||
"properties": {
|
'description': ''}
|
||||||
"id": {"title": "Id", "type": "integer"},
|
}
|
||||||
"name": {"title": "Name", "type": "string"},
|
|
||||||
"toys": {
|
|
||||||
"items": {"$ref": "#/components/schemas/Toy"},
|
|
||||||
"title": "Toys",
|
|
||||||
"type": "array",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": ["id", "name", "toys"],
|
|
||||||
"title": "Pet",
|
|
||||||
"type": "object",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"404": {"description": "", "content": {}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -335,7 +378,7 @@ async def test_pets_id_route_should_have_put_method(generated_oas):
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": ["id", "name", "toys"],
|
"required": ["id", "toys"],
|
||||||
"title": "Pet",
|
"title": "Pet",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
@@ -54,6 +55,20 @@ class ArticleViewWithPaginationGroup(PydanticView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Lang(str, Enum):
|
||||||
|
EN = 'en'
|
||||||
|
FR = 'fr'
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleViewWithEnumInQuery(PydanticView):
|
||||||
|
async def get(self, lang: Lang):
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"lang": lang
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_get_article_without_required_qs_should_return_an_error_message(
|
async def test_get_article_without_required_qs_should_return_an_error_message(
|
||||||
aiohttp_client, event_loop
|
aiohttp_client, event_loop
|
||||||
):
|
):
|
||||||
@@ -236,6 +251,20 @@ async def test_get_article_with_page_and_page_size(aiohttp_client, event_loop):
|
|||||||
assert resp.content_type == "application/json"
|
assert resp.content_type == "application/json"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_article_with_enum_in_query(aiohttp_client, event_loop):
|
||||||
|
app = web.Application()
|
||||||
|
app.router.add_view("/article", ArticleViewWithEnumInQuery)
|
||||||
|
|
||||||
|
client = await aiohttp_client(app)
|
||||||
|
|
||||||
|
resp = await client.get(
|
||||||
|
"/article", params={"lang": Lang.EN.value}
|
||||||
|
)
|
||||||
|
assert await resp.json() == {'lang': Lang.EN}
|
||||||
|
assert resp.status == 200
|
||||||
|
assert resp.content_type == "application/json"
|
||||||
|
|
||||||
|
|
||||||
async def test_get_article_with_page_and_wrong_page_size(aiohttp_client, event_loop):
|
async def test_get_article_with_page_and_wrong_page_size(aiohttp_client, event_loop):
|
||||||
app = web.Application()
|
app = web.Application()
|
||||||
app.router.add_view("/article", ArticleViewWithPaginationGroup)
|
app.router.add_view("/article", ArticleViewWithPaginationGroup)
|
||||||
|
Reference in New Issue
Block a user