Skip to main content

Async Support

Python Liquid supports loading and rendering templates asynchronously. When BoundTemplate.render_async() is awaited, {% render %} and {% include %} tags will use Environment.get_template_async(), which delegates to get_source_async() of the configured template loader.

import asyncio
from liquid import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("templates/"))

async def coro():
template = await env.get_template_async("index.html")
return await template.render_async(you="World")

result = asyncio.run(coro())

Async Loaders

Custom template loaders should implement get_source_async() and pass a coroutine as the uptodate argument to TemplateSource.

See AsyncDatabaseLoader for an example that loads templates from a PostgreSQL database asynchronously.

Async Drops

Custom drops can implement __getitem_async__(). If an instance of a drop that implements __getitem_async__() appears in a render_async() context, __getitem_async__() will be awaited instead of calling __getitem__().

Most likely used for lazy loading objects from a database, an async drop could look something like this.

class AsyncCollection(abc.Mapping):
def __init__(self, val):
self.keys = ["products"]
self.cached_products = []

def __len__(self):
return 1

def __iter__(self):
return iter(self["products"])

async def __aiter__(self):
# Note that Liquid's built-in `for` loop does not yet support async iteration.
return iter(self.__getitem_async__("products"))

def __getitem__(self, k):
if not self.cached_products:
# Blocking IO here
self.cached_products = get_stuff_from_database()
return self.cache_products

async def __getitem_async__(self, k):
if not self.cached_products:
# Do async IO here.
self.cached_products = await get_stuff_from_database_async()
return self.cache_products