A template engine for people who enjoy the simpler things in life.

Version 3.0.0

Template API

Template Objects

A template starts life as a simple string containing template markup, usually stored in the form of a text file. This string is used to initialize a Template object:

>>> import ibis
>>> template = ibis.Template('{{foo}} and {{bar}}')

Internally, a lexer transforms the string into a sequence of tokens. A parser then takes this sequence and compiles it into a tree of nodes. Each node has a .render() method which takes a context object and returns a string. The entire compiled node tree can be rendered by calling .render() on the root node.

Compiling and rendering the node tree are two distinct operations. The template only needs to be compiled once; it can then be cached and rendered multiple times with different context objects.

The Template class acts as the public interface to the template engine. This is the only class the end-user needs to interact with directly. A Template object is initialized with a template string. It compiles the string and stores the resulting node tree for future rendering. Calling the template object's .render() method with a dictionary of key-value pairs renders the template and returns the result as a string.

>>> template.render({'foo': 'ham', 'bar': 'eggs'})
'ham and eggs'

>>> template.render({'foo': 1, 'bar': 2})
'1 and 2'

Template IDs

The Template constructor takes an optional template_id argument, an arbitrary string which is used to identify the template in error messages.

template = ibis.Template(template_string, "template.txt")

If you've loaded the template from a file, the filename is the logical choice to use.

Template Loaders

If you want to use template inheritance or the {% include %} tag you'll need to specify a template loader so Ibis can locate the appropriate templates to include or extend. A template loader is a callable that accepts a filename argument and either returns a corresponding Template object or raises a TemplateLoadError exception.

You can define your own custom template loader, but in most cases an instance of the builtin FileLoader class will be suitable. A FileLoader instance is initialized with a path to a base template directory:

loader = ibis.loaders.FileLoader('/path/to/base/dir')

You can call the loader object with a filename argument which it interprets as a path to a UTF-8 encoded template file stored in the base directory:

template = loader('template.txt')

A FileLoader instance compiles its templates once and caches them in memory for future lookups. A FileReloader instance is similar but will automatically reload and recompile a template if the underlying template file changes.

To specify a template loader set ibis.loader to an instance of your callable:

ibis.loader = ibis.loaders.FileLoader('/path/to/base/dir')

The Undefined Type

An instance of the ibis.context.Undefined type is returned whenever an attempt to resolve a variable name against a particular context fails. Undefined is a null type that renders as an empty string, evaluates as the boolean False, behaves like an empty sequence, etc.

You can check if a variable is defined using the boolean is_defined filter:

{% if foo.bar|is_defined %}
    do stuff with {{ foo.bar }}
{% endif %}

You can specify a fallback value for undefined variables using the if_undefined filter

{{ foo.bar|if_undefined("baz") }}

In this case if the variable foo.bar is not defined in the current context, the fallback value "baz" will be used in its place.

Strict Mode

You can render a template in strict mode by adding a strict_mode=True keyword argument to the .render() method:

output = template.render(data, strict_mode=True)

In strict mode attempting to resolve an undefined variable will raise an UndefinedVariable exception instead of returning an instance of the Undefined type.

The is_defined and if_undefined filters won't work in strict mode (the exception will already have been raised before the filter is called) but you can use the is_defined() function to check if a variable is defined:

{% if is_defined("post.title") %}
    .. do something with {{ post.title }} ...
{% endif %}


The following built-in variables and functions are available in all contexts:


The following exception types may be raised by the template engine: