Python templating engine cookoff

A side-by-side comparison of Python templating engines, with an emphasis on error message quality

See blog post for more details.

For the success column, templates are rendered by passing the following:

    class Universe(object):
        answer = 42
    universe = Universe()
    data = {'weapons': ('fear', 'surprise', 'ruthless efficiency'),
            'answer': 42,
            'universe': universe,
            'everything': {'universe': universe}}  
    

The failure column is induced by passing an empty dictionary of data instead, to see how helpful the engine's error stack is. Look for the green highlight in the final column, demonstrating that the engine reports the filename and position of the problem location in the template file (unless it doesn't).

Generated by code hosted at https://code.launchpad.net/~catherine-devlin/templatecookoff/trunk

cheetah v. 2.4.2.1
The answer to the Ultimate
Question is ${everything['universe'].answer}.

Among our weapons are: #for $weapon in $weapons
  $weapon              #end for
        def demo(self, vars):
            self.template_name = 'cheetah.txt'
            template = Cheetah.Template.Template(
                file=self.template_name, namespaces=[vars])
            return str(template)
The answer to the Ultimate
Question is 42.

Among our weapons are: 
  fear              
  surprise              
  ruthless efficiency              
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 272, in demo
    return str(template)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Cheetah-2.4.2.1-py2.6-linux-x86_64.egg/Cheetah/Template.py", line 1005, in __str__
    rc = getattr(self, mainMethName)()
  File "_home_cat_proj_cookoff_cheetah_txt.py", line 87, in respond
NotFound: cannot find 'everything'
cherrytemplate v. 1.0.0
The answer to the Ultimate
Question is <py-eval="str(everything['universe'].answer)">

Among our weapons are: <py-for="weapon in weapons">
  <py-eval="weapon">   </py-for>
        def demo(self, vars):
            self.template_name = 'cherrytemplate.txt'
            return cherrytemplate.renderTemplate(file=self.template_name, loc=vars)
The answer to the Ultimate
Question is 42

Among our weapons are: 
  fear   
  surprise   
  ruthless efficiency   
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 224, in demo
    return cherrytemplate.renderTemplate(file=self.template_name, loc=vars)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/cherrytemplate/cherrytemplate.py", line 407, in renderTemplate
    result = ''.join(list(result))
  File "<string>", line 5, in _renderTemplate
NameError: global name 'everything' is not defined
django v. 1.2.1
The answer to the Ultimate
Question is {{everything.universe.answer}}.

Among our weapons are: {% for weapon in weapons %}
  {{weapon}}           {% endfor %}
        def demo(self, vars):
            try:
                settings.configure(DEBUG=True, TEMPLATE_DEBUG=True,
                                   TEMPLATE_DIRS=(os.path.dirname(__file__),))
            except RuntimeError:
                pass # would normally ``settings.configure()`` at start of file
            self.template_name = 'django.txt'
            return render_to_string(self.template_name, vars)    
The answer to the Ultimate
Question is 42.

Among our weapons are: 
  fear           
  surprise           
  ruthless efficiency           
The answer to the Ultimate
Question is .

Among our weapons are: 
genshi v. 0.6
<html xmlns:py="http://genshi.edgewall.org/">
<p>The answer to the Ultimate
Question is <span py:replace="everything['universe'].answer">?</span>.</p>

Among our weapons are:
  <ul>
    <li py:for="weapon in weapons"><span py:content="weapon">weapon</span>
    </li>
  </ul>
</html>
        def demo_html(self, vars):
            self.template_name = 'genshi.html'
            template = genshi.template.MarkupTemplate(
                open(self.template_name), filename=self.template_name)
            stream = template.generate(**vars)
            return stream.render()
<html>
<p>The answer to the Ultimate
Question is 42.</p>
Among our weapons are:
  <ul>
    <li><span>fear</span>
    </li><li><span>surprise</span>
    </li><li><span>ruthless efficiency</span>
    </li>
  </ul>
