Tags
Note
This post is a thought. It's a short note that I make about someone else's content online. Learn more about the process thoughts
Here's my thought on π fastapi decorators
I've been using these decorators to modify the behavior of specific routes. It will do things like 404 admin only routes in a way that looks just like fastapi's default, or only allow certain roles into the route, or redirect unauthenticated users to login.
After listening to yesterday's syntaxfm I'm now really thinking about middleware and the benefits it might have. middleware would make it easy to apply things like admin to an entire admin router, so you wont forget it on any one admin route. It will look cleaner as the admin checker is only applied once per router, not once per route.
import inspect import time from functools import wraps from inspect import signature from fastapi import Request from fastapi.responses import FileResponse, JSONResponse, RedirectResponse from starlette import status from fokais.config import get_config from fokais.models.user import Role config = get_config() admin_routes = [] authenticated_routes = [] not_cached_routes = [] cached_routes = [] def not_found(request): hx_request_header = request.headers.get("hx-request") user_agent = request.headers.get("user-agent", "").lower() if "mozilla" in user_agent or "webkit" in user_agent or hx_request_header: return config.templates.TemplateResponse( "error.html", {"status_code": 404, "detail": "Not Found", "request": request}, status_code=404 ) else: return JSONResponse( content={ "status_code": 404, "detail": "Not Found", }, status_code=404, ) def no_cache(func): not_cached_routes.append(f"{func.__module__}.{func.__name__}") @wraps(func) async def wrapper(*args, request: Request, **kwargs): # my_header will be now available in decorator if "request" in signature(func).parameters: kwargs["request"] = request if inspect.iscoroutinefunction(func): response = await func(*args, **kwargs) else: response = func(*args, **kwargs) response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "0" return response return wrapper def cache(max_age=86400): def inner_wrapper(func): cached_routes.append(f"{func.__module__}.{func.__name__}") @wraps(func) async def wrapper(*args, request: Request, **kwargs): if "request" in signature(func).parameters: kwargs["request"] = request if inspect.iscoroutinefunction(func): response = await func(*args, **kwargs) else: response = func(*args, **kwargs) response.headers[ "Cache-Control" ] = f"public, max-age={max_age}, stale-while-revalidate=31536000, stale-if-error=31536000" response.headers["Expires"] = f"{int(time.time()) + max_age}" return response return wrapper return inner_wrapper def admin_only(func): admin_routes.append(f"{func.__module__}.{func.__name__}") @wraps(func) async def wrapper(*args, request: Request, **kwargs): if request.state.user is None: return not_found(request) if request.state.user.role != Role.admin: return not_found(request) if "request" in signature(func).parameters: kwargs["request"] = request if inspect.iscoroutinefunction(func): response = await func(*args, **kwargs) else: response = func(*args, **kwargs) return response return wrapper def authenticated(roles=[Role.user, Role.admin], redirect_to="get_login"): def inner_wrapper(func): authenticated_routes.append(f"{func.__module__}.{func.__name__}") @wraps(func) async def wrapper(*args, request: Request, **kwargs): if request.state.user is None: return RedirectResponse( url=request.url_for(redirect_to, source=request.url), status_code=status.HTTP_302_FOUND ) if request.state.user.role not in roles: return not_found(request) if "request" in signature(func).parameters: kwargs["request"] = request if inspect.iscoroutinefunction(func): response = await func(*args, **kwargs) else: response = func(*args, **kwargs) return response return wrapper return inner_wrapper default_data = {} def defaults(data=default_data): def inner_wrapper(func): default_data[f"{func.__module__}.{func.__name__}"] = data @wraps(func) async def wrapper(*args, request: Request, **kwargs): if "request" in signature(func).parameters: kwargs["request"] = request if inspect.iscoroutinefunction(func): response = await func(*args, **kwargs) else: response = func(*args, **kwargs) return response return wrapper return inner_wrapper
This post was a thought by Waylon Walker see all my thoughts at https://waylonwalker.com/thoughts