mirror of
https://github.com/mikeckennedy/fastapi-chameleon.git
synced 2025-08-16 04:10:38 +08:00

Retained my changes. May need to rerun any linting that may have been reverted in exceptions.py, but functionality should now be retained. Signed-off-by: Ayzee Patton <vlpatton@vlpatton.gay>
147 lines
5.4 KiB
Python
147 lines
5.4 KiB
Python
import inspect
|
|
import os
|
|
from functools import wraps
|
|
from typing import Optional, Union, Callable
|
|
|
|
import fastapi
|
|
from chameleon import PageTemplateLoader, PageTemplate
|
|
|
|
from fastapi_chameleon.exceptions import (
|
|
FastAPIChameleonException,
|
|
FastAPIChameleonGenericException,
|
|
FastAPIChameleonNotFoundException,
|
|
)
|
|
|
|
__templates: Optional[PageTemplateLoader] = None
|
|
template_path: Optional[str] = None
|
|
|
|
|
|
def global_init(template_folder: str, auto_reload=False, cache_init=True):
|
|
global __templates, template_path
|
|
|
|
if __templates and cache_init:
|
|
return
|
|
|
|
if not template_folder:
|
|
msg = 'The template_folder must be specified.'
|
|
raise FastAPIChameleonException(msg)
|
|
|
|
if not os.path.isdir(template_folder):
|
|
msg = f"The specified template folder must be a folder, it's not: {template_folder}"
|
|
raise FastAPIChameleonException(msg)
|
|
|
|
template_path = template_folder
|
|
__templates = PageTemplateLoader(template_folder, auto_reload=auto_reload)
|
|
|
|
|
|
def clear():
|
|
global __templates, template_path
|
|
__templates = None
|
|
template_path = None
|
|
|
|
|
|
def render(template_file: str, **template_data: dict) -> str:
|
|
if not __templates:
|
|
raise FastAPIChameleonException('You must call global_init() before rendering templates.')
|
|
|
|
page: PageTemplate = __templates[template_file]
|
|
return page.render(encoding='utf-8', **template_data)
|
|
|
|
|
|
def response(template_file: str, mimetype='text/html', status_code=200, **template_data) -> fastapi.Response:
|
|
html = render(template_file, **template_data)
|
|
return fastapi.Response(content=html, media_type=mimetype, status_code=status_code)
|
|
|
|
|
|
def template(template_file: Optional[Union[Callable, str]] = None, mimetype: str = 'text/html'):
|
|
"""
|
|
Decorate a FastAPI view method to render an HTML response.
|
|
|
|
:param str template_file: Optional, the Chameleon template file (path relative to template folder, *.pt).
|
|
:param str mimetype: The mimetype response (defaults to text/html).
|
|
:return: Decorator to be consumed by FastAPI
|
|
"""
|
|
|
|
wrapped_function = None
|
|
if callable(template_file):
|
|
wrapped_function = template_file
|
|
template_file = None
|
|
|
|
def response_inner(f):
|
|
nonlocal template_file
|
|
global template_path
|
|
|
|
if not template_path:
|
|
template_path = 'templates'
|
|
# raise FastAPIChameleonException("Cannot continue: fastapi_chameleon.global_init() has not been called.")
|
|
|
|
if not template_file:
|
|
# Use the default naming scheme: template_folder/module_name/function_name.pt
|
|
module = f.__module__
|
|
if '.' in module:
|
|
module = module.split('.')[-1]
|
|
view = f.__name__
|
|
template_file = f'{module}/{view}.html'
|
|
|
|
if not os.path.exists(os.path.join(template_path, template_file)):
|
|
template_file = f'{module}/{view}.pt'
|
|
|
|
@wraps(f)
|
|
def sync_view_method(*args, **kwargs):
|
|
try:
|
|
response_val = f(*args, **kwargs)
|
|
return __render_response(template_file, response_val, mimetype)
|
|
except FastAPIChameleonNotFoundException as nfe:
|
|
return __render_response(nfe.template_file, {}, 'text/html', 404)
|
|
except FastAPIChameleonGenericException as nfe:
|
|
template_data = nfe.template_data if not None else {}
|
|
return __render_response(nfe.template_file, template_data, 'text/html', nfe.status_code)
|
|
|
|
@wraps(f)
|
|
async def async_view_method(*args, **kwargs):
|
|
try:
|
|
response_val = await f(*args, **kwargs)
|
|
return __render_response(template_file, response_val, mimetype)
|
|
except FastAPIChameleonNotFoundException as nfe:
|
|
return __render_response(nfe.template_file, {}, 'text/html', 404)
|
|
except FastAPIChameleonGenericException as nfe:
|
|
template_data = nfe.template_data if not None else {}
|
|
return __render_response(nfe.template_file, template_data, 'text/html', nfe.status_code)
|
|
|
|
if inspect.iscoroutinefunction(f):
|
|
return async_view_method
|
|
else:
|
|
return sync_view_method
|
|
|
|
return response_inner(wrapped_function) if wrapped_function else response_inner
|
|
|
|
|
|
def __render_response(template_file, response_val, mimetype, status_code: int = 200) -> fastapi.Response:
|
|
# source skip: assign-if-exp
|
|
if isinstance(response_val, fastapi.Response):
|
|
return response_val
|
|
|
|
if template_file and not isinstance(response_val, dict):
|
|
msg = f'Invalid return type {type(response_val)}, we expected a dict or fastapi.Response as the return value.'
|
|
raise FastAPIChameleonException(msg)
|
|
|
|
model = response_val
|
|
|
|
html = render(template_file, **model)
|
|
return fastapi.Response(content=html, media_type=mimetype, status_code=status_code)
|
|
|
|
|
|
def not_found(four04template_file: str = 'errors/404.pt'):
|
|
msg = 'The URL resulted in a 404 response.'
|
|
|
|
if four04template_file and four04template_file.strip():
|
|
raise FastAPIChameleonNotFoundException(msg, four04template_file)
|
|
else:
|
|
raise FastAPIChameleonNotFoundException(msg)
|
|
|
|
|
|
def generic_error(template_file: str, status_code: int, template_data: Optional[dict] = None):
|
|
msg = 'The URL resulted in an error.'
|
|
|
|
raise FastAPIChameleonGenericException(template_file, status_code, msg, template_data=template_data)
|