Skip to content

Instantly share code, notes, and snippets.

@le717
Last active November 17, 2023 21:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save le717/c87e87eb8419c6ba82a8c18c3e635f8f to your computer and use it in GitHub Desktop.
Save le717/c87e87eb8419c6ba82a8c18c3e635f8f to your computer and use it in GitHub Desktop.
"""Transparent app/non-app context Jinja rendering setup.
Use this module whenever you need to render Jinja templates outside
the app context or in code that is shared between app/non-app contexts,
such as in Celery tasks.
If available, the app context renderer will be used. Otherwise, a mostly
identical Jinja render environment will be created, with the caveat that
it may not be as efficient and will not support all Flask-provided
convenience rendering features.
All user information and app context is unavailable in the standalone renderer.
However, a static files only `url_for` implementation is provided.
Source: https://gist.github.com/le717/c87e87eb8419c6ba82a8c18c3e635f8f
"""
from pathlib import PurePath
from typing import Any
from flask import current_app, render_template as frt, url_for as fuf
from jinja2 import Environment, Template, PackageLoader, select_autoescape
from src.core.config import get_app_config, get_flask_config
from src.core.filters import ALL_FILTERS
from src.core.filters.context import ALL_MIDDLEWARE
__all__ = ["render_template", "url_for"]
# We have an existing context, use it
if current_app:
render_template = frt
url_for = fuf
# Build up our standalone renderer
else:
JINJA_ENV = Environment(
loader=PackageLoader("src", "templates"),
autoescape=select_autoescape(),
)
# Register any custom filters
for name, f in ALL_FILTERS.items():
JINJA_ENV.filters[name] = f
# Register the context methods
JINJA_ENV.globals.update(ALL_MIDDLEWARE)
def jrt(template_name_or_list: str | Template, **context: Any) -> str:
"""Render a Jinja template."""
template = JINJA_ENV.get_template(template_name_or_list)
return template.render(**context)
def jur(
endpoint: str,
*,
_anchor: str | None = None,
_method: str | None = None,
_scheme: str | None = None,
_external: bool | None = None,
**values: Any,
) -> str:
"""Generate a URL to the given statIc file."""
# The `_method` param doesn't work in this context
if _method:
raise NotImplementedError(
"Standalone `url_for` does not support the `_method` parameter."
)
# We only support static files
endpoint = endpoint.lower()
if endpoint.lower() != "static":
raise NotImplementedError(
"Standalone `url_for` only supports static files."
)
# We must a have file name to work with
if "filename" not in values:
raise KeyError("A `filename` parameter and path must be provided.")
# If we're not given a URL scheme, try to get it from the config
if _scheme is None:
try:
_scheme = get_flask_config("PREFERRED_URL_SCHEME")
# Should a preferred scheme not be in the config,
# default to the Flask default scheme
# https://flask.palletsprojects.com/en/2.2.x/config/#PREFERRED_URL_SCHEME
except KeyError:
_scheme = "http"
# Build up the relative URL to this static file, making sure
# to prepend the leading slash
final_url = (PurePath("static") / values.pop("filename")).as_posix()
final_url = f"/{final_url}"
# If any values remaining, convert them into a query string.
# Yes, I know supporting `values` and `_anchor` doesn't make sense
# if only static files are supported, but I wanted to do it anyway
if values:
qs = "&".join(f"{k}={v}" for k, v in values.items())
final_url = f"{final_url}?{qs}"
# Add the URL anchor if it was provided
if _anchor:
final_url = f"{final_url}#{_anchor}"
# As the very last step, check if this is supposed to be an external URL
# and prefix the URL scheme and app domain
if _external:
url_domain = get_app_config("APP_DOMAIN")
final_url = f"{_scheme}://{url_domain}{final_url}"
return final_url
# Export our standalone render
render_template = jrt # type: ignore
url_for = jur # type: ignore
# Be sure to register our `url_for` as a context method, too
JINJA_ENV.globals["url_for"] = jur
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment