Motivation

Some friends and I often eat in the mensa at our faculty. Now eating together is nicer than alone, but coordinating this is hard especially with different and varying schedules. But I wouldn’t be genuine CS students if I didn’t try to solve this problem with technology… so I wrote a Matrix Chat Bot using maubot that posts the day’s menu, you can reply when you’ll eat and see when others will eat.

A chat message listing the day's canteen menu and poll to coordinate the canteen visit.
A formatted menu with responses in a Matrix Chat

To display the menu we need the format some data as Markdown to send it as a message. From working with Django I have some experience with it’s templating language. Jinja is a standalone Python templating library that has very similar and functionality and syntax to Django’s, so its seems a great fit.

The only problem in the way is loading the templates. In Django you have a folder with the templates. The problem is that maubot plugins are run from an archive. Imports are resolved dynamically by a custom importer. This means that there is no file system folder to load templates from Jinja. The other loadersexterner Link are also not helpfull here. (The DictLoaderexterner Link would of course work, but I would like to have the templates in separate files.)

The solution

Write a custom Jinja loader that loads the templates from the archive for us. To get started simply copy the loader.py file this repoexterner Link into your project. This enables us to use Jinja as one would otherwhere. Just place the templates in a templates folder in any of the plugins modules1 and you’re ready to go:


# loader is the `BasePluginLoader` from the `Plugin` instance
loader = make_jinja_loader(loader)
environment = Environment(
    loader=loader,
)

dayplan = environment.get_template("menu.jinja")
print(dayplan.render(menu=some_data))

Some details

There is not much magic behind curtain. maubot plugins are instances of the maubot.Plugin class. A Plugin has an instance variable loader of type maubot.loader.BasePluginLoader. It can be used to read files from the archive and list folders in the archive. With the two methods we can write our custom loader. Dervive jinja2.BaseLoaderexterner Link and implement the required method.

make_jinja_loadermake_jinja_loader(loader: BasePluginLoader, extra_paths: [Path] = []) is a helper functions that creates a Jinja Loader that exposes templates from

  • the templates module1 in all modules declared under modules in the config
  • any extra paths you define

For technical reasons (being maubot does not include plain folders in the plugin archive) the templates folders have to be python modules with a (possibly empty) __init__.py.


  1. It has to be a module, not a folder. So just add a __init__.py. It has to be a module to be included in the plugin archive. ↩︎ ↩︎