</html>
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 156, in demo_html
    return stream.render()
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/core.py", line 183, in render
    return encode(generator, method=method, encoding=encoding, out=out)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/output.py", line 57, in encode
    return _encode(''.join(list(iterator)))
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/output.py", line 223, in __call__
    for kind, data, pos in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/output.py", line 670, in __call__
    for kind, data, pos in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/output.py", line 771, in __call__
    for kind, data, pos in chain(stream, [(None, None, None)]):
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/output.py", line 586, in __call__
    for ev in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/core.py", line 288, in _ensure
    for event in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/base.py", line 605, in _include
    for event in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/markup.py", line 327, in _match
    for event in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/base.py", line 565, in _flatten
    result = _eval_expr(data, ctxt, vars)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/base.py", line 277, in _eval_expr
    retval = expr.evaluate(ctxt)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/eval.py", line 178, in evaluate
    return eval(self.code, _globals, {'__data__': data})
  File "genshi.html", line 3, in <Expression u"everything['universe'].answer">
    Question is <span py:replace="everything['universe'].answer">?</span>.</p>
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/eval.py", line 309, in lookup_name
    val = cls.undefined(name)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/eval.py", line 410, in undefined
    raise UndefinedError(key, owner=owner)
UndefinedError: "everything" not defined
 
The answer to the Ultimate
Question is ${everything['universe'].answer}.

Among our weapons are: {% for weapon in weapons %}
  $weapon              {% end %}

        def demo_txt(self, vars):
            self.template_name = 'genshi.txt'
            template = genshi.template.NewTextTemplate(
                open(self.template_name), filename=self.template_name)
            stream = template.generate(**vars)
            return stream.render()
 
The answer to the Ultimate
Question is 42.

Among our weapons are: 
  fear              
  surprise              
  ruthless efficiency              

Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 162, in demo_txt
    return stream.render()
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/core.py", line 183, in render
    return encode(generator, method=method, encoding=encoding, out=out)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/output.py", line 57, in encode
    return _encode(''.join(list(iterator)))
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/output.py", line 569, in __call__
    for event in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/core.py", line 288, in _ensure
    for event in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/base.py", line 605, in _include
    for event in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/base.py", line 565, in _flatten
    result = _eval_expr(data, ctxt, vars)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/base.py", line 277, in _eval_expr
    retval = expr.evaluate(ctxt)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/eval.py", line 178, in evaluate
    return eval(self.code, _globals, {'__data__': data})
  File "genshi.txt", line 3, in <Expression u"everything['universe'].answer">
    Question is ${everything['universe'].answer}.
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/eval.py", line 309, in lookup_name
    val = cls.undefined(name)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg/genshi/template/eval.py", line 410, in undefined
    raise UndefinedError(key, owner=owner)
UndefinedError: "everything" not defined
gluon v. ?
The answer to the Ultimate
Question is {{=everything['universe'].answer}}.

Among our weapons are: {{ for weapon in weapons: }}
  {{=weapon}} {{ pass }}
        def demo(self, vars):
            self.template_name = 'gluon.txt'
            content = open(self.template_name).read()
            return gluon.template.render(content, filename=self.template_name, context=vars)
The answer to the Ultimate
Question is 42.

Among our weapons are: 
  fear 
  surprise 
  ruthless efficiency 
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 175, in demo
    return gluon.template.render(content, filename=self.template_name, context=vars)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/gluon/template.py", line 224, in render
    exec(parse_template(stream,path=path)) in context
  File "<string>", line 2, in <module>
NameError: name 'everything' is not defined
jinja2 v. 2.4.1
The answer to the Ultimate
Question is {{ everything['universe'].answer }}.

Among our weapons are: {% for weapon in weapons %}
  {{ weapon }}         {% endfor %}
        def demo(self, vars):
            self.template_name = 'jinja2.txt'
            env = jinja2.Environment(loader=jinja2.FileSystemLoader('.'))    
            return env.get_template(self.template_name).render(vars)            
