mirror of
https://github.com/DarrylNixon/ghostforge
synced 2024-04-22 06:27:20 -07:00
94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
from functools import wraps
|
|
from inspect import _ParameterKind
|
|
from inspect import Parameter
|
|
from inspect import signature
|
|
from typing import Callable
|
|
|
|
from fastapi import Request
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.engine.row import Row
|
|
|
|
from ghostforge.templates import templates
|
|
|
|
# Adapted from original at https://github.com/acmpo6ou/fastapi_html_json/blob/master/html_json.py
|
|
|
|
|
|
class HtmlJson:
|
|
def __init__(self):
|
|
"""
|
|
Provides @html_or_json decorator, see its documentation for more info.
|
|
:param templates_dir: directory containing jinja2 templates.
|
|
"""
|
|
pass
|
|
|
|
@staticmethod
|
|
def add_request_param(wrapper: Callable, f: Callable):
|
|
"""
|
|
Adds `request` parameter to signature of wrapper if it's not there already.
|
|
:param f: decorated function.
|
|
"""
|
|
sig = signature(f)
|
|
if "request" in sig.parameters:
|
|
return
|
|
|
|
params = list(sig.parameters.values())
|
|
request_param = Parameter("request", _ParameterKind.POSITIONAL_OR_KEYWORD, annotation=Request)
|
|
params.append(request_param)
|
|
wrapper.__signature__ = sig.replace(parameters=params)
|
|
|
|
def render_template(self, template: str, request: Request, result):
|
|
"""
|
|
Renders jinja2 template no matter what the view function returns: be it dictionary,
|
|
pydantic model or a list.
|
|
|
|
:param template: path to the template.
|
|
:param request: needed by TemplateResponse.
|
|
:param result: return value of the view function.
|
|
:param breadcrumbs: breadcrumbs for HTML rendering.
|
|
:return: rendered template.
|
|
"""
|
|
if isinstance(result, BaseModel):
|
|
result = result.dict()
|
|
elif isinstance(result, list):
|
|
result = {"data": result[:]}
|
|
elif isinstance(result, Row):
|
|
tmp = {}
|
|
for k in result.keys():
|
|
tmp[k] = result[k]
|
|
result = tmp
|
|
|
|
if hasattr(request.state, "ghostforge"):
|
|
for key, value in request.state.ghostforge.items():
|
|
result[key] = value
|
|
|
|
result.update({"request": request})
|
|
return templates.TemplateResponse(template, result)
|
|
|
|
def html_or_json(self, template: str):
|
|
"""
|
|
A decorator that will make decorated async view function able to return jinja2 template
|
|
or json depending on Accept header of request.
|
|
:param template: path to jinja2 template.
|
|
"""
|
|
|
|
def decorator(f: Callable):
|
|
@wraps(f)
|
|
async def wrapper(*args, **kwargs):
|
|
request = kwargs["request"]
|
|
|
|
try:
|
|
result = await f(*args, **kwargs)
|
|
except TypeError:
|
|
kwargs.pop("request")
|
|
result = await f(*args, **kwargs)
|
|
|
|
accept = request.headers["accept"].split(",")[0]
|
|
if accept == "text/html":
|
|
return self.render_template(template, request, result)
|
|
|
|
return result
|
|
|
|
self.add_request_param(wrapper, f)
|
|
return wrapper
|
|
|
|
return decorator
|