Read my book

I wrote books about Webpack and React. Check them out!

Thursday, May 30, 2013

How to Write a Simple Templating Engine in Python?

I have covered the concept of templating earlier on this blog. To recap the idea is simple. You have a template which contains "slots". It is possible to inject content into these "slots". You end up with a string that contains that new content injected to your template. Simple as that.

There are many applications for this basic technique ranging from web development to document generation.

Existing Templating Solutions

Python comes with a few templating mechanisms already. I've listed a few common ones below:
  • '%d bottles of beer on the wall' % 50 # the old way
  • '{amount} bottles of beer on the wall'.format(amount=50) # the new way
  • '{amount} bottles of beer on the wall'.format(**{'amount': 50}) # unpack dictionary (handy trick)
  • string.Template('$amount bottles of beer on the wall').format(amount=50) # the verbose way
  • string.Template('$amount bottles of beer on the wall').format({'amount': 50}) # no need to unpack. See Template Strings for more information
As you might guess, there are a lot of templating engines available for Python. In my case using one seemed a bit overkill and I ended up coming up with a simple pattern that goes beyond what Python provides by default.

Simple Templating Engine

In my case I wanted to use a recursive dictionary structure that contains the context. This is something I construct based on a collection of JSON files. This task is very simple thanks to Python's json module. Unfortunately the tools above break down when trying to access recursive data.

After thinking about it for a while, the solution wasn't that difficult. It did mean I had to use a regular expression but nothing too shady. You can find my rendering function below:

import re


def render(tpl, context):
    """Substitute text in <> with corresponding variable value."""
    regex = re.compile('\<([a-zA-Z.]+)\>', re.MULTILINE)

    def repl(m):
        group = m.group(1)
        parts = group.split('.')
        value = context

        for part in parts:
            try:
                value = value[part]
                value = str(value) if isinstance(value, int) else value
            except KeyError:
                value = ''

        return value

    return regex.sub(repl, tpl)


print(render('<amount> bottles of <bottle.label>', {
    'amount': 10,
    'bottle': {
        'label': 'Amarillo'
    }
}))

The solution above takes a template and fields (a dictionary) as its parameters and returns a rendered template. There is some error handling in place and it deals with a recursive context.

Conclusion

I think the example highlights the usefulness of regular expressions. The expression doesn't look too bad. If you investigate it further, you noticed I've used a group to extract the data out of a match. It's a handy trick. You can even extract multiple groups in the same time.

There are likely other ways to achieve the same result but this one seemed to fit my purposes just fine. This is a tiny bit of an invoice generator we've been working on at our co-op. We're trying to replace our Google Drive based solution with something nicer and programmatic. So far it looks pretty good and might make a cool web service later on even!