The answer to the Ultimate
Question is 42.

Among our weapons are: 
  fear         
  surprise         
  ruthless efficiency         
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 237, in demo
    return env.get_template(self.template_name).render(vars)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Jinja2-2.4.1-py2.6.egg/jinja2/environment.py", line 868, in render
    return self.environment.handle_exception(exc_info, True)
  File "./jinja2.txt", line 2, in top-level template code
    Question is {{ everything['universe'].answer }}.
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Jinja2-2.4.1-py2.6.egg/jinja2/environment.py", line 345, in getitem
    return obj[argument]
UndefinedError: 'everything' is undefined
kid v. 0.9.6
<?xml version='1.0' encoding='utf-8'?>
<html xmlns="http://www.w3.org/1999/xhtml" 
 xmlns:py="http://purl.org/kid/ns#">
The answer to the Ultimate
Question is ${everything['universe'].answer}.

Among our weapons are:
  <ul>
    <li py:for="weapon in weapons"><span py:content="weapon">weapon</span>
    </li>
  </ul>
</html>
        def demo_scalar(self, vars):
            self.template_name = 'kid.txt'
            return str(kid.Template(self.template_name, **vars))
<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
The answer to the Ultimate
Question is 42.
Among our weapons are:
  <ul>
    <li><span>fear</span>
    </li><li><span>surprise</span>
    </li><li><span>ruthless efficiency</span>
    </li>
  </ul>
</html>
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 140, in demo_scalar
    return str(kid.Template(self.template_name, **vars))
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/__init__.py", line 333, in __str__
    return self.serialize()
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/__init__.py", line 301, in serialize
    raise_template_error(module=self.__module__)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/__init__.py", line 299, in serialize
    return serializer.serialize(self, encoding, fragment, format)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/serialization.py", line 107, in serialize
    text = ''.join(self.generate(stream, encoding, fragment, format))
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/serialization.py", line 342, in generate
    for ev, item in self.apply_filters(stream, format):
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/serialization.py", line 165, in format_stream
    for ev, item in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/parser.py", line 221, in _coalesce
    for ev, item in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/parser.py", line 179, in _track
    for p in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/filter.py", line 26, in apply_matches
    for ev, item in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/parser.py", line 179, in _track
    for p in stream:
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/kid-0.9.6-py2.6.egg/kid/parser.py", line 221, in _coalesce
    for ev, item in stream:
  File "/home/cat/proj/cookoff/kid.py", line 33, in _pull
NameError: name 'everything' is not defined
Error location in template file '/home/cat/proj/cookoff/kid.txt'
between line 2 and line 8, column 2:
<html xmlns="http://www.w3.org/1999/xhtml"
...
Among our weapons are:
mako v. 0.3.2
The answer to the Ultimate
Question is ${everything['universe'].answer}.

Among our weapons are: 
  % for weapon in weapons:
  ${weapon}              
  % endfor
        def demo(self, vars):
            self.template_name = 'mako.txt'
            template = mako.template.Template(filename=self.template_name)
            return template.render(**vars)
The answer to the Ultimate
Question is 42.

Among our weapons are: 
  fear              
  surprise              
  ruthless efficiency              
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 192, in demo
    return template.render(**vars)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Mako-0.3.2-py2.6.egg/mako/template.py", line 189, in render
    return runtime._render(self, self.callable_, args, data)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Mako-0.3.2-py2.6.egg/mako/runtime.py", line 403, in _render
    _render_context(template, callable_, context, *args, **_kwargs_for_callable(callable_, data))
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Mako-0.3.2-py2.6.egg/mako/runtime.py", line 434, in _render_context
    _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Mako-0.3.2-py2.6.egg/mako/runtime.py", line 457, in _exec_template
    callable_(context, *args, **kwargs)
  File "mako_txt", line 24, in render_body
TypeError: 'Undefined' object is unsubscriptable
The answer to the Ultimate
Question is ${everything['universe'].answer}.

