Using Jinja2 with Sanic

Jinja 2.9+ has async support built right in, so now you can handle templates without needing any specially made packages.

Here is a simple way to use jinja2 in a Sanic app.

First, I built a couple utility functions that can be reused throughout the handlers: util/templating.py

async def _build_env():
    project_path = '/path/to/project'
    templates_dir = 'templates'

    package_loader = PackageLoader(project_path, templates_dir, encoding='utf-8')
    environment = Environment(loader=package_loader, enable_async=True)
    environment.filters["usd"] = lambda x: "${:,.2f} USD".format(x)
    environment.filters["datefmt"] = lambda x: x.strftime('%Y-%m-%d')
    return environment

async def render_template(request, file_name, **kwargs):
    environment = await _build_environment()
    template = environment.get_template(file_name)

    context = {
        'web_wrapper': 'web_wrapper.html',
        'request': request,
        'session': request.ctx.session
    }
    rendered_template = await template.render_async(context, **kwargs)
    return rendered_template

There are a few (very small) things to do to take Jinja2 asynchronous:

  • When instantiating Environment, specify enable_async=True
  • When rendering a loaded template, use template.render_async()
  • and of course, use async def and await appropriately

That’s all there is to it. For completeness, here’s how to use it from within a handler:

blueprint = Blueprint('public')

@blueprint.route("/")
async def index(request):
    template = await render_template("/index.html", request, my_test_arg="This is a test!")
    return HTTPResponse(template, content_type='text/html')

web_wrapper.html

<html>
 <head><title>{{ page_title }}</title></head>
 <body>{% block content %}{% endblock %}</body>
</html>

index.html

{% extends web_wrapper %}
{% set page_title = "Async Jinja2 w/ Sanic Example" %}
{% block content %}
    This is the index! {{ my_test_arg }}</body></html>
{% endblock %}

(I provide this code w/o any guarantees that it will work on your project. I tried to make it as simple as possible so it can be modified to fit your needs.)

(Also, please feel free to copy this example and use it on any blogs or w/e else. I created this because it seemed like there was a shortage of information on Jinja2’s async capability, so PLEASE share this around if you’re inclined to do so.)

I think you’ll be just fine with non-async Jinja2, just by using more Sanic workers. The template processing is only CPU-bound with no I/O, and thus there is little reason to use async, except in single-worker programs where the async API allows utilising multiple CPUs. This could also be of some use if you have templates that take a long time (dozens of milliseconds) to render because they could then under heavy load block other requests for a while even when using e.g. twice the number of workers to actual CPU count.

What’s the benefit of async with Jinja? Loading of templates? I’m not sure I see the overarching need for it.

Nonetheless,thanks for sharing the code. :sunglasses:

Interesting. I’m admittedly pretty new to async dev. I was under the impression that anything IO related would be blocking. This being realized, is there any reason Sanic couldn’t use the standard WTForms?

That is correct. More to the point anything could be made into async. But you will only gain performance if the thing is io bound as you are potentially saving the time the server is waiting on a response. So i imagine the fetching of files by jinja would fall into this category. It would be an interesting test, but I imagine it is not a huge impact.

As for wtforms, yes you could use the regular version. There was a sanic version at some point. Not sure if it’s status. https://github.com/mekicha/awesome-sanic