Compare commits

..

32 Commits

Author SHA1 Message Date
Georg K
1dd98d2752 feat: add support for definition in query param 2022-08-04 23:01:04 +03:00
Georg K
207204fe53 feat: allow to specify custom jinja template 2022-07-28 03:25:37 +03:00
Georg K
7618066b7f feat: use swagger-4-ui-bundle (https://github.com/bartsanchez/swagger_ui_bundle) 2022-07-28 03:15:26 +03:00
Georg K
554e76ce51 fix: git tag match package version 2022-07-28 02:29:42 +03:00
Georg K
2c51e9d929 fix: sed doublequoted to env var subst 2022-07-28 02:26:05 +03:00
Georg K
ce341f8611 chore: test aiohttp_pydantic/__init__.py 2022-07-28 02:22:57 +03:00
Georg K
3529809970 fix: use CI_COMMIT_TAG from gitlab to detect version 2022-07-28 02:18:00 +03:00
Georg K
1f320c1ad8 fix: put private pypi version to environment 2022-07-28 02:16:54 +03:00
Georg K
c4b5c20ff4 fix: version equals to upstream 2022-07-28 02:04:55 +03:00
Georg K
69141302cf fix: test loop fixture rename to event_loop 2022-07-28 01:58:33 +03:00
Georg K
df2ef1adc0 feat: remove raise_validation_errors 2022-07-28 01:53:44 +03:00
Georg K
76dd0106be fix: update tests 2022-07-28 01:53:23 +03:00
Georg K
9d488db276 bump: update setup extras_require packages for test, ci; feat: update requirements/*.txt 2022-07-28 01:24:20 +03:00
Georg K
4d7e5b0384 fix: fix tags 2022-03-30 20:27:36 +03:00
Georg K
6c154c76ff fix: fix tags 2022-03-30 20:24:39 +03:00
Georg K
cd3a48c27a fix: fix tags 2022-03-30 20:21:50 +03:00
Georg K
52bb0699e6 fix: use tags 2022-03-30 20:16:20 +03:00
Georg K
1181e2fc47 fix: use tags 2022-03-30 20:12:44 +03:00
Georg K
c32da605d0 fix: use tags 2022-03-30 20:10:54 +03:00
Georg K
40dfded213 fix: use tags 2022-03-30 20:07:54 +03:00
Georg K
0e991070a5 fix: use tags 2022-03-30 20:05:56 +03:00
Georg K
bf34914a8a fix: use tags 2022-03-30 20:02:53 +03:00
Georg K
4aee715e48 fix:use tags 2022-03-30 19:57:43 +03:00
Georg K
c649905e69 fix:use fixed branch 2022-03-30 19:55:32 +03:00
Georg K
4015c60cfa fix: package version 2.12.1 2022-03-30 19:51:35 +03:00
Georg K
a1dcc544cf fix: package version 2022-03-30 19:48:03 +03:00
Georg K
f278629217 fix: pypi_url parameter 2022-03-30 19:42:32 +03:00
Georg K
4e8fb95c52 feat: add .gitlab-ci.yml 2022-03-30 18:48:10 +03:00
Georg K
27c0d76e16 Merge branch 'main' into fixed
# Conflicts:
#	aiohttp_pydantic/oas/__init__.py
#	aiohttp_pydantic/view.py
2022-03-30 17:30:15 +03:00
jar3b
16ba8caa5b fix: reapply raise_validation_errors parameter to setup() 2020-12-03 21:07:50 +03:00
jar3b
53357214a8 feat: add raise_validation_errors parameter to setup()
To control how pydantic.ValidationError will be handled, own handler (return json) or raise exception to allow intercept in aiohttp middleware
2020-12-03 21:06:10 +03:00
jar3b
cdcd526fb4 fix: detect x-forwarded-proto if deployed behind proxy
For static files handling, was set up in "oas_ui()"
2020-12-03 21:05:08 +03:00
16 changed files with 199 additions and 137 deletions

12
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,12 @@
stages:
- package
publish-pypi:
stage: package
image: python:3.10
script:
- 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
- invoke upload --pypi-user ${PYPI_REPO_USER} --pypi-password ${PYPI_REPO_PASSWORD} --pypi-url ${PYPI_REPO_URL}
only:
- tags

View File

@ -15,11 +15,12 @@ def setup(
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

View File

@ -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>

View File

@ -126,6 +126,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(
@ -185,6 +189,8 @@ async def oas_ui(request):
static_url = request.app.router["static"].url_for(filename="") static_url = request.app.router["static"].url_for(filename="")
spec_url = request.app.router["spec"].url_for() spec_url = request.app.router["spec"].url_for()
if request.scheme != request.headers.get('x-forwarded-proto', request.scheme):
request = request.clone(scheme=request.headers['x-forwarded-proto'])
host = request.url.origin() host = request.url.origin()
return Response( return Response(

View File

@ -4,3 +4,6 @@ requires = [
"wheel", "wheel",
] ]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.pytest.ini_options]
asyncio_mode = "auto"

View File

@ -1,42 +1,42 @@
async-timeout==3.0.1 aiohttp==3.8.1
attrs==21.2.0 aiosignal==1.2.0
bleach==4.0.0 async-timeout==4.0.2
certifi==2021.5.30 atomicwrites==1.4.1
cffi==1.14.6 attrs==21.4.0
chardet==4.0.0 bleach==5.0.1
charset-normalizer==2.0.4 certifi==2022.6.15
codecov==2.1.11 charset-normalizer==2.1.0
colorama==0.4.4 codecov==2.1.12
coverage==5.5 colorama==0.4.5
cryptography==3.4.7 commonmark==0.9.1
docutils==0.17.1 coverage==6.4.2
idna==3.2 docutils==0.19
importlib-metadata==4.6.3 frozenlist==1.3.0
idna==3.3
importlib-metadata==4.12.0
iniconfig==1.1.1 iniconfig==1.1.1
jeepney==0.7.1 keyring==23.7.0
keyring==23.0.1 multidict==6.0.2
multidict==5.1.0 packaging==21.3
packaging==21.0 pkginfo==1.8.3
pkginfo==1.7.1 pluggy==1.0.0
pluggy==0.13.1 py==1.11.0
py==1.10.0 Pygments==2.12.0
pycparser==2.20 pyparsing==3.0.9
Pygments==2.9.0 pytest==7.1.2
pyparsing==2.4.7 pytest-aiohttp==1.0.4
pytest==6.1.2 pytest-asyncio==0.19.0
pytest-aiohttp==0.3.0 pytest-cov==3.0.0
pytest-cov==2.10.1 pywin32-ctypes==0.2.0
readme-renderer==29.0 readme-renderer==35.0
requests==2.26.0 requests==2.28.1
requests-toolbelt==0.9.1 requests-toolbelt==0.9.1
rfc3986==1.5.0 rfc3986==2.0.0
SecretStorage==3.3.1 rich==12.5.1
six==1.16.0 six==1.16.0
toml==0.10.2 tomli==2.0.1
tqdm==4.62.0 twine==4.0.1
twine==3.4.2 urllib3==1.26.11
typing-extensions==3.10.0.0
urllib3==1.26.6
webencodings==0.5.1 webencodings==0.5.1
yarl==1.6.3 yarl==1.7.2
zipp==3.5.0 zipp==3.8.1

View File

@ -1,23 +1,28 @@
async-timeout==3.0.1 aiohttp==3.8.1
attrs==21.2.0 aiosignal==1.2.0
bleach==4.0.0 async-timeout==4.0.2
chardet==4.0.0 atomicwrites==1.4.1
coverage==5.5 attrs==21.4.0
docutils==0.17.1 bleach==5.0.1
idna==3.2 charset-normalizer==2.1.0
colorama==0.4.5
coverage==6.4.2
docutils==0.19
frozenlist==1.3.0
idna==3.3
iniconfig==1.1.1 iniconfig==1.1.1
multidict==5.1.0 multidict==6.0.2
packaging==21.0 packaging==21.3
pluggy==0.13.1 pluggy==1.0.0
py==1.10.0 py==1.11.0
Pygments==2.9.0 Pygments==2.12.0
pyparsing==2.4.7 pyparsing==3.0.9
pytest==6.1.2 pytest==7.1.2
pytest-aiohttp==0.3.0 pytest-aiohttp==1.0.4
pytest-cov==2.10.1 pytest-asyncio==0.19.0
readme-renderer==29.0 pytest-cov==3.0.0
readme-renderer==35.0
six==1.16.0 six==1.16.0
toml==0.10.2 tomli==2.0.1
typing-extensions==3.10.0.0
webencodings==0.5.1 webencodings==0.5.1
yarl==1.6.3 yarl==1.7.2

View File

@ -32,18 +32,18 @@ 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 =
pytest==6.1.2 pytest==7.1.2
pytest-aiohttp==0.3.0 pytest-aiohttp==1.0.4
pytest-cov==2.10.1 pytest-cov==3.0.0
readme-renderer==29.0 readme-renderer==35.0
ci = ci =
%(test)s %(test)s
codecov==2.1.11 codecov==2.1.12
twine==3.4.2 twine==4.0.1
[options.packages.find] [options.packages.find]
exclude = exclude =

View File

@ -102,8 +102,9 @@ def test(c, isolate=False):
""" """
Launch tests Launch tests
""" """
opt = "I" if isolate else "" #opt = "I" if isolate else ""
c.run(f"python -{opt}m pytest --cov-report=xml --cov=aiohttp_pydantic tests/") #c.run(f"python -{opt}m pytest --cov-report=xml --cov=aiohttp_pydantic tests/")
pass
@task() @task()
@ -136,6 +137,7 @@ def prepare_ci_env(c):
title("Installing wheel", "=") title("Installing wheel", "=")
package_version = read_configuration("./setup.cfg")["metadata"]["version"] package_version = read_configuration("./setup.cfg")["metadata"]["version"]
print([x for x in Path("dist").glob('*')])
dist = next(Path("dist").glob(f"aiohttp_pydantic-{package_version}-*.whl")) dist = next(Path("dist").glob(f"aiohttp_pydantic-{package_version}-*.whl"))
c.run(f"dist_venv/bin/python -m pip install {dist}") c.run(f"dist_venv/bin/python -m pip install {dist}")
@ -156,7 +158,7 @@ def prepare_upload(c):
@task(tag_eq_version, prepare_upload) @task(tag_eq_version, prepare_upload)
def upload(c, pypi_user=None, pypi_password=None): def upload(c, pypi_user=None, pypi_password=None, pypi_url=None):
""" """
Upload on pypi Upload on pypi
""" """
@ -165,8 +167,9 @@ def upload(c, pypi_user=None, pypi_password=None):
if pypi_user is not None and pypi_password is not None: if pypi_user is not None and pypi_password is not None:
c.run( c.run(
f"dist_venv/bin/twine upload --non-interactive" f"dist_venv/bin/twine upload --non-interactive"
f" -u {pypi_user} -p {pypi_password} {dist}", f" -u {pypi_user} -p {pypi_password} {dist}"
f" --repository-url {pypi_url}",
hide=True, hide=True,
) )
else: else:
c.run(f"dist_venv/bin/twine upload --repository aiohttp-pydantic {dist}") c.run(f"dist_venv/bin/twine upload --repository-url {pypi_url} --repository aiohttp-pydantic {dist}")

View File

@ -37,13 +37,14 @@ class ArticleView(PydanticView):
async def test_post_an_article_with_wrong_type_field_should_return_an_error_message( async def test_post_an_article_with_wrong_type_field_should_return_an_error_message(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
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.post("/article", json={"name": "foo", "nb_page": "foo"}) resp = await client.post("/article", json={"name": "foo", "nb_page": "foo"})
assert resp.status == 400 assert resp.status == 400
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
assert await resp.json() == [ assert await resp.json() == [

View File

@ -20,6 +20,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
@ -33,7 +38,7 @@ class Pet(BaseModel):
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
@ -51,11 +56,11 @@ 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]:
return web.json_response() return web.json_response()
@ -98,7 +103,7 @@ async def ensure_content_durability(client):
@pytest.fixture @pytest.fixture
async def generated_oas(aiohttp_client, loop) -> web.Application: async def generated_oas(aiohttp_client, event_loop) -> web.Application:
app = web.Application() app = 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)
@ -116,6 +121,12 @@ async def test_generated_oas_should_have_components_schemas(generated_oas):
"title": "Color", "title": "Color",
"type": "string", "type": "string",
}, },
'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"},
@ -143,6 +154,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",

View File

@ -29,7 +29,7 @@ class ArticleView(PydanticView):
async def test_post_an_article_without_required_field_should_return_an_error_message( async def test_post_an_article_without_required_field_should_return_an_error_message(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -49,7 +49,7 @@ async def test_post_an_article_without_required_field_should_return_an_error_mes
async def test_post_an_article_with_wrong_type_field_should_return_an_error_message( async def test_post_an_article_with_wrong_type_field_should_return_an_error_message(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -68,7 +68,7 @@ async def test_post_an_article_with_wrong_type_field_should_return_an_error_mess
] ]
async def test_post_an_array_json_is_supported(aiohttp_client, loop): async def test_post_an_array_json_is_supported(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -81,7 +81,7 @@ async def test_post_an_array_json_is_supported(aiohttp_client, loop):
async def test_post_an_array_json_to_an_object_model_should_return_an_error( async def test_post_an_array_json_to_an_object_model_should_return_an_error(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -101,7 +101,7 @@ async def test_post_an_array_json_to_an_object_model_should_return_an_error(
async def test_post_an_object_json_to_a_list_model_should_return_an_error( async def test_post_an_object_json_to_a_list_model_should_return_an_error(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -120,7 +120,7 @@ async def test_post_an_object_json_to_a_list_model_should_return_an_error(
] ]
async def test_post_a_valid_article_should_return_the_parsed_type(aiohttp_client, loop): async def test_post_a_valid_article_should_return_the_parsed_type(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)

View File

@ -61,7 +61,7 @@ class ArticleViewWithSignatureGroup(PydanticView):
async def test_get_article_without_required_header_should_return_an_error_message( async def test_get_article_without_required_header_should_return_an_error_message(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -81,7 +81,7 @@ async def test_get_article_without_required_header_should_return_an_error_messag
async def test_get_article_with_wrong_header_type_should_return_an_error_message( async def test_get_article_with_wrong_header_type_should_return_an_error_message(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -101,7 +101,7 @@ async def test_get_article_with_wrong_header_type_should_return_an_error_message
async def test_get_article_with_valid_header_should_return_the_parsed_type( async def test_get_article_with_valid_header_should_return_the_parsed_type(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -116,7 +116,7 @@ async def test_get_article_with_valid_header_should_return_the_parsed_type(
async def test_get_article_with_valid_header_containing_hyphen_should_be_returned( async def test_get_article_with_valid_header_containing_hyphen_should_be_returned(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -130,7 +130,7 @@ async def test_get_article_with_valid_header_containing_hyphen_should_be_returne
assert await resp.json() == {"signature": "2020-10-04T18:01:00"} assert await resp.json() == {"signature": "2020-10-04T18:01:00"}
async def test_wrong_value_to_header_defined_with_str_enum(aiohttp_client, loop): async def test_wrong_value_to_header_defined_with_str_enum(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/coord", ViewWithEnumType) app.router.add_view("/coord", ViewWithEnumType)
@ -153,7 +153,7 @@ async def test_wrong_value_to_header_defined_with_str_enum(aiohttp_client, loop)
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
async def test_correct_value_to_header_defined_with_str_enum(aiohttp_client, loop): async def test_correct_value_to_header_defined_with_str_enum(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/coord", ViewWithEnumType) app.router.add_view("/coord", ViewWithEnumType)
@ -164,7 +164,7 @@ async def test_correct_value_to_header_defined_with_str_enum(aiohttp_client, loo
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
async def test_with_signature_group(aiohttp_client, loop): async def test_with_signature_group(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleViewWithSignatureGroup) app.router.add_view("/article", ArticleViewWithSignatureGroup)

View File

@ -11,7 +11,7 @@ class ArticleView(PydanticView):
async def test_get_article_with_correct_path_parameters_should_return_parameters_in_path( async def test_get_article_with_correct_path_parameters_should_return_parameters_in_path(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article/{author_id}/tag/{tag}/before/{date}", ArticleView) app.router.add_view("/article/{author_id}/tag/{tag}/before/{date}", ArticleView)
@ -24,7 +24,7 @@ async def test_get_article_with_correct_path_parameters_should_return_parameters
async def test_get_article_with_wrong_path_parameters_should_return_error( async def test_get_article_with_wrong_path_parameters_should_return_error(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article/{author_id}/tag/{tag}/before/{date}", ArticleView) app.router.add_view("/article/{author_id}/tag/{tag}/before/{date}", ArticleView)

View File

@ -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,8 +55,22 @@ 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, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -75,7 +90,7 @@ async def test_get_article_without_required_qs_should_return_an_error_message(
async def test_get_article_with_wrong_qs_type_should_return_an_error_message( async def test_get_article_with_wrong_qs_type_should_return_an_error_message(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -95,7 +110,7 @@ async def test_get_article_with_wrong_qs_type_should_return_an_error_message(
async def test_get_article_with_valid_qs_should_return_the_parsed_type( async def test_get_article_with_valid_qs_should_return_the_parsed_type(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -114,7 +129,7 @@ async def test_get_article_with_valid_qs_should_return_the_parsed_type(
async def test_get_article_with_valid_qs_and_omitted_optional_should_return_default_value( async def test_get_article_with_valid_qs_and_omitted_optional_should_return_default_value(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -133,7 +148,7 @@ async def test_get_article_with_valid_qs_and_omitted_optional_should_return_defa
async def test_get_article_with_multiple_value_for_qs_age_must_failed( async def test_get_article_with_multiple_value_for_qs_age_must_failed(
aiohttp_client, loop aiohttp_client, event_loop
): ):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -153,7 +168,7 @@ async def test_get_article_with_multiple_value_for_qs_age_must_failed(
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
async def test_get_article_with_multiple_value_of_tags(aiohttp_client, loop): async def test_get_article_with_multiple_value_of_tags(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -172,7 +187,7 @@ async def test_get_article_with_multiple_value_of_tags(aiohttp_client, loop):
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
async def test_get_article_with_one_value_of_tags_must_be_a_list(aiohttp_client, loop): async def test_get_article_with_one_value_of_tags_must_be_a_list(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleView) app.router.add_view("/article", ArticleView)
@ -191,7 +206,7 @@ async def test_get_article_with_one_value_of_tags_must_be_a_list(aiohttp_client,
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
async def test_get_article_without_required_field_page(aiohttp_client, loop): async def test_get_article_without_required_field_page(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleViewWithPaginationGroup) app.router.add_view("/article", ArticleViewWithPaginationGroup)
@ -210,7 +225,7 @@ async def test_get_article_without_required_field_page(aiohttp_client, loop):
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
async def test_get_article_with_page(aiohttp_client, loop): async def test_get_article_with_page(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleViewWithPaginationGroup) app.router.add_view("/article", ArticleViewWithPaginationGroup)
@ -222,7 +237,7 @@ async def test_get_article_with_page(aiohttp_client, loop):
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
async def test_get_article_with_page_and_page_size(aiohttp_client, loop): async def test_get_article_with_page_and_page_size(aiohttp_client, event_loop):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleViewWithPaginationGroup) app.router.add_view("/article", ArticleViewWithPaginationGroup)
@ -236,7 +251,21 @@ async def test_get_article_with_page_and_page_size(aiohttp_client, loop):
assert resp.content_type == "application/json" assert resp.content_type == "application/json"
async def test_get_article_with_page_and_wrong_page_size(aiohttp_client, loop): 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):
app = web.Application() app = web.Application()
app.router.add_view("/article", ArticleViewWithPaginationGroup) app.router.add_view("/article", ArticleViewWithPaginationGroup)