Skip to content

Commit

Permalink
Merge pull request #4 from dapper91/dev
Browse files Browse the repository at this point in the history
- optional body, cookies, headers passing implemented.
- docstring added.
- README completed.
  • Loading branch information
dapper91 authored Jan 30, 2022
2 parents 8f956de + 64f5cd9 commit f9765e2
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 19 deletions.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,28 @@ app.add_routes(routes)

web.run_app(app, port=8080)

```
```

If any path or query parameter name are clashes with body, headers or cookies argument
for some reason the last can be renamed:

```py
@routes.post('/{cookies}')
@validator.validated(cookies_argname='_cookies')
async def method(request: web.Request, body: Dict[str, Any], _cookies: AuthCookies, cookies: str):
# your code here ...

return web.Response(status=201)
```

If any argname is `None` the corresponding request part will not be passed to the function
and argname can be used as a path or query parameter.

```py
@routes.post('/{body}/{headers}')
@validator.validated(body_argname=None, headers_argname=None, cookies_argname=None)
async def method(request: web.Request, body: str, headers: str, cookies: str = ''):
# your code here ...

return web.Response(status=201)
```
47 changes: 30 additions & 17 deletions aiohttp_validator/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import typing
from collections import defaultdict
from types import SimpleNamespace
from typing import Any, Callable, Collection, Dict, Type
from typing import Any, Callable, Collection, Dict, Optional, Type

import multidict
import pydantic
Expand All @@ -13,9 +13,9 @@

def extract_annotations(
func: Callable,
body_argname: str,
headers_argname: str,
cookies_argname: str,
body_argname: Optional[str] = None,
headers_argname: Optional[str] = None,
cookies_argname: Optional[str] = None,
) -> SimpleNamespace:
body_annotation, headers_annotation, cookies_annotation, params_annotations = None, None, None, {}

Expand All @@ -24,11 +24,11 @@ def extract_annotations(
# skip aiohttp method first argument (aiohttp.web.Request)
parameters = list(signature.parameters.values())[1:]
for param in parameters:
if param.name == body_argname:
if body_argname and param.name == body_argname:
body_annotation = param.annotation
elif param.name == headers_argname:
elif headers_argname and param.name == headers_argname:
headers_annotation = param.annotation
elif param.name == cookies_argname:
elif cookies_argname and param.name == cookies_argname:
cookies_annotation = param.annotation
else:
params_annotations[param.name] = (
Expand Down Expand Up @@ -127,31 +127,44 @@ async def process_cookes(request: web.Request, cookies_annotation: Any) -> Any:


def validated(
body_argname: str = 'body',
headers_argname: str = 'headers',
cookies_argname: str = 'cookies',
body_argname: Optional[str] = 'body',
headers_argname: Optional[str] = 'headers',
cookies_argname: Optional[str] = 'cookies',
) -> Callable:
"""
Creates a function validating decorator.
If any path or query parameter name are clashes with body, headers or cookies argument for some reason
the last can be renamed. If any argname is `None` the corresponding request part will not be passed to the function
and argname can be used as a path or query parameter.
:param body_argname: argument name the request body is passed by
:param headers_argname: argument name the request headers is passed by
:param cookies_argname: argument name the request cookies is passed by
:return: decorator
"""

def decorator(func: Callable) -> Callable:
annotations = extract_annotations(func, body_argname, headers_argname, cookies_argname)

params_model = pydantic.create_model('Params', **annotations.params)

@ft.wraps(func)
async def wrapper(request: web.Request, *args, **kwargs) -> Any:
if annotations.body is not None:
kwargs[body_argname] = await process_body(request, annotations.body)
if annotations.headers is not None:
kwargs[headers_argname] = await process_headers(request, annotations.headers)
if annotations.cookies is not None:
kwargs[cookies_argname] = await process_cookes(request, annotations.cookies)

fitted_query = fit_multidict_to_model(request.query, params_model)
try:
params = params_model.parse_obj(dict(fitted_query, **request.match_info))
except pydantic.ValidationError:
raise web.HTTPBadRequest

kwargs.update(params.dict())
if body_argname and annotations.body is not None:
kwargs[body_argname] = await process_body(request, annotations.body)
if headers_argname and annotations.headers is not None:
kwargs[headers_argname] = await process_headers(request, annotations.headers)
if cookies_argname and annotations.cookies is not None:
kwargs[cookies_argname] = await process_cookes(request, annotations.cookies)

return await func(request, *args, **kwargs)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "aiohttp-validator"
version = "0.1.2"
version = "0.1.3"
description = "aiohttp simple pydantic validator"
authors = ["Dmitry Pershin <[email protected]>"]
license = "Unlicense"
Expand Down

0 comments on commit f9765e2

Please sign in to comment.