# from https://github.com/mikeckennedy/fastapi-chameleon import inspect import os from functools import wraps from typing import Optional from starlette.responses import HTMLResponse from chameleon import PageTemplateLoader, PageTemplate class ChameleonException(Exception): pass __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 ChameleonException(msg) if not os.path.isdir(template_folder): msg = f"The specified template folder must be a folder, it's not: {template_folder}" raise ChameleonException(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 Exception( "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 HTMLResponse( content=html, media_type=mimetype, status_code=status_code ) def template(template_file: str, mimetype: str = "text/html"): """ Decorate a FastAPI view method to render an HTML response. :param str template_file: 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 """ if not template_file: raise ChameleonException("You must specify a template file.") def response_inner(f): @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): # sourcery skip: assign-if-exp if isinstance(response_val, HTMLResponse): 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 Exception(msg) model = response_val html = render(template_file, **model) return HTMLResponse(content=html, media_type=mimetype)