Source code for aioli.controller.decorators
from functools import wraps
from starlette.requests import Request
from starlette.responses import Response
from aioli.exceptions import AioliException
from aioli.utils import jsonify
from .consts import Method, RequestProp
from .registry import Handler
[docs]def route(path, method, description=None):
"""Prepares route registration, and performs handler injection.
:param path: Handler path, relative to application and unit paths
:param method: HTTP Method
:param description: Endpoint description
:return: Route handler
"""
def wrapper(fn):
@wraps(fn)
async def handler_fn(*args, **kwargs):
return await fn(*args, **kwargs)
if not isinstance(method, Method):
raise AioliException(
f"Invalid HTTP method supplied in @route for handler: {handler_fn}. "
f"Must be of type: {Method.__module__}.{Method.__name__}"
)
handler = Handler(handler_fn)
# Adds the handler for registration once the loop is ready.
handler.register_route(path, method.value, description)
return handler_fn
return wrapper
[docs]def takes(props=None, **schemas):
"""Takes a list of schemas used to validate and transform parts of a request object.
The selected parts are injected into the route handler as arguments.
:param props: List of `Pluck` targets
:param schemas: list of schemas (kwargs)
:return: Route handler
"""
header = schemas.get("header")
body = schemas.get("body")
path = schemas.get("path")
query = schemas.get("query")
def wrapper(fn):
@wraps(fn)
async def handler_fn(*args, **kwargs):
args_new = list(args)
request = kwargs["request"] if "request" in kwargs else args_new.pop(1)
for prop in props or []:
value = RequestProp(prop).value
assert "." in value
target = request
for member in value.split("."):
target = getattr(target, member)
kwargs.update({RequestProp(prop).name: target})
if header:
kwargs.update({"header": header().load(request.headers)})
if path:
kwargs.update(path().load(request.path_params))
if body:
payload = await request.json()
kwargs.update({"body": body().load(payload)})
if query:
kwargs.update({"query": query().load(request.query_params)})
return await fn(*args_new, **kwargs)
handler = Handler(handler_fn)
# Add the provided schemas to the RouteStack
handler.schemas.from_dict(**schemas)
return handler_fn
return wrapper
[docs]def returns(schema_cls=None, status=200, many=False):
"""Returns a transformed and serialized Response
:param schema_cls: Marshmallow.Schema class
:param status: Return status (on success)
:param many: Whether to return a list or single object
:return: Response
"""
schema = schema_cls(many=many) if schema_cls else None
def wrapper(fn):
@wraps(fn)
async def handler_fn(unit, *args, **kwargs):
args_new = list(args)
# Remove `Request` object from args (in case it wasn't consumed by an `input_local`).
if len(args) > 1 and isinstance(args[1], Request):
args_new.pop(1)
rv = await fn(unit, *args_new, **kwargs)
indent = 4 if unit.app.config["pretty_json"] else 0
if not schema:
return jsonify(rv, status, indent=indent)
data = (
schema.dumps(rv, indent=indent, ensure_ascii=False).encode("utf8")
if schema
else rv
)
# Return HTTP encoded JSON response
return Response(
content=data,
status_code=status,
headers={"content-type": "application/json"},
)
handler = Handler(handler_fn)
# Add the `response` schema to this handler
handler.schemas.response = schema_cls
handler.status = status
return handler_fn
return wrapper