IPython Custom Cell Magic for Rendering Jinja2 Templates

After watching some of the PyCon 2013 videos on IPython, I felt inspired, as always, to play once more with IPython. Since I had just recently learned to use Jinja2, I thought it would be cool if I could test some jinja2 template rendering in the IPython notebook.

According to a post on the IPython mailing list, unfortunately, jinja2 rendering is not supported in the markdown cells (which would be really neat) and probably will not be, because it is too Python specific. This means I am restricted rendering input cells and displaying the result in output cells.

It would be simple enough to just import jinja2 and render a string, but I wanted to make it a little nicer, so I looked up the documentation on defining your own magic functions. Turns out that it's pretty simple. There's an example in the IPython docs that I used as a starting point to create the following class.

In [5]:
from IPython import display
from IPython.core.magic import register_cell_magic, Magics, magics_class, cell_magic
import jinja2

class JinjaMagics(Magics):
    '''Magics class containing the jinja2 magic and state'''
    def __init__(self, shell):
        super(JinjaMagics, self).__init__(shell)
        # create a jinja2 environment to use for rendering
        # this can be modified for desired effects (ie: using different variable syntax)
        self.env = jinja2.Environment(loader=jinja2.FileSystemLoader('.'))
        # possible output types
        self.display_functions = dict(html=display.HTML, 

    def jinja(self, line, cell):
        jinja2 cell magic function.  Contents of cell are rendered by jinja2, and 
        the line can be used to specify output type.

        ie: "%%jinja html" will return the rendered cell wrapped in an HTML object.
        f = self.display_functions.get(line.lower().strip(), display.display)
        tmp = self.env.from_string(cell)
        rend = tmp.render(dict((k,v) for (k,v) in self.shell.user_ns.items() 
                                        if not k.startswith('_') and k not in self.shell.user_ns_hidden))
        return f(rend)
ip = get_ipython()

The class creates a simple jinja2 environment with the FileSystemLoader, so template files can be imported/extended, and defines a function used to register a cell tagged with the cellmagic "%% jinja <output>". The output specifier is optional, and will return the rendered text wrapped in one of IPython's rich display objects

The local (non-hidden) namespace is used for rendering, so any variables or functions defined in the IPython notebook can be accessed.

In [6]:
names = ['alice','bob']

Here is an example of rendering a simple HTML template and displaying it with the HTML object:

In [7]:
%%jinja html
<title>{{ title }}</title>
{% for name in names %}

Hello {{ name }} <br/>

{% endfor %} 
Hello alice
Hello bob

A string with no template synax will simply be passed through as-is to the display object. The following will produce the same as using the %%latex cellmagic, but there are no built-in cellmagics for the other display objects.

In [8]:
%%jinja latex

\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\
\nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0 
\begin{eqnarray} \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\ \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\ \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\ \nabla \cdot \vec{\mathbf{B}} & = 0 \end{eqnarray}

The following is an example of using the %%jinja magic to generate and render some latex:

In [9]:
vars = {'rho':5,'alpha':6,'pi':3.14,'phi':1.618,'hbar':'6.582121 \cdot 10^{-16} eV\cdot s'}
In [10]:
%%jinja latex


{% for k,v in vars.iteritems() %}
\{{ k }} & = {{ v }} \\
{% endfor %}

\begin{eqnarray} \alpha & = 6 \\ \phi & = 1.618 \\ \pi & = 3.14 \\ \rho & = 5 \\ \hbar & = 6.582121 \cdot 10^{-16} eV\cdot s \\ \end{eqnarray}

I honestly thought it would be more difficult to extend IPython with my own magic function, but the IPython devs really know what they're doing. It has come a long long way since the first time I used it back around version 0.9, when it was simply an enhanced, interactive python terminal. What it has become now is pretty amazing.