mirror of
https://github.com/mikeckennedy/fastapi-chameleon.git
synced 2025-08-16 04:10:38 +08:00
106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
import inspect
|
|
import os
|
|
from functools import wraps
|
|
from typing import Optional
|
|
|
|
import fastapi
|
|
from chameleon import PageTemplateLoader, PageTemplate
|
|
|
|
from fastapi_chameleon.exceptions import FastAPIChameleonException
|
|
|
|
__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 = f'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):
|
|
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):
|
|
html = render(template_file, **template_data)
|
|
return fastapi.Response(content=html, media_type=mimetype, status_code=status_code)
|
|
|
|
|
|
def template(template_file: Optional[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
|
|
"""
|
|
|
|
def response_inner(f):
|
|
nonlocal template_file
|
|
|
|
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):
|
|
response_val = f(*args, **kwargs)
|
|
return __render_response(template_file, response_val, mimetype)
|
|
|
|
@wraps(f)
|
|
async def async_view_method(*args, **kwargs):
|
|
response_val = await f(*args, **kwargs)
|
|
return __render_response(template_file, response_val, mimetype)
|
|
|
|
if inspect.iscoroutinefunction(f):
|
|
return async_view_method
|
|
else:
|
|
return sync_view_method
|
|
|
|
return response_inner
|
|
|
|
|
|
def __render_response(template_file, response_val, mimetype):
|
|
# 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)
|