Merge branch 'main' into fixed
# Conflicts: # aiohttp_pydantic/oas/__init__.py # aiohttp_pydantic/view.py
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from functools import update_wrapper
|
||||
from inspect import iscoroutinefunction
|
||||
from typing import Any, Callable, Generator, Iterable
|
||||
from typing import Any, Callable, Generator, Iterable, Set, ClassVar
|
||||
import warnings
|
||||
|
||||
from aiohttp.abc import AbstractView
|
||||
from aiohttp.hdrs import METH_ALL
|
||||
@@ -9,8 +10,16 @@ from aiohttp.web_exceptions import HTTPMethodNotAllowed
|
||||
from aiohttp.web_response import StreamResponse
|
||||
from pydantic import ValidationError
|
||||
|
||||
from .injectors import (AbstractInjector, BodyGetter, HeadersGetter,
|
||||
MatchInfoGetter, QueryGetter, _parse_func_signature)
|
||||
from .injectors import (
|
||||
AbstractInjector,
|
||||
BodyGetter,
|
||||
HeadersGetter,
|
||||
MatchInfoGetter,
|
||||
QueryGetter,
|
||||
_parse_func_signature,
|
||||
CONTEXT,
|
||||
Group,
|
||||
)
|
||||
|
||||
|
||||
class PydanticView(AbstractView):
|
||||
@@ -18,30 +27,46 @@ class PydanticView(AbstractView):
|
||||
An AIOHTTP View that validate request using function annotations.
|
||||
"""
|
||||
|
||||
# Allowed HTTP methods; overridden when subclassed.
|
||||
allowed_methods: ClassVar[Set[str]] = {}
|
||||
|
||||
async def _iter(self) -> StreamResponse:
|
||||
method = getattr(self, self.request.method.lower(), None)
|
||||
resp = await method()
|
||||
return resp
|
||||
if (method_name := self.request.method) not in self.allowed_methods:
|
||||
self._raise_allowed_methods()
|
||||
return await getattr(self, method_name.lower())()
|
||||
|
||||
def __await__(self) -> Generator[Any, None, StreamResponse]:
|
||||
return self._iter().__await__()
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
def __init_subclass__(cls, **kwargs) -> None:
|
||||
"""Define allowed methods and decorate handlers.
|
||||
|
||||
Handlers are decorated if and only if they directly bound on the PydanticView class or
|
||||
PydanticView subclass. This prevents that methods are decorated multiple times and that method
|
||||
defined in aiohttp.View parent class is decorated.
|
||||
"""
|
||||
|
||||
cls.allowed_methods = {
|
||||
meth_name for meth_name in METH_ALL if hasattr(cls, meth_name.lower())
|
||||
}
|
||||
|
||||
for meth_name in METH_ALL:
|
||||
if meth_name not in cls.allowed_methods:
|
||||
setattr(cls, meth_name.lower(), cls.raise_not_allowed)
|
||||
else:
|
||||
if meth_name.lower() in vars(cls):
|
||||
handler = getattr(cls, meth_name.lower())
|
||||
decorated_handler = inject_params(handler, cls.parse_func_signature)
|
||||
setattr(cls, meth_name.lower(), decorated_handler)
|
||||
|
||||
async def raise_not_allowed(self):
|
||||
def _raise_allowed_methods(self) -> None:
|
||||
raise HTTPMethodNotAllowed(self.request.method, self.allowed_methods)
|
||||
|
||||
def raise_not_allowed(self) -> None:
|
||||
warnings.warn(
|
||||
"PydanticView.raise_not_allowed is deprecated and renamed _raise_allowed_methods",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self._raise_allowed_methods()
|
||||
|
||||
@staticmethod
|
||||
def parse_func_signature(func: Callable) -> Iterable[AbstractInjector]:
|
||||
path_args, body_args, qs_args, header_args, defaults = _parse_func_signature(
|
||||
@@ -65,6 +90,22 @@ class PydanticView(AbstractView):
|
||||
injectors.append(HeadersGetter(header_args, default_value(header_args)))
|
||||
return injectors
|
||||
|
||||
async def on_validation_error(
|
||||
self, exception: ValidationError, context: CONTEXT
|
||||
) -> StreamResponse:
|
||||
"""
|
||||
This method is a hook to intercept ValidationError.
|
||||
|
||||
This hook can be redefined to return a custom HTTP response error.
|
||||
The exception is a pydantic.ValidationError and the context is "body",
|
||||
"headers", "path" or "query string"
|
||||
"""
|
||||
errors = exception.errors()
|
||||
for error in errors:
|
||||
error["in"] = context
|
||||
|
||||
return json_response(data=errors, status=400)
|
||||
|
||||
|
||||
def inject_params(
|
||||
handler, parse_func_signature: Callable[[Callable], Iterable[AbstractInjector]]
|
||||
@@ -89,11 +130,7 @@ def inject_params(
|
||||
if self.request.app['raise_validation_errors']:
|
||||
raise
|
||||
else:
|
||||
errors = error.errors()
|
||||
for error in errors:
|
||||
error["in"] = injector.context
|
||||
|
||||
return json_response(data=errors, status=400)
|
||||
return await self.on_validation_error(error, injector.context)
|
||||
|
||||
return await handler(self, *args, **kwargs)
|
||||
|
||||
@@ -109,3 +146,14 @@ def is_pydantic_view(obj) -> bool:
|
||||
return issubclass(obj, PydanticView)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
__all__ = (
|
||||
"AbstractInjector",
|
||||
"BodyGetter",
|
||||
"HeadersGetter",
|
||||
"MatchInfoGetter",
|
||||
"QueryGetter",
|
||||
"CONTEXT",
|
||||
"Group",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user