fix bug we cannot use optional params
This commit is contained in:
parent
13c19105d8
commit
d866ce5358
@ -5,6 +5,8 @@ from typing import Callable, Tuple
|
|||||||
from aiohttp.web_request import BaseRequest
|
from aiohttp.web_request import BaseRequest
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from .utils import is_pydantic_base_model
|
||||||
|
|
||||||
|
|
||||||
class AbstractInjector(metaclass=abc.ABCMeta):
|
class AbstractInjector(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
@ -98,7 +100,7 @@ def _parse_func_signature(func: Callable) -> Tuple[dict, dict, dict, dict]:
|
|||||||
if param_spec.kind is param_spec.POSITIONAL_ONLY:
|
if param_spec.kind is param_spec.POSITIONAL_ONLY:
|
||||||
path_args[param_name] = param_spec.annotation
|
path_args[param_name] = param_spec.annotation
|
||||||
elif param_spec.kind is param_spec.POSITIONAL_OR_KEYWORD:
|
elif param_spec.kind is param_spec.POSITIONAL_OR_KEYWORD:
|
||||||
if issubclass(param_spec.annotation, BaseModel):
|
if is_pydantic_base_model(param_spec.annotation):
|
||||||
body_args[param_name] = param_spec.annotation
|
body_args[param_name] = param_spec.annotation
|
||||||
else:
|
else:
|
||||||
qs_args[param_name] = param_spec.annotation
|
qs_args[param_name] = param_spec.annotation
|
||||||
|
@ -3,27 +3,17 @@ from typing import List, Type
|
|||||||
|
|
||||||
from aiohttp.web import Response, json_response
|
from aiohttp.web import Response, json_response
|
||||||
from aiohttp.web_app import Application
|
from aiohttp.web_app import Application
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from aiohttp_pydantic.oas.struct import OpenApiSpec3, OperationObject, PathItem
|
from aiohttp_pydantic.oas.struct import OpenApiSpec3, OperationObject, PathItem
|
||||||
|
|
||||||
from ..injectors import _parse_func_signature
|
from ..injectors import _parse_func_signature
|
||||||
|
from ..utils import is_pydantic_base_model
|
||||||
from ..view import PydanticView, is_pydantic_view
|
from ..view import PydanticView, is_pydantic_view
|
||||||
from .typing import is_status_code_type
|
from .typing import is_status_code_type
|
||||||
|
|
||||||
JSON_SCHEMA_TYPES = {float: "number", str: "string", int: "integer"}
|
JSON_SCHEMA_TYPES = {float: "number", str: "string", int: "integer"}
|
||||||
|
|
||||||
|
|
||||||
def _is_pydantic_base_model(obj):
|
|
||||||
"""
|
|
||||||
Return true is obj is a pydantic.BaseModel subclass.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return issubclass(obj, BaseModel)
|
|
||||||
except TypeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class _OASResponseBuilder:
|
class _OASResponseBuilder:
|
||||||
"""
|
"""
|
||||||
Parse the type annotated as returned by a function and
|
Parse the type annotated as returned by a function and
|
||||||
@ -35,7 +25,7 @@ class _OASResponseBuilder:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _handle_pydantic_base_model(obj):
|
def _handle_pydantic_base_model(obj):
|
||||||
if _is_pydantic_base_model(obj):
|
if is_pydantic_base_model(obj):
|
||||||
return obj.schema()
|
return obj.schema()
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
11
aiohttp_pydantic/utils.py
Normal file
11
aiohttp_pydantic/utils.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
def is_pydantic_base_model(obj):
|
||||||
|
"""
|
||||||
|
Return true is obj is a pydantic.BaseModel subclass.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return issubclass(obj, BaseModel)
|
||||||
|
except TypeError:
|
||||||
|
return False
|
@ -9,14 +9,8 @@ from aiohttp.web_exceptions import HTTPMethodNotAllowed
|
|||||||
from aiohttp.web_response import StreamResponse
|
from aiohttp.web_response import StreamResponse
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from .injectors import (
|
from .injectors import (AbstractInjector, BodyGetter, HeadersGetter,
|
||||||
AbstractInjector,
|
MatchInfoGetter, QueryGetter, _parse_func_signature)
|
||||||
BodyGetter,
|
|
||||||
HeadersGetter,
|
|
||||||
MatchInfoGetter,
|
|
||||||
QueryGetter,
|
|
||||||
_parse_func_signature,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PydanticView(AbstractView):
|
class PydanticView(AbstractView):
|
||||||
|
@ -4,6 +4,7 @@ from pydantic import BaseModel
|
|||||||
class Pet(BaseModel):
|
class Pet(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
|
age: int
|
||||||
|
|
||||||
|
|
||||||
class Error(BaseModel):
|
class Error(BaseModel):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import List, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
@ -9,9 +9,11 @@ from .model import Error, Pet
|
|||||||
|
|
||||||
|
|
||||||
class PetCollectionView(PydanticView):
|
class PetCollectionView(PydanticView):
|
||||||
async def get(self) -> r200[List[Pet]]:
|
async def get(self, age: Optional[int] = None) -> r200[List[Pet]]:
|
||||||
pets = self.request.app["model"].list_pets()
|
pets = self.request.app["model"].list_pets()
|
||||||
return web.json_response([pet.dict() for pet in pets])
|
return web.json_response(
|
||||||
|
[pet.dict() for pet in pets if age is None or age == pet.age]
|
||||||
|
)
|
||||||
|
|
||||||
async def post(self, pet: Pet) -> r201[Pet]:
|
async def post(self, pet: Pet) -> r201[Pet]:
|
||||||
self.request.app["model"].add_pet(pet)
|
self.request.app["model"].add_pet(pet)
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from aiohttp_pydantic import PydanticView
|
from aiohttp_pydantic import PydanticView
|
||||||
|
|
||||||
|
|
||||||
class ArticleView(PydanticView):
|
class ArticleView(PydanticView):
|
||||||
async def get(self, with_comments: bool):
|
async def get(self, with_comments: bool, age: Optional[int] = None):
|
||||||
return web.json_response({"with_comments": with_comments})
|
return web.json_response({"with_comments": with_comments, "age": age})
|
||||||
|
|
||||||
|
|
||||||
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(
|
||||||
@ -53,7 +55,22 @@ async def test_get_article_with_valid_qs_should_return_the_parsed_type(
|
|||||||
app.router.add_view("/article", ArticleView)
|
app.router.add_view("/article", ArticleView)
|
||||||
|
|
||||||
client = await aiohttp_client(app)
|
client = await aiohttp_client(app)
|
||||||
|
|
||||||
|
resp = await client.get("/article", params={"with_comments": "yes", "age": 3})
|
||||||
|
assert resp.status == 200
|
||||||
|
assert resp.content_type == "application/json"
|
||||||
|
assert await resp.json() == {"with_comments": True, "age": 3}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_article_with_valid_qs_and_omitted_optional_should_return_none(
|
||||||
|
aiohttp_client, loop
|
||||||
|
):
|
||||||
|
app = web.Application()
|
||||||
|
app.router.add_view("/article", ArticleView)
|
||||||
|
|
||||||
|
client = await aiohttp_client(app)
|
||||||
|
|
||||||
resp = await client.get("/article", params={"with_comments": "yes"})
|
resp = await client.get("/article", params={"with_comments": "yes"})
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
assert resp.content_type == "application/json"
|
assert resp.content_type == "application/json"
|
||||||
assert await resp.json() == {"with_comments": True}
|
assert await resp.json() == {"with_comments": True, "age": None}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user