Among our weapons are: 
  % for weapon in weapons:
  ${weapon}              
  % endfor
        def demo_with_error_handler(self, vars):
            self.template_name = 'mako.txt'
            try:
                template = mako.template.Template(filename=self.template_name)
                return template.render(**vars)
            except Exception:
                return mako.exceptions.text_error_template().render()
The answer to the Ultimate
Question is 42.

Among our weapons are: 
  fear              
  surprise              
  ruthless efficiency              


Traceback (most recent call last):
  File "cookoff.py", line 197, in demo_with_error_handler
    return template.render(**vars)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Mako-0.3.2-py2.6.egg/mako/template.py", line 189, in render
    return runtime._render(self, self.callable_, args, data)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Mako-0.3.2-py2.6.egg/mako/runtime.py", line 403, in _render
    _render_context(template, callable_, context, *args, **_kwargs_for_callable(callable_, data))
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Mako-0.3.2-py2.6.egg/mako/runtime.py", line 434, in _render_context
    _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)
  File "/home/cat/.virtualenvs/cookoff/lib/python2.6/site-packages/Mako-0.3.2-py2.6.egg/mako/runtime.py", line 457, in _exec_template
    callable_(context, *args, **kwargs)
  File "mako.txt", line 2, in render_body
    Question is ${everything['universe'].answer}.
TypeError: 'Undefined' object is unsubscriptable
python3_string_format v. 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) [GCC 4.4.3]
The answer to the Ultimate
Question is {universe.answer}.

I don't know how to loop.
    def demo(self, vars):
        self.template_name = 'py3str.txt'
        txt = open(self.template_name).read()
        return txt.format(**vars)
The answer to the Ultimate
Question is 42.

I don't know how to loop.
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 291, in demo
    return txt.format(**vars)
KeyError: 'universe'
standard_library_string_template v. 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) [GCC 4.4.3]
The answer to the Ultimate
Question is $answer.

I don't know how to loop.
    def demo(self, vars):
        self.template_name = 'stdlib.txt'
        template = string.Template(open(self.template_name).read())
        return template.substitute(**vars)
The answer to the Ultimate
Question is 42.

I don't know how to loop.
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 283, in demo
    return template.substitute(**vars)
  File "/usr/lib/python2.6/string.py", line 172, in substitute
    return self.pattern.sub(convert, self.template)
  File "/usr/lib/python2.6/string.py", line 162, in convert
    val = mapping[named]
KeyError: 'answer'
tempita v. ?
The answer to the Ultimate
Question is {{ everything['universe'].answer }}.

Among our weapons are:
{{for weapon in weapons}}
  {{weapon}}
{{endfor}}
        def demo(self, vars):
            self.template_name = 'tempita.txt'
            template = tempita.Template.from_filename(self.template_name)
            return template.substitute(**vars)
The answer to the Ultimate
Question is 42.

Among our weapons are:
  fear
  surprise
  ruthless efficiency
Traceback (most recent call last):
  File "cookoff.py", line 70, in method_result
    result = method(data)
  File "cookoff.py", line 212, in demo
    return template.substitute(**vars)
  File "build/bdist.linux-x86_64/egg/tempita/__init__.py", line 154, in substitute
    result, defs, inherit = self._interpret(ns)
  File "build/bdist.linux-x86_64/egg/tempita/__init__.py", line 165, in _interpret
    self._interpret_codes(self._parsed, ns, out=parts, defs=defs)
  File "build/bdist.linux-x86_64/egg/tempita/__init__.py", line 193, in _interpret_codes
    self._interpret_code(item, ns, out, defs)
  File "build/bdist.linux-x86_64/egg/tempita/__init__.py", line 213, in _interpret_code
    base = self._eval(parts[0], ns, pos)
  File "build/bdist.linux-x86_64/egg/tempita/__init__.py", line 275, in _eval
    value = eval(code, self.default_namespace, ns)
  File "<string>", line 1, in <module>
NameError: name 'everything' is not defined at line 2 column 15 in file tempita.txt