.. -*- coding: utf-8- ================ Template Engines ================ :Author: Roland Koebler (rk *at* simple-is-better *dot* org) :Website: http://www.simple-is-better.org/template/ :Date: 2009-01-07 .. meta:: :description: Concept of template-engines :keywords: template-engine, template-engines, template processor, template, templates, model-view, pyratemp, overview, comparison, benchmark, benchmarks .. contents:: **Table of Contents** :backlinks: none .. sectnum:: Please don't hesitate to `contact`_ me if you have any comments/suggestions/etc. Introduction ============ Template engines are tools to separate program-logic and presentation into two independent parts [#]_. (see i.e. articles in `wikipedia-en`_, `wikipedia-de`_) This makes the development of both logic and presentation easier, improves flexibility and eases modification and maintenance. .. _wikipedia-en: http://en.wikipedia.org/wiki/Template_processor .. _wikipedia-de: http://de.wikipedia.org/wiki/Template_Engine .. _contact: mailto: rk at simple-is-better dot org .. [#] By the way: Splitting things into **independent parts** is **always** a **very good** idea (although unfortunately mostly neglected). Concept ======= Model-View separation --------------------- So, with a template engine the programmer should *separate the presentation (=view) from the logic (=model)*. It is **very important** to **strictly separate** these two parts! Otherwise these parts won't be independent anymore, and you will lose all advantages of a template engine! Unfortunately, the template engine itself cannot completely enforce this separation without some serious restrictions on the view (see `below`__). And -- at least in my opinion -- the template-engine should not restrict the design of the view or even make some designs impossible. Since the separation cannot be done automatically, here are some **rules**: - | **Always cleanly separate model and view**. | Don't put parts of the view into the model, don't put parts of the model into the view. Not even temporarily. - | **Always push the data** from the model to the view. | Accessing/modifying the model from the view is forbidden. Pulling data is forbidden. | So, the model may only provide a bunch of values, and the view then uses these values. - All **data** from the model **must be display-/layout-independent**. Display/layout-information in the data is forbidden. - Make all **computations** of the model **in the model**. - The **view** must only perform **computations for view-specific** and model-independent "things". __ `Why if/for/calculations etc. are necessary in the View`_ Different approaches -------------------- But in reality, there are different kinds of template-engines, with different approaches. I'll group them into 4 categories (although many of the existing template-engines are somewhere between these categories): 1. **substitution only**: The template only contains placeholders which are replaced by data. No loops, no conditions etc. are possible in the template. This is the simplest but also least powerful kind of templates. Examples: ``printf``-formatstring, python's ``string.Template``, `Templayer`_. It should be clear that this is **insufficient for most cases**. 2. **substitution + if-defined + loop-over-data + recursive macros**: The template can contain: - placeholders (for string-substitution) - if-defined-conditionals (to test presence/absence of some data) - foreach-data-loops (for multi-valued data/lists) - recursive macros (to walk recursive data-structures) This is powerful enough for some websites. And it cuts down the power of the template, to prevent that anything which belongs to the model can be done in the template/view. This approach is described in detail in an interesting paper, named `Enforcing Strict Model-View Separation in Template Engines`_. Although I don't agree to many conclusions of the author (and think that these templates are definitely not powerful enough), it may be worth reading. **BUT**: This approach puts some serious restrictions on the view: Some designs simply can't be done (see `Why if/for/...`_) -- or can only be done if you put parts of the presentation into the model code, which is a no-go. **This often makes this approach unusable.** Examples: `StringTemplate`_ (the template of the above paper), ... 3. **substitution + conditionals + loops + macros + "embedded expressions" with restricted access**: In addition to the above, the template may also contain "free" loops, tests of variable-values, and has "expressions of a embedded language" (e.g. for calculations, formatting etc.). But this "embedded language" can only access a restricted set of variables/functions; it especially cannot access or modify the model. This is **powerful enough for all cases**. And it prevents direct access and modification of the model in the template. It can't prevent that tests, calculations etc. which belong to the model are done in the template -- this is in the responsibility of the programmer/designer. But that's the price for a full flexible, unrestricted view. Examples: unfortunately not many, but e.g. my `pyratemp`_ 4. **unrestricted templates, i.e. unrestricted embedded code**: Many template-engines simply include a full-powered programming language without any restrictions into the template. But this has the drawback that even the model can be modified in the template, which can **completely countermine the model-view-separation**. Examples: `Mako`_, `Cheetah`_, `TemplateToolkit`_, `EmPy`_, ... .. _Enforcing Strict Model-View Separation in Template Engines: http://www.cs.usfca.edu/~parrt/papers/mvc.templates.pdf .. _Why if/for/...: `Why if/for/calculations etc. are necessary in the View`_ Why if/for/calculations etc. are necessary in the View ------------------------------------------------------ There are many people who think that the "category 2" above is sufficient. Unfortunately, this is not the case. There are a lot of things which clearly belong to the view, but are not possible without variable-testing, for-loops, calculations, formatting etc. This can be demonstrated best with a few examples. Note that these examples are not constructed, but really occurred in existing templates: - "free" for-loops: - Imagine a fixed form, with e.g. a table which should always have 10 rows, no matter how much data exists. If there is less data, the table will have some empty rows. If there is more data, there will be 2 (or more) tables. This probably isn't needed very often in websites, but definitely occurs in fixed formats, e.g. in LaTeX. - Or simply take an enumerated list like this: == ===== .. Value == ===== 1 one 2 two 3 three 4 four == ===== The enumeration and enumeration-style here belongs to the view. (Note: This is possible in some "category-2-template-engines", but only due to some "magic-variables" like loop-counters. But it then only works for "1,2,3,..." and not for "10,20,30,..." or "I,II,III,...".) - formatting: - How should a date look like? "``CCYY-MM-DD``" or "``DD. Month CCYY``" or "``MM/DD/YY``"? - How many digits of a float should be displayed? One output may e.g. require 2, an other 3. Some other needs a "+" in front of a positive number... - calculations: Imagine a bill (e.g. in LaTeX) with a variable number of items, which may be broken into several sheets of paper. The calculation of a subtotal at the end of each page clearly belongs to the view. All these examples *clearly belong to the view*, but are not possible with the pure "category 2" approach. And these are only some examples -- there are a lot more... This is why such "category 2"-templates often get extended bit by bit, until they end in "category 3 (or 4)", but then have an inelegant concept. What a template-engine needs ---------------------------- Here's a short description of how a template-engine should look like in my opinion: - stand-alone (i.e. work without a webserver) - | result-independent: able to create any kind of textual data, e.g. HTML/XML (both static and dynamic), LaTeX, PostScript, email, ... | (Contrary to most XML-template-engines, which can only create XML-documents.) - simple, small, lightweight, easy to use, well-documented and extensible - clear template syntax ("There should be one -- and preferably only one -- obvious way to do it." [#]_) - completely Unicode - good syntax-error-checking and good error-messages, to make the writing and debugging of templates easy - fast - | secure, so an "untrusted template" cannot compromise your system. | (think of a sandbox, prevention of infinite loops, etc.) - basic functionality from "category 3" above): - placeholders/string-substitution - conditionals - loops - macros (with parameters) - restricted "embedded expressions" (formatting, arithmetics etc.) - additional functionality: - inclusion of other templates (for reusability and "things" which are used in several templates) - escaping-mechanism (e.g. for html: escape ``< > & ' "``) - (MAYBE) definition of the result-encoding - | complain about undefined/non-existing variables in the template by default | (Most template-engines silently ignore undefined variables in the template. But silently ignoring errors is bad, and wrong data is *much* worse than no data.) Since unfortunately I didn't find any existing template-engine which fitted these needs, I created an own template-engine which accomplishes most of the above: `pyratemp`_ .. [#] from: `The Zen of Python`_ .. _`The Zen of Python`: http://www.python.org/dev/peps/pep-0020/ Benchmarks ========== How *fast* is a template-engine? That's a difficult question, and the answer always depends on the used benchmark, since a benchmark can't tell us how fast a template-engine is, but only how fast *one specific implementation of one specific template* is. So, don't rely too much on benchmarks. Additionally, the speed of a template-engine normally isn't a problem -- except if the template-engine is *really* slow. Here are some benchmarks: - benchsimple: This is a simple template-benchmark, which originally was posted on a python-forum, and then modified and extended by myself. It creates a single html-page with a table with 50 entries. - A bench-suite of `Mako`_ or `evoque`_, which were adapted from one included with `Genshi`_. But note that this benchmark **only measures the pure rendering-time**, and completely omits the time needed to parse/compile/... the template! Benchsimple ----------- "Benchsimple" creates a `simple html-page`_ with a navigation-bar and a table with 50 entries (5 rows * 10 columns). It only uses a few template-features that are supported by all templates (escaped substitution, conditionals, loops), and so there's no Unicode, no included templates, no arithmetics, no template-defined formatting etc. | You can download the complete sourcecode: ``_ | This code can be used (a) for benchmarking and (b) for comparing how different template-engines look like. I've tested `pyratemp`_, `cubictemp`_, `Jinja`_, `Cheetah`_, `Mako`_, `EmPy`_, `evoque`_ and `SimpleTAL`_, and also compared them to manually written code. But note that the following "results" can only give you a *hint* how fast a specific template-engine might be. Results: =================== =========== =============== ======= ============ =========== =========== Template-Engine Version correct result import **complete** parse only render only =================== =========== =============== ======= ============ =========== =========== Cheetah 2.0rc7 yes 0.0028 **0.84** 0.054 0.70 cubictemp 0.4 yes 0.0012 **2.0** -- 1.8 cubictemp [#py2.5]_ 2.0 yes 0.0013 **2.0** 0.72 1.2 pyratemp 0.1 yes 0.0011 **2.3** 1.0 1.2 evoque [#py2.5]_ 0.3 yes 0.0030 **3.6** 1.0 2.0 SimpleTAL 4.1-6 no [#]_ 0.0060 **7.2** 2.5 4.7 EmPy 3.3-6 yes 0.0012 **8.8** -- -- Jinja 0.7 partly [#]_ 0.0029 **13.2** 10.2 (2.9) Mako 0.1.8-1 yes 0.0030 **25.0** 23.8 0.74 =================== =========== =============== ======= ============ =========== =========== .. _simple html-page: bench.simple.html .. [#py2.5] tested with Python 2.5 .. [#] SimpleTAL: The result had different whitespace. Although this is probably not a problem for generating HTML, it is a problem for other documents. .. [#] Jinja produces two different results if it is rendered several times, because ``{% cycle ...%}`` alternately uses 'class=row1' and 'class=row2' for the first row. Notes: - run on a AMD Athlon 64 3200+, 2.0 GHz, Debian Etch - Python 2.4 (except evoque and cubictemp-2.0, which require Python 2.5) - all times are in "ms" - the times are pure "template-execution"-times without the time needed to start the Python-interpreter, load the files and compile the code - "import": time to import the module - "complete": complete time to parse and render the template - "parse only": time to parse (+maybe compile) a template - "render only": time to re-render an already parsed/compiled template after it has been rendered the first time I also wrote some python-code, which also creates the same page, but without a template-engine. This shows how fast the template-engines are compared to manually written Python-code. Additionally, I modified the code to optionally don't escape special characters, to use the variables in the substitutions directly without eval() and to use psyco. This shows which part takes how much time. =================== ======= =========== ======= =============== ======= .. eval() escaping psyco correct result time =================== ======= =========== ======= =============== ======= python hand-coded yes yes -- yes 0.53 python hand-coded yes yes yes yes 0.34 ------------------- ------- ----------- ------- --------------- ------- python hand-coded no yes -- (yes) 0.38 python hand-coded no yes yes (yes) 0.18 python hand-coded yes no -- no 0.32 python hand-coded yes no yes no 0.27 python hand-coded no no -- no 0.18 python hand-coded no no yes no 0.12 =================== ======= =========== ======= =============== ======= Existing Template Engines ========================= There are *very many* template-engines, and every day someone "invents" another... Overview -------- | Here is an quick comparison of some template-engines, including nearly *all* template-engines for python I found (some time ago). | Other overviews of template-engines for python can be found at: - ``_ - ``_ - ``_ - ``_ (in German) - ``_ (in German) Additionally, I've created the same html-page using different template-engines in `Benchsimple`_, so look there to get a first impression how these template-engines look like. Since I haven't tested every template-engine in detail, some of the fields below are empty, and I probably haven't found some hidden features of some template-engines. So, please tell me if anything below is wrong. .. Note: use non-breaking-spaces (\u00A0, vim: iu00a0) to prevent line-wraps below =================== =========== =========== ======= =============== =========== =========== =========== ======= =========== =========== =========== =============== =================== =========== ======= ======= =================== =============== =============== =========================== ======================= =================== =================== =================== =============== ================ Template-Engine code features syntax ------------------------------- ----------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- name version language size lines-of-code license `category`_ XML-based Unicode error-msg. escaping undef. vars embedded code embedded expr. sandbox macros include variable access substitution escaped conditionals loops macros include inheritance ... block-end =================== =========== =========== ======= =============== =========== =========== =========== ======= =========== =========== =========== =============== =================== =========== ======= ======= =================== =============== =============== =========================== ======================= =================== =================== =================== =============== ================ `pyratemp`_ 0.1.2 Python 42 kB 486 MIT-like 3 no |+|| |+|| |+|| complain no restricted Python |+|| |+|| |+|| v, v[i], v["key"] $!...!$ @!...!@ | | via include + macro (optionally | | replace) | ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `cubictemp`_ 0.4 Python 8 kB 120 MIT-like 4 no |-|| |-|| |+|| (HTML) complain no Python |-|| |+|| |-|| v, v[i], v["key"] $!...!$ @!...!@ @!if .. then .. else ..!@ |-|| |-|| ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `cubictemp`_ 2.0 Python 2.5 9 kB 185 MIT-like 4 no |-|| |-|| |+|| (HTML) complain no Python |-|| |+|| |-|| v, v[i], v["key"] $!...!$ @!...!@ @!.. if .. else ..!@ |-|| |-|| ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `Mako`_ 0.1.8-1 Python 148 kB 2530 MIT-like 4 no |+|| |o|| |+|| complain Python Python |-|| |+|| |+|| v, v[i], v["key"] ${...} ${...|x} | % if .. % for .. in ..: <%def ..> <%include ../> <%inherit ../> % end.., | % elif .. | % else ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `Cheetah`_ 2.0~rc7-1 Python 703 kB 11295-16955 MIT-like 4 no |+|| |-|| |o|| complain Python Python |-|| |+|| |+|| v, v[i], v["key"] $... ${...} #filter WebSafe | #if .. | #for .. in .. #def .. #include .. #import, #extends, #try, #except, #end .. | #elif .. | #repeat, #while #block, #implements #return .. | #else | #break, #continue | #unless ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `Jinja`_ 0.7 Python 100 kB 1789 GPL 2(-4) no |o|| |-|| |o|| ignore no own language |+|| |+|| |+|| v, v.i, v.key {{ .. }} {{.|escapexml}} | {% if .. %} | {% for .. in .. %} | {% prepare ..%} | {% include ..%} | {% extends %} {% end.. %} | {% else %} | {% range .. %} | {% call ..%} | {% require ..%} | {% block ..%} | {% marker ..%} ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `Jinja`_ 1.1 Python 283 kB 3436 BSD 4 no |+|| |o|| ignore or no own lang. + Python |+|| |+|| |+|| v, v[i], v["key"], {{ .. }} {{..|e} | {% if .. %} | {% for .. in .. %} {% macro ..%} {% include ..%} | {% extends ..%} {% end.. %} complain v.key | {% elif .. %} | {% else %} | {% block ..%} | {% else %} ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `Jinja`_ 2.0 Python ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `evoque`_ 0.3 Python 2.5 123 kB 932 AFL 3 no |+|| |-| / |o| |+|| ignore / no (restricted) Python |o|| [#]_ |+|| |+|| v, v[i], v["key"] ${...} | $if{..} | $for{.. in ..} | $begin{...} $evoque{...} | $evoque{..} ... $fi, $rof complain / | $elif{..} | $else | $end{...} | $overlay{..} ... | $else{..} ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `EmPy`_ 3.3-6 Python 112 kB 2387 LGPL 4 no |o|| |-|| |-|| / |o|| complain Python Python |-|| Python Python via Python @(...) via Python | @[if ..] | @[for .. in ..] @{def ..} via Python ... @[end ..] | @[elif ..] | @[else ..] ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `TemplateToolkit`_ 2.? Perl GPL / 4 no Perl [% PERL %] Perl |-|| |+|| [% .. %] | [% IF ..%] | [% FOREACH ..%] [% MACRO %] [% WRAPPER ..%] [% TRY %] [% END %] Artistic | [% ELIF ..%] | [% WHILE ..%] [% BLOCK ..%] [% CATCH %] | [% ELSE ..%] | [% LAST %] [% NEXT %] [% PROCESS ..%] [% RETURN %] | [% UNLESS ..%] | [% SWITCH%] [% CASE %] ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `StringTemplate`_ 2 no no ------------------- ----------- ----------- ------- --------------- ----------- ----------- ----------- ------- ----------- ----------- ----------- --------------- ------------------- ----------- ------- ------- ------------------- --------------- --------------- --------------------------- ----------------------- ------------------- ------------------- ------------------- --------------- ---------------- `pyTemple`_ 0.1 Python 59 kB 927 LGPL 2-4 no `pyt`_ 0.2.1 Python 10 kB 215 LGPL 4 no Python %(VAR)F `htmltmpl`_ 1.22 Python/PHP 59 kB 729 GPL no `Templayer`_ 1.4 Python 21 kB 386 LGPL 1 no `texttemplate`_ 0.2.0 Python 16 kB 223 MIT-like `HTMLtemplate`_ 1.5.0 Python 30 kB 397 MIT-like 1-2 yes XML/XSLT yes `Genshi`_ 0.3.4-1 Python 179 kB 2753 BSD-like yes `Kid`_ 0.9.3-1 Python 107 kB 3474 MIT-like 4 yes `SimpleTAL`_ 4.1-6 Python 104 kB 1953 BSD-like yes `OpenTAL`_ yes `ClearSilver`_ `N:PyTpl`_ 0.5.1 Python MIT-like `XTemplate4Python`_ 0.1.0 Python LGPL =================== =========== =========== ======= =============== =========== =========== =========== ======= =========== =========== =========== =============== =================== =========== ======= ======= =================== =============== =============== =========================== ======================= =================== =================== =================== =============== ================ lines-of-code: Number of "code"-lines as reported by pylint, without docstrings, comments and "empty" lines. Unicode: A template-engine should accept both templates and data in Unicode, but some of them don't and some only do optionally. error-msg.: | How good are the error-messages for template-syntax-errors etc.? Good error-messages are *extremely useful* when developing or "debugging" a template, and save *a lot* of time. | |+||: good error-messages, including line- and column-number of the error and the names of undefined variables | |o||: only partially the position of an error or name of an undefined variable is reported | |-||: error-messages do not tell where the error occurred escaping: | Ideally, escaping should be easy to use in the template (so you don't accidently forget it somewhere), should escape ``<>&"'`` for HTML/XML, and be configurable for other escaping. | |+||: escapes ``<>&"'`` | |o||: only escapes ``<>&`` | |-||: no escaping undef. vars: What should be done if the template uses a variable (or other object) which does not exist? Some of the template-engines ignore undefined variables, some complain (and exit), and some are configurable. embedded code: Can (Python-)statements be included in the template? | In contrast to expressions (see below), statements do not have a value but "do something" (e.g. if/for, print, raise, return, import etc.). In my opinion, templates should not directly include statements from a programming-language but define a small set of needed "statements" themselves. | (see also: ``_) embedded expr.: Can (Python-)expressions be included in the template? | An expression is everything which evaluates to a value, e.g. variables, arithmetics, comparisons, boolean expressions, function/method calls, list comprehensions etc. | (see also: ``_) sandbox: | Are the embedded expressions/statements sandboxed/restricted, or can they do anything? "Anything" of course includes things like ``open("/etc/passwd")`` and worse. | Note that these sandboxes do not limit the memory- and CPU-usage nor prevent infinite loops; if you want to limit these, use ulimit. | |+||: sandbox, where I don't know any way to break out (if you know any, please let me know!) | |-||: no sandbox .. _category: `Different approaches`_ .. [#] The sandbox of evoque is disabled by default. .. |o| replace:: **o** .. |-| replace:: **--** .. |+| replace:: **+** .. |o|| replace:: |o| .. |-|| replace:: |-| .. |+|| replace:: |+| .. _pyratemp: http://www.simple-is-better.org/template/pyratemp.html .. _cubictemp: http://dev.nullcube.com/ .. _Mako: http://www.makotemplates.org/ .. _Cheetah: http://www.cheetahtemplate.org/ .. _Jinja: http://jinja.pocoo.org/ .. _evoque: http://evoque.gizmojo.org/ .. _EmPy: http://www.alcyone.com/software/empy/ .. _TemplateToolkit: http://www.tt2.org .. _pyTemple: http://www.naderman.de/pyTemple/ .. _pyt: http://pyt.sourceforge.net .. _StringTemplate: http://www.stringtemplate.org .. _htmltmpl: http://htmltmpl.sourceforge.net/ .. _Templayer: http://excess.org/templayer/ .. _ClearSilver: http://www.clearsilver.net/ .. _texttemplate: http://py-templates.sourceforge.net/ .. _HTMLtemplate: http://py-templates.sourceforge.net/ .. _Kid: http://www.kid-templating.org/ .. _SimpleTAL: http://www.owlfish.com/software/simpleTAL/ .. _Genshi: http://genshi.edgewall.org/ .. _OpenTAL: http://savannah.nongnu.org/projects/opental/ .. _`N:PyTpl`: http://www.necoro.net/?projekte/npt.en .. _XTemplate4Python: http://www.python.org/pypi/XTemplate4Python/0.1.0 pyratemp -------- `pyratemp`_ is my own template-engine. It's probably (one of) the smallest complete template-engines [#]_, and it uses a very small set of special syntax in the templates. Both is good, because it reduces complexity and the probability of bugs and leads to a easy-to-use and intuitive user-interface. Additionally, it is quite fast (although not optimized for speed), uses a (pseudo-)sandbox and produces exceptional good error-messages (e.g. ``pyratemp.TemplateSyntaxError: line 18, col 1: invalid keyword 'fo'``), which is extremely useful. pyratemp was inspired by cubictemp. Since there were some essential features missing in cubictemp (see below), after trying to extend cubictemp, I decided to write my own template-engine from scratch, which is nearly as small and simple as cubictemp, but without its weaknesses. More details can be found on my `pyratemp`_-page. .. [#] pyratemp consists of about 500 lines-of-code. And the whole sourcecode is easy to understand and documented by about 500 lines of docstrings! cubictemp --------- `cubictemp`_ is probably the smallest of all template-engines. I completely agree to a statement on its homepage: *There are many large, over-designed Python templating systems out there.* Unfortunately, there are some essential features missing in cubictemp, like Unicode-support, inclusion of other templates, good/helpful error-messages, and some kind of a sandbox. Additionally, its conditionals (if/else) are weak, since the don't have a "elif" and can't contain template-syntax in its branches. Jinja ----- Jinja 0.7 was probably the best template-engine without embedded Python-expressions. And it included a sandbox, so a template could not do "bad things". But its embedded expressions were not powerful enough for many purposes, and so embedded Python-expressions were added in later releases. (Note that this is exactly what I predicted at the end of `Why if/for/calculations etc. are necessary in the View`_.) But although I haven't tested Jinja 2.0 yet and don't know how good its sandbox is, Jinja may be worth a look. evoque ------ I recently found `evoque`_, and was surprised that it has many things in common with pyratemp, and nearly has everything I described a template-engine should have (see `What a template-engine needs`_). But after reading parts of its documentation and a short test, I also found a few things I don't like -- especially in comparison with pyratemp: - It's *really counterintuitive* to generate a document with an exact amount of whitespace. You can find an example in my `benchsimple`_-code, where you can compare the whitespace in the template with the ones in the result. (Note that in the benchsimple-example, the whitespace before "" is ignored, and instead the whitespace before "$rof" of the for-loop above is used for ""!) Although that's probably not a problem when creating HTML/XML, it is when creating other documents. - "sandbox": By default, the sandbox is disabled, and I don't know if this sandbox is secure or not. But a look into the sourcecode showed that the programmer forbids known-unsafe functions instead of explicitly allowing only known-safe functions, which is definitely the wrong way and leaves a bad impression. - And it's code base is larger and worse documented than pyratemp. ;)