query string accept multiple values for same parameter key (#11)
This commit is contained in:
parent
81d4e93a1d
commit
145d2fc0f2
25
README.rst
25
README.rst
@ -54,7 +54,7 @@ Example:
|
||||
return web.json_response({'name': article.name,
|
||||
'number_of_page': article.nb_page})
|
||||
|
||||
async def get(self, with_comments: Optional[bool]):
|
||||
async def get(self, with_comments: bool=False):
|
||||
return web.json_response({'with_comments': with_comments})
|
||||
|
||||
|
||||
@ -101,7 +101,7 @@ API:
|
||||
Inject Path Parameters
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To declare a path parameters, you must declare your argument as a `positional-only parameters`_:
|
||||
To declare a path parameter, you must declare your argument as a `positional-only parameters`_:
|
||||
|
||||
|
||||
Example:
|
||||
@ -118,18 +118,33 @@ Example:
|
||||
Inject Query String Parameters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To declare a query parameters, you must declare your argument as a simple argument:
|
||||
To declare a query parameter, you must declare your argument as a simple argument:
|
||||
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
class AccountView(PydanticView):
|
||||
async def get(self, customer_id: str):
|
||||
async def get(self, customer_id: Optional[str] = None):
|
||||
...
|
||||
|
||||
app = web.Application()
|
||||
app.router.add_get('/customers', AccountView)
|
||||
|
||||
|
||||
A query string parameter is generally optional and we do not want to force the user to set it in the URL.
|
||||
It's recommended to define a default value. It's possible to get a multiple value for the same parameter using
|
||||
the List type
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
class AccountView(PydanticView):
|
||||
async def get(self, tags: List[str] = []):
|
||||
...
|
||||
|
||||
app = web.Application()
|
||||
app.router.add_get('/customers', AccountView)
|
||||
|
||||
|
||||
Inject Request Body
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -152,7 +167,7 @@ To declare a body parameter, you must declare your argument as a simple argument
|
||||
Inject HTTP headers
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To declare a HTTP headers parameters, you must declare your argument as a `keyword-only argument`_.
|
||||
To declare a HTTP headers parameter, you must declare your argument as a `keyword-only argument`_.
|
||||
|
||||
|
||||
.. code-block:: python3
|
||||
|
@ -1,10 +1,12 @@
|
||||
import abc
|
||||
import typing
|
||||
from inspect import signature
|
||||
from json.decoder import JSONDecodeError
|
||||
from typing import Callable, Tuple
|
||||
|
||||
from aiohttp.web_exceptions import HTTPBadRequest
|
||||
from aiohttp.web_request import BaseRequest
|
||||
from multidict import MultiDict
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .utils import is_pydantic_base_model
|
||||
@ -94,9 +96,27 @@ class QueryGetter(AbstractInjector):
|
||||
attrs = {"__annotations__": args_spec}
|
||||
attrs.update(default_values)
|
||||
self.model = type("QueryModel", (BaseModel,), attrs)
|
||||
self.args_spec = args_spec
|
||||
self._is_multiple = frozenset(
|
||||
name for name, spec in args_spec.items() if typing.get_origin(spec) is list
|
||||
)
|
||||
|
||||
def inject(self, request: BaseRequest, args_view: list, kwargs_view: dict):
|
||||
kwargs_view.update(self.model(**request.query).dict())
|
||||
kwargs_view.update(self.model(**self._query_to_dict(request.query)).dict())
|
||||
|
||||
def _query_to_dict(self, query: MultiDict):
|
||||
"""
|
||||
Return a dict with list as value from the MultiDict.
|
||||
|
||||
The value will be wrapped in a list if the args spec is define as a list or if
|
||||
the multiple values are sent (i.e ?foo=1&foo=2)
|
||||
"""
|
||||
return {
|
||||
key: values
|
||||
if len(values := query.getall(key)) > 1 or key in self._is_multiple
|
||||
else value
|
||||
for key, value in query.items()
|
||||
}
|
||||
|
||||
|
||||
class HeadersGetter(AbstractInjector):
|
||||
|
@ -59,7 +59,7 @@ class PetItemView(PydanticView):
|
||||
return web.json_response()
|
||||
|
||||
|
||||
class TestResponseReturnASimpleType(PydanticView):
|
||||
class ViewResponseReturnASimpleType(PydanticView):
|
||||
async def get(self) -> r200[int]:
|
||||
"""
|
||||
Status Codes:
|
||||
@ -73,7 +73,7 @@ async def generated_oas(aiohttp_client, loop) -> web.Application:
|
||||
app = web.Application()
|
||||
app.router.add_view("/pets", PetCollectionView)
|
||||
app.router.add_view("/pets/{id}", PetItemView)
|
||||
app.router.add_view("/simple-type", TestResponseReturnASimpleType)
|
||||
app.router.add_view("/simple-type", ViewResponseReturnASimpleType)
|
||||
oas.setup(app)
|
||||
|
||||
client = await aiohttp_client(app)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
@ -7,10 +7,19 @@ from aiohttp_pydantic import PydanticView
|
||||
|
||||
class ArticleView(PydanticView):
|
||||
async def get(
|
||||
self, with_comments: bool, age: Optional[int] = None, nb_items: int = 7
|
||||
self,
|
||||
with_comments: bool,
|
||||
age: Optional[int] = None,
|
||||
nb_items: int = 7,
|
||||
tags: List[str] = [],
|
||||
):
|
||||
return web.json_response(
|
||||
{"with_comments": with_comments, "age": age, "nb_items": nb_items}
|
||||
{
|
||||
"with_comments": with_comments,
|
||||
"age": age,
|
||||
"nb_items": nb_items,
|
||||
"tags": tags,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -65,7 +74,12 @@ async def test_get_article_with_valid_qs_should_return_the_parsed_type(
|
||||
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, "nb_items": 7}
|
||||
assert await resp.json() == {
|
||||
"with_comments": True,
|
||||
"age": 3,
|
||||
"nb_items": 7,
|
||||
"tags": [],
|
||||
}
|
||||
|
||||
|
||||
async def test_get_article_with_valid_qs_and_omitted_optional_should_return_default_value(
|
||||
@ -77,6 +91,70 @@ async def test_get_article_with_valid_qs_and_omitted_optional_should_return_defa
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
resp = await client.get("/article", params={"with_comments": "yes"})
|
||||
assert await resp.json() == {"with_comments": True, "age": None, "nb_items": 7}
|
||||
assert await resp.json() == {
|
||||
"with_comments": True,
|
||||
"age": None,
|
||||
"nb_items": 7,
|
||||
"tags": [],
|
||||
}
|
||||
assert resp.status == 200
|
||||
assert resp.content_type == "application/json"
|
||||
|
||||
|
||||
async def test_get_article_with_multiple_value_for_qs_age_must_failed(
|
||||
aiohttp_client, loop
|
||||
):
|
||||
app = web.Application()
|
||||
app.router.add_view("/article", ArticleView)
|
||||
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
resp = await client.get("/article", params={"age": ["2", "3"], "with_comments": 1})
|
||||
assert await resp.json() == [
|
||||
{
|
||||
"in": "query string",
|
||||
"loc": ["age"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
]
|
||||
assert resp.status == 400
|
||||
assert resp.content_type == "application/json"
|
||||
|
||||
|
||||
async def test_get_article_with_multiple_value_of_tags(aiohttp_client, loop):
|
||||
app = web.Application()
|
||||
app.router.add_view("/article", ArticleView)
|
||||
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
resp = await client.get(
|
||||
"/article", params={"age": 2, "with_comments": 1, "tags": ["aa", "bb"]}
|
||||
)
|
||||
assert await resp.json() == {
|
||||
"age": 2,
|
||||
"nb_items": 7,
|
||||
"tags": ["aa", "bb"],
|
||||
"with_comments": True,
|
||||
}
|
||||
assert resp.status == 200
|
||||
assert resp.content_type == "application/json"
|
||||
|
||||
|
||||
async def test_get_article_with_one_value_of_tags_must_be_a_list(aiohttp_client, loop):
|
||||
app = web.Application()
|
||||
app.router.add_view("/article", ArticleView)
|
||||
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
resp = await client.get(
|
||||
"/article", params={"age": 2, "with_comments": 1, "tags": ["aa"]}
|
||||
)
|
||||
assert await resp.json() == {
|
||||
"age": 2,
|
||||
"nb_items": 7,
|
||||
"tags": ["aa"],
|
||||
"with_comments": True,
|
||||
}
|
||||
assert resp.status == 200
|
||||
assert resp.content_type == "application/json"
|
||||
|
Loading…
x
Reference in New Issue
Block a user