There are many template engines available for Python already; Mako, Tenjin, Evoque, Cheetah, etc.
Unfortunately, many of these do not support Py3k, and of the ones that do, none of them were building their template objects the way I felt it should be done: by using python’s AST [abstract syntax tree] objects directly.
So, I wrote Suba.
Any of the template engines worth their salt use the text template to create a python program, then compile that program to byte code. The byte code is typically cached, and exec() is used to run it for each rendering request. Outside this central theme are a few variations; some of the systems generate a module (instead of just a script) and then import it rather than exec(), some cache to disk, some to memory, etc.; but, none of them create an AST.
The difference in using the AST is that there is no initial text stage; the language objects are instatiated directly.
For instance, the following template in Suba:
Hello, %(name)!
Would produce the following AST tree (some arguments omitted for brevity):
FunctionDef(name='execute', ..., body=[ Expr(value=Yield(value=Str(s='Hello, '))), Expr(value=Yield(value=Name(id='name')), Expr(value=Yield(value=Str(s='!')))])
When combined with a post-process walk of the AST, this allows you to tune and manipulate the code generated from a template with precision that you can’t get when using code-strings. (As an aside: the python-savvy have noticed that all templates become a generator, the wiki has some discussion about why this works well).
For instance, suppose you want to filter whitespace from the output. We could wrap the output in a whitespace filter,or, we can walk the AST tree and edit all the Str() nodes before the code even compiles. So, when we cache the compiled bytecode, it doesn’t even contain the instructions that would emit the whitespace, meaning that we get this additional feature along with a reduction in run-time. This is something that would be very difficult to do quickly and cleanly on a code-string using regex and string.replace().
This produces faster templates than the fastest competitor libraries (at the moment, the pool of Python 3.0 supporters is small enough for this to be true).
Here is an example, using Tenjin’s own benchmark templates and data:
tenjin_test: 2864.37 pages/sec (in 3.49 seconds) suba_test: 5801.79 pages/sec (in 1.72 seconds)