Sunday, April 18, 2010

templating engine cookoff

There are more Python string templating engines in the world than homemade chili recipes. Which is tastiest? Let's have a cookoff!

We all have different criteria, of course. The only organized comparisons I've seen between them are based mostly on measuring speed of rendering. Huh? I have never said to myself, "Wow, I'm wasting so much time because these templating engines aren't rendering fast enough. If only they would finish in 0.4 seconds each instead of 0.6!"

I have said to myself, "Wow, I'm wasting so much time because there are errors in my templates, and my templating engine isn't helping me find them." Writing templates is tricky. Errors are common. So, for me, clear error messages are the key virtue. My cookoff focuses on recognizing the engines that report the filename and the offending string when a rendering failure occurs. It also displays a typical simple syntax for writing and using each type of template (so it can serve as a cheatsheet as well).

My results? So far, only jinja2 and genshi report both filename and the error-causing string. The jinja2 error stack is more concise overall, so I award it the prize by a nose.

I'm really happy with the framework I wrote for doing the cookoff, too, so I posted it on Launchpad. It is really, really easy to add new engines for comparison - feel free to suggest or submit them! In fact, I hope that others can adapt my cookoff framework to other situations that call for side-by-side package comparisons.

Incidentally, why are comparisons like this usually styled "smackdowns" or "shootouts"? What's up with that? We're not trying to put anybody in the hospital! We're just trying to select our favorite from a banquet of delicious offerings. Eat up!

13 comments:

jafo said...

You're right that we shouldn't be calling them smackdowns. Just in the wrong direction. We should be calling them deathmatches. We aren't looking to put anyone in the hospital, we're looking to put them in the ground. :-)

In actuality, I'm more of a pacifist than you might think from the above. The cookoff name is a good one.

Sean

mike bayer said...

You didn't read the Mako docs carefully. Here's information on the various options for handling exceptions:

http://www.makotemplates.org/docs/usage.html#usage_handling

Using that, we can produce this script:

http://paste.pocoo.org/show/203341/

which produces this pretty reasonable (IMHO) error traceback, including filename, and exact line of original code which failed:

http://paste.pocoo.org/show/203340/

Mako is about acting as much like Python as possible, so the usage of undefined variable names resolves to the global "Undefined" object, which you can test for using "x is Undefined". Simple and unsurprising.

Max said...

I would like to add the web2py template engine. How do I do it?
"""
The answer to the Unltimate Question is {{=everything['universe'].answer}}.
"""

The view would be

from gluon.template import render
der run():
return render('filename.html',dict(...))

On error it will raise an exception of class gluon.restricted.RestrictedError which contains, the traceback, the file that caused the error and Python code generated from the template.

Tarek said...

For the Python new formatter, you are passing a string, so it should be "N/A" in the "Error-causing Filename reported" imho :) (and for some other too)

Catherine said...

Sean, you need to ease up on those Untouchables marathons. :)

Mike, when I'm comparing error messages, I'm literally comparing error messages. "What extra error-related information can programmers also access, if they write module-specific error handlers to trap and query it?" is, in my opinion, a separate question - so I'm attaching new columns to the table for your suggested error handler and its results. I can gradually fill those columns in for the other engines, too, as I research up details of where other engines store further data on error conditions.

Max, I added Gluon, but this error (referencing variables that do not exist) does not throw gluon.restricted.RestrictedError instances, but plain-vanilla NameErrors with no extra data. Otherwise I'd put a special error handler in the new columns for it.

Tarek, the point is simply that, if you want information on the source file in your errors, you'll have to write custom error handlers to report it yourself - as you can do for any templating engine. That's the same situation as for most of the templating engines. Granted, because Python strings don't have any implied link to a file of origin, it's hard to imagine how they could reference that file, but the point is the effect - like most of the templating engines surveyed, they don't handle this for you.

mike bayer said...

here is the shorthand version of Mako error trapper, I should have given you this one to start with:

http://paste.pocoo.org/show/203626/

output:

http://paste.pocoo.org/show/203627/

Marius Gedminas said...

I *love* how Pylons (which uses Mako by default) handles template errors: it not only gives you the template filename and line number, but also shows a small snippet of the template source right there in the error page.

Here's a screenshot.

Sarah Elkins said...

Corrected link for Marius' screenshot: http://mg.pov.lt/lovely-pylons-template-errors.png

Ian Bicking said...

I'd love to see a little more syntax in the standard display -- with just variable substitution they all look almost identical. Maybe a simple for loop.

Inheritance would be even more interesting, but I don't think it's easy to normalize across all template languages in that case. Or maybe it would be... they are mostly pretty similar.

dartdog said...

how could you leave Django out?

Catherine said...

Ian: I should be able to add looping demos... but not till May. There's a presentation I need to work on for now.

Michael Watkins said...

Could you check in the missing text files? mako.txt, tempita.txt, perhaps others...

stev-a said...

Great article, good debugging is essential.