Source code for piccolo_api.crud.validators

from __future__ import annotations

import functools
import inspect
import typing as t

from piccolo.utils.sync import run_sync
from starlette.exceptions import HTTPException
from starlette.requests import Request

if t.TYPE_CHECKING:  # pragma: no cover
    from .endpoints import PiccoloCRUD


ValidatorFunction = t.Callable[
    ["PiccoloCRUD", Request], t.Union[t.Coroutine, None]
]


[docs] class Validators: """ These validators are run by the corresponding method on :class:`PiccoloCRUD`. The validator function is given the ``PiccoloCRUD`` instance, and the Starlette ``Request`` instance, and should raise a Starlette ``HTTPException`` if there is a problem. Async functions are also supported. Here are some examples: .. code-block:: python def validator_1(piccolo_crud: PiccoloCRUD, request: Request): if not request.user.user.superuser: raise HTTPException( status_code=403, "Only a superuser can do this" ) async def validator_2(piccolo_crud: PiccoloCRUD, request: Request): if not await my_check_user_function(request.user.user): raise HTTPException( status_code=403, "The user can't do this." ) """ def __init__( self, every: t.List[ValidatorFunction] = [], get_single: t.List[ValidatorFunction] = [], put_single: t.List[ValidatorFunction] = [], patch_single: t.List[ValidatorFunction] = [], delete_single: t.List[ValidatorFunction] = [], post_single: t.List[ValidatorFunction] = [], get_all: t.List[ValidatorFunction] = [], delete_all: t.List[ValidatorFunction] = [], get_references: t.List[ValidatorFunction] = [], get_ids: t.List[ValidatorFunction] = [], get_new: t.List[ValidatorFunction] = [], get_schema: t.List[ValidatorFunction] = [], get_count: t.List[ValidatorFunction] = [], extra_context: t.Dict[str, t.Any] = {}, ): self.every = every self.get_single = get_single self.put_single = put_single self.patch_single = patch_single self.delete_single = delete_single self.post_single = post_single self.get_all = get_all self.delete_all = delete_all self.get_references = get_references self.get_ids = get_ids self.get_new = get_new self.get_schema = get_schema self.get_count = get_count self.extra_context = extra_context
def apply_validators(function): """ A decorator used to apply validators to the corresponding methods on :class:`PiccoloCRUD`. """ async def run_validators(*args, **kwargs) -> None: piccolo_crud: PiccoloCRUD = args[0] validators = piccolo_crud.validators if validators is None: return request = kwargs.get("request") or next( (i for i in args if isinstance(i, Request)), None ) validator_functions = ( getattr(validators, function.__name__) + validators.every ) if validator_functions and request: for validator_function in validator_functions: try: if inspect.iscoroutinefunction(validator_function): await validator_function( request=request, piccolo_crud=piccolo_crud, **validators.extra_context, ) else: validator_function( request=request, piccolo_crud=piccolo_crud, **validators.extra_context, ) except HTTPException as exception: raise exception except Exception: raise HTTPException( status_code=400, detail="Validation error" ) if inspect.iscoroutinefunction(function): @functools.wraps(function) async def inner_coroutine_function(*args, **kwargs): await run_validators(*args, **kwargs) return await function(*args, **kwargs) return inner_coroutine_function else: @functools.wraps(function) def inner_function(*args, **kwargs): run_sync(run_validators(*args, **kwargs)) return function(*args, **kwargs) return inner_function