entire evoque site in a single printable page

evoque - managed eval-based freeform templating

Evoque is a full-featured and generic text templating system for python using a simple $-substitution syntax and providing support for flow control, nested templates, overlays, inter-template addressing and invocation, cache management, arbitrary python expressions, all python % operator formatting, restricted execution, automatic cross-site scripting protection, advanced encoding guessing algorithm, and more.

Allowing only python expressions and a managed evaluation namespace, Evoque offers a surprising level of simplicity, versatility and performance.

Buzz

  • Full-featured pure python templating engine / 972 SLOC
  • Automatic input quoting / XSS protection
  • Restricted execution
  • Dynamic template inheritance
  • Every text file is a template
  • Template collections
  • Unicode
  • Simplicity
  • Speed

Snapshot

$overlay{site_base} $begin{table_row} $for{ col in row } <td>${col}</td>\ $else <td class="empty" colspan="7">empty row</td> $rof $end{table_row} <table> $for{ i, row in enumerate(rows) } <tr$if{ i%2 } class="odd"$fi> #[ styled blueish ]# $evoque{#table_row} </tr> $rof </table> $evoque{table_legend.html} $evoque{data_disclaimer, collection="legals"} $test{ rows=[("a","b",3.0,"d","<escape-me/>","i","j")] }

Usage from within an application

Templates are always part of a Collection in a Domain -- a Domain and a default Collection instance are always created, be it explicitly or implicitly.

The preferred way to load/retrieve a Template is via a Domain instance; the following code implicitly creates the default Collection instance:

domain = Domain("/home/user/templates") t = domain.get_template("snap.html") print t.evoque(vars())

It may sometimes be more convenient to instantiate directly; the following code implicitly creates the Domain and the default Collection instances:

t = Template("/home/user/templates", "snap.html") print t.evoque(vars())

Basic benchmark

A basic benchmark, consisting of a small template that does a variety of standard templating tasks such as looping, calling nested and external sub-templates, escaping of data, etc. For each run, time is averaged over 2000 renderings, and the best time of 4 runs is retained. All times are in ms. What you should want is automatic quoting and less time.

quotingqpy 1.6evoque 0.1.2mako 0.2.2genshi 0.5.1
automatic0.170.550.604.10
manual0.530.60
none0.430.43
restricted0.66
manual quoting implies no qpyxml mode

Feature highlights

Full-featured generic templating engine, with rich features such as overlays and template caching.

Small footprint, coming in at 972 SLOC. And 146 of those SLOC are consumed by the generic and fairly advanced algorithm for guessing a string's encoding (see a separate discussion of the decodeh module). On the other hand this number does not include the code for the unit tests or the benchmarks.

Unicode. All internal processing is done on unicode instances. To facilitate this Evoque includes a sophisticated encoding guessing algorithm. Any and all encoding of the output is left to the liberty of the client application.

Template addressing. Every template, nested or not, is evoque'able from within any other.

Conditionals, loops, nested templates, evoque'ations, comments may all be liberally intermixed and nested.

Automatic input quoting / XSS protection thanks to the h8 quoted-string class from the Qpy Templating package, providing automatic cross-site scripting protection by guaranteeing that all input is always quoted and quoted only once. To benefit from this feature Qpy needs to be installed -- running without Qpy only means that you must take care to do your input quoting manually (which is just what some other text templating systems require you to do anyway). But, if you want to Avoid Being Called a Bozo When Producing XML, then this is one killer feature that you want!

Python expressions only, i.e. no python statements, nothing is ever exec'ed.

Restricted execution. Evaluation is within a managed, and optionally restricted, namespace; templates may be safely exposed to untrusted clients.

Easily introspectable templates, of their signatures and evaluation namespace.

Good template evaluation error handling making template development and debugging easy.

Applications may extend evaluation namespace with any python callable or, naturally, with Qpy Templates.

Every text file is a template. No particular distinction between template and content, i.e. any text source when used as one, is a template. Useful to assemble and evoque bits and pieces of content from any kind of source, and either rendered raw or evaluated.

Extremely dynamic e.g. see how easy it is to parametrize the base template of a template hierarchy chain per user.

Simplicity, with a trivially memorable syntax, just a handful of directives, easy management of different template collections, and general consistency.

Speed. The simplicity of the implementation, and the awesome python builtins and standard library, translate into what may well be the fastest pure python templating system around.

Other notes on the design and philosophy

  • Templates are organized in Collections that belong to a Domain.
  • Collections have a root folder -- all text files below are templates.
  • All collections are explicitly named -- decouples template addressing from deployment paths and protects sensitive path info within templates.
  • The creation of the Domain also creates the default Collection, that is named "" (the empty string). The default collection may be given another name, by simply creating it first.
  • Every template instance is associated to one and only one collection.
  • All templates are assigned a nick name, unique within the collection: memory-based templates are always named explicitly; for file-based templates the default name is their collection-root-relative or c-rel locator.
  • The src or location of any collection-qualified template is always specified as c-rel.
  • A previously loaded template may be retrieved by its name and the collection.
  • The eval globals dict belongs to Evoque and/or the Client Application, and is shared by all templates in a domain -- it is never modified after initialization.
  • The eval locals dict belongs to the document being rendered, and is passed down the nested execution scope, possibly cloned and modified each time.

Template syntax

definition: An evoque template is either (a) a textual file or (b) a string in memory or (c) a begin/end delineated block in a textual file or a string, each optionally containing additional evoque-specific markup.

Entire template syntax at a glance

substitutions $$ -> escape, replaced with $ ${expr} -> substitution, like %(expr)s ${expr!format} -> substitution, like %(expr)format directives $if{expr} ... $elif{expr} ... $else ... $fi $for{item in items} ... $else ... $rof $begin{label} ... $end{label} $prefer{raw=False, data=None, quoting=None, filters=None} $test{**kw} $evoque{name, src=None, collection=None, raw=False, quoting=None, input_encoding=None, filters=None, **kw} $overlay{name, src=None, collection=None, space="positive"} #[ comment ]# delimeters either "{" and "}" or "{%" and "%}", liberally interchangeable whitespace "\" at end-of-line consumes the following newline built-in callables ${evoque(name, **kw)} ${inspect()}

Explanations

Expressions

Expressions have the form ${expr !format} where the "!format" is optional, and defaults to "!s", i.e. default string rendering. The possible values for format are whatever is allowed by the python's string formating operations. The expr itself may be any valid python expression, that will be evaluated within the passed context (that may optionally be restricted). For example, the trivial template:

<p>${amount} ${amount!.2f}</p>

will produce the following output when rendered with amount=1.0/3:

<p>0.333333333333 0.33</p>

Directives

Directives have the form $directive{expr}. Syntactically they are same as an expression but have an additional qualifying keyword inserted between the $ and the opening {. See dedicated discussion on directives in general and for each directive in the directives section.

Literals: "$", "{", "}", "!", "\"

A literal "$" must always be escaped. A single "$" always signals the beginning of a substitution or a directive, e.g $expr has no specific meaning for Evoque and will give a SyntaxError (the "$" used to delineate any expanded svn keywords are exempted from this). Should always use either $$expr or ${expr} instead, depending on what is desired.

A literal "\" at end of a line slurps the newline character but a literal "\" followed by white space (only) until the first newline character is put out as is but without the white space, i.e. "\   \n" outputs ""\\n"".

All other literals within the text are never escaped.

Within an expression it is sometimes necessary to use literal braces e.g. to specify a dict value in place, such as key={"a":1}, or a literal piece of CSS such as "tag.class{display:inline}". The alternative "{%" and "%}" expression delimeters are provided to comfortably allow for this.

Whitespace

A "\" at end-of-line consumes the following newline, providing a basic whitespace control. In addition to this, the slurpy_directives=True on domain or on each collection will consume all trailing whitespace after a directive.

In addition, all leading or trailing whitespace within expressions and directives, i.e. between the curly braces, even if multi-line, is always ignored.

Comments

Single or multi line comments are delineated with #[ and ]#, respectively. For the convenience of easily commenting out a template section (even if that section itself already contains comments) comments may be nested. This however necessitates a minimum syntax to be respected, namely that comment openings and closing are balanced.

Built-in callables

Any callable set on the evaluation context, either with domain.set_on_globals() or by setting it on a locals dict that is then passed as a parameter to template.evoque(locals), may be called as an expression, e.g. ${my_callable(params)}. Evoque provides the following built-in callables.

inspect(output=True)

Pretty prints an overview of the evaluation namespace, namely the context's globals/locals and the template's expressions needing evaluation. When output is set to False, the output is written to domain.log.info() instead of rendered as part of the template output.

evoque(name, **kw)

This is perfectly equivalent to the directive form of $evoque{name, **kw}. The difference is that in the callable variation it is possible to intercept the output and process it further via any other callable -- but note that it is recommended that such processing be specified via the filters template parameter.

Directives

Directives have the form $directive{expr}. What expr may be depends on the directive, e.g. for if and for the difference is that for $if{expr} the expr is a normal expression evaluated as a bool, and for $for{expr in sequence} it must have this specific form.

A directive clause does not always have an expr, and in these cases the curly braces are not present, e.g. $else, $fi, $rof.

Directives must respect their specific context, e.g. if a $for{} is opened inside a nested template, then it must be closed with $rof within the same nested template.

Directives, in general, may be liberally intermixed and nested. A begin/end block may include other begin/end blocks, if blocks, for blocks, an overlay declaration, etc. However, specific constraints apply, e.g. only one prefer and overlay directives are allowed per template, nested or not, and they must be declared at top level of the template to which they pertain.

There is no significance to whether directives and any clauses are specified on a single line or on separate lines. This has benefits such as allowing an inlined if/else e.g. to calculate the value of an HTML attribute, just as easily as multi-line if/else blocks to control which section of a template gets rendered.

conditional if

$if{expr} ... $elif{expr} ... $else ... $fi

The conditional if/elif/else construct, apart from the closing $fi, behaves just like you would expect i.e. as in python, with optional (multiple) $elif{}'s and an optional concluding $else.

The condition expression may be any python expression, that must evaluate without errors in the supplied namespace. As for all directives, being on a single line or spanning many lines has no relevance.

for loops

$for{item in items} ... $else ... $rof

Again, the for loop directive behaves like you would expect (like in python) except for additional $else option for the case of an empty sequence (note the difference to python's $else that is executed at end unless loop exited with break statement).

One should note that the statement of the loop must be made up of an iterable on the right side of an in keyword, and whatever is on the left side of the in statement is the loop variable or variables that get set on the evaluation context on each iteration. This may be a single atomic variable, a flat sequence of variables, or even something more complex. Here's an example of a 2-level tuple as left part of a for loop statement:

$prefer{% data={"items":[("Apples", 7), (789, 9), ("Blue", 17)]} %} $if{items}<ul>\ $for{i, (item, code) in enumerate(items)} <li$if{i+1==len(items)} class="last"$fi>${code}:${item}</li> $rof </ul>$fi

That when rendered will produce:

<ul> <li>7:Apples</li> <li>9:789</li> <li class="last">17:Blue</li> </ul>

begin/end blocks: sub-templates

The $begin{label} and the $end{label} pair of directives define a nested template, addressable from any other template. A nested template has all the characteristics of top-level file or string templates -- the only difference is in how they are addressed. In fact, for the case of top-level file or string templates, you may consider that the begin and end directives are simply just implied by the beginning and end of the string. The label may be considered just as a named anchor, a destination inside the file to which one can point to directly.

Parameters

There is really only one parameter, and that is the label that is what is used to address the template.

$begin{ label } ...template body... $end{ label }

Rules

  • A label defining a nested template (whatever the nesting level) may be used once and only once in a template file or string.
  • Nested templates must be addressed via their label, using a syntax similar to fragment identifiers of a URL, i.e. #label. When being addressed locally, i.e. from same file or string, nested templates may be addressed with just #label. From within other templates, the information to identify the top-level template must also be specified, e.g. template_name#label for an explicitly named template, or by something like template.html#label for a template implicitly named by its file name.
  • For overlay templates it is possible to explicitly specify what level the lookup for a locally addressed nested template should start from i.e. if the name starts with a single "#" then from current current, if with "##" then from one level down, if with "###" then from two levels down, etc.
  • Nesting of templates may be multi-level; addressing is always flat, i.e. a nested template may further nest other templates, but all templates irrespective of nesting depth are addressed either (locally) with #label or with template_name#label.
  • If only one of the begin or end directives is present, then the other is implied by, respectively, the beginning or the end of the string.
  • A nested template is not automatically rendered when the template that defines it is rendered; it must be explicitly evoque'd.
  • Everything already established about directives applies, e.g. both begin and end may all be on same line, or each may span many lines; any of the two allowed delimeter pairs may be used; any whitespace around the label is ignored, etc.

Execution

Nested templates are the def, the macro, the callable block, the sub-template of Evoque. To be executed, they are evoque'd:

$evoque{template_name#label}

This will evaluate the nested template using a copy of the caller's context. Note that given that a nested template is a template in its own right, it can specify its own default data (and other preferences) that is what will be used when the caller's context does not specify a required variable. If neither caller nor default data specify a required variable, an evaluation error will occur and will be reported as per the domain's errors setting.

evoque: rendering a template

Any Evoque template, be it file-based or string-based, top-level or nested, is addressable from any other Evoque template in the domain. From within a template, $evoque{name, **kw} is the directive that gets and renders a template -- it combines the full functionality of the two methods you would need to call to do the same thing from within an application, i.e. the methods Collection.get_template() and Template.evoque().

Parameters

$evoque{ name, src, collection, raw, quoting, input_encoding, filters, **kw } name: required, the name of the template to evoque() src, collection, raw, quoting, input_encoding, filters : behave as in Domain.get_template() **kw : any additional keyword args are set on evaluator's locals

Rules

The only required parameter is name, that must always be specified first, and most often the call is as simple as: $evoque{name}.

Note that for convenience the name parameter may be specified as an unquoted string literal i.e. when the name= qualifier is not explicitly stated, the string found will be interpreted as a literal even if it is not enclosed in quotes. For example, for a template with name my_template, using any of the following 3 forms is perfectly equivalent: $evoque{my_template}, $evoque{"my_template"}, $evoque{name="my_template"}. But, $evoque{name=my_template} will cause the runtime evaluation of a my_template variable that will be used as the name.

The evoque directive gets the named template from the specified collection -- if collection=None, then the calling template's own collection is used, otherwise collection must name an already existing collection. Template may still need to be loaded. If it is already loaded, the template is retrieved from the cache, subject to cache staleness checking as per the collections's cache settings.

The src, input_encoding parameters are only used in the event that the template needs to be loaded:

  • if template is not yet loaded then it is assumed to be a file-based template and the location of the file to load is given by joining the collection's base path and the value of src.
  • if src=None then name is interpreted as the location of file to load.

All parameters other than name, src, collection, if not specified, will be set to the collection's default.

The raw parameter determines whether the raw template string is to be returned, without any evaluation, e.g. for the purpose of editing the template string itself. Note that if the template is not yet loaded and raw=True, then it will be loaded but not compiled. See the raw source howto for more details.

The quoting parameter determines what the default quoting class for the template is (if the template is not yet loaded) and/or which quoting class to use for this evoque'ation.

Any specified filters, that must be either None or a sequence of filter functions, are executed after evaluation. Filters must of course be available in the evaluation space.

An evoque'd sub-template will be quoted as per the quoting class of the calling template. This of course will only happen in the event that the two templates use different quoting classes.

Built-in callable

The $evoque{name, **kw} directive is also provided as the perfectly equivalent built-in callable:

${evoque("name", **kw}
  • "name" is now a normal python string so may be evaluated, i.e. if literal, then it must be quoted.
  • may be intermingled with other callables, python builtins, custom filters, etc. For example: ${markdown(evoque("my_mardown_template"))}

overlay

The $overlay{} directive defines a dependency chain of templates, super-imposing a template on top of another. Other template systems often call this functionality template inheritance -- a term that may however be a little misleading given the very specific connotations from object-oriented programming, and it is best to not add to any object-disorientation. Nonetheless, a great feature of evoque overlays is that they are in many ways similar in behaviour to the inheritance paradigm of object-oriented programming, as for example the nice feature that an evoque template does not care or even know about whether it is being used as a base template to an overlay.

Layered positive and negative spaces

A simple way to think of an overlay is as a graphic, with layers that may be transparent, and having positive and negative space. The positive space are the sub-template objects on the page, i.e. the top-level begin/end blocks defined by the template. The negative space is everything else, i.e. all markup that is not contained in any top-level begin/end block. An overlay template may declare that it overlays the space="positive" or the space="negative" of the overlaid template:

  • A positive-space overlay is one that may override any objects in the positive-space, i.e. top-level sub-template objects. Any markup in the negative-space of such a template is ignored. By default, overlays are positive-space.
  • A negative-space overlay is required to supply the negative-space for the template. That negative space may evoque sub-templates from the overlay chain below as if they were defined locally. Any sub-template defined below that is not evoque'd from the supplied negative-space will not be rendered. Naturally, such a template may of course also override any top-level sub-templates that make up its positive space.

Examples

The pages below offer some overlay scenario examples. These examples are live, in the sense that the templates are included directly from the Evoque unit tests, and the generated output is included into these pages automatically and with zero touch-up or modification.

Overlay directive parameters

The parameters are the same as for the evoque directive, but with an additional space keyword (inserted at the second position, after the name that is the only one required) that as we have seen may be either positive or negative:

$overlay{ name, src, collection, space } name, src, collection : behave in same way as for $evoque{} directive space : "positive" : either("positive", "negative")

Additional notes about overlays

  • An overlay directive declares that this template overlays the specified template, in either a positive-space or in a negative-space mode.
  • Only one overlay directive per template is allowed.
  • Any template may be overlaid, and overlaid templates are just normal templates requiring no modifications.
  • An overlaid template may itself overlay another template.
  • In an overlay, overlaid templates may be addressed with multiple leading # characters, to indicate deeper levels into the overlay chain, i.e.:
    • #label tells to start lookup from self
    • ##label tells to start lookup from one level below
    • ###label tells to start lookup from two levels below, etc.
    • specified_name#label continues to work as in any other template, of course, irrespective of whether the template given by specified_name participates in the overlay chain or not.

overlay example - basic

Here is a simple example, showing a frequently occuring case of having a template overlay another's positive-space. We use a base template that defines a page layout, and three sub-templates. We overlay the base.html template and override one of the three sub-templates, namely #content.

The templates

base.html

<html> <head><title>template = ${title}</title></head> <body> <span> -ve space (base.html) </span> $begin{header} +ve space: base header $end{header} $begin{content} +ve space: base content $end{content} $begin{footer} +ve space: base footer $end{footer} <table class="layout"> <tr><td>$evoque{#header}</td></tr> <tr><td>$evoque{#content}</td></tr> <tr><td>$evoque{#footer}</td></tr> </table> </body> </html>
  • just a normal template, and it is just fine for standalone usage as is
  • defines 3 nested templates, and evoques them
  • no need to be aware of any overlays over it

overlay.html

$overlay{base.html} -ve space (overlay.html) $begin{content} overlay ${parametrized} content $end{content} -ve space (overlay.html)
  • positive overlay template on base.html, own negative space is ignored
  • overrides #content, one of the 3 nested templates defined by base.html
  • if we add definitons for other nested (except #header and #footer that are defined in base) they would be ignored
  • when evoque'd directly, this template is rendered with:
    • the -ve space from base.html
    • own #content
    • the #header and #footer from base.html

Output

Rendering the overlay:

name = "overlay.html" domain.get_template(name).evoque(title=name, parametrized="HAPPY")

Will give the following output:

<html> <head><title>template = overlay.html</title></head> <body> <span> -ve space (base.html) </span> <table class="layout"> <tr><td>+ve space: base header </td></tr> <tr><td>overlay HAPPY content </td></tr> <tr><td>+ve space: base footer </td></tr> </table> </body> </html>

overlay example - positive

Let's extend the simple example to add an intermediate template, so we therefore have an overlay chain conisting of 3 templates, namely overlay_chain_pos.html over overlay_mid.html over the unmodified base.html (from simple example).

The templates

overlay_mid.html

$overlay{base.html} -ve space (overlay_mid.html) $begin{footer}<span>overlay_mid footer</span>$end{footer} -ve space (overlay_mid.html)
  • an intermediate positive overlay on base.html
  • the -ve space from base.html is imposed
  • overrides one +ve block, #footer

overlay_chain_pos.html

$overlay{overlay_mid.html} <html> <head><title>template = ${title}</title></head> <body> <span> -ve space (overlay_chain_pos.html) </span> $begin{content} overlay_chain_pos ${parametrized} content $end{content} <div class="header">$evoque{#header}</div> <div class="content">$evoque{#content}</div> <div class="footer">$evoque{#footer}</div> </body> </html>
  • overrides the positive space of overlay_mid.html, redefining #content
  • when evoque'd directly, this template is rendered with:
    • the -ve space from below
    • own #content
    • the #header and #footer from below

Output

Rendering the overlay:

name = "overlay_chain_pos.html" domain.get_template(name).evoque(title=name, parametrized="HAPPY")

Will give the following output (note negative space used is that from base.html):

<html> <head><title>template = overlay_chain_pos.html</title></head> <body> <span> -ve space (base.html) </span> <table class="layout"> <tr><td>+ve space: base header </td></tr> <tr><td>overlay_chain_pos HAPPY content </td></tr> <tr><td><span>overlay_mid footer</span></td></tr> </table> </body> </html>

overlay example - negative

Let's modify the positive chain example a little to show a negative overlay. We add the overlay_chain_neg.html template that is a negative overlay on the unmodified overlay_mid.html + base.html base templates (from previous example). This example also shows how to specify specific levels in the overlay chain at which to start lookup.

overlay_chain_neg.html

$overlay{overlay_mid.html, space="negative"} <html> <head><title>template = ${title}</title></head> <body> <span> -ve space (overlay_chain_neg.html) </span> $begin{content} overlay_chain_neg ${parametrized} content $end{content} $begin{footer}<span>overlay_chain_neg footer</span>$end{footer} <div class="header">$evoque{#header}</div> <div class="content">$evoque{#content}</div> <div class="refootered"> <div class="own">$evoque{#footer}</div> <div class="overlaid">$evoque{##footer}</div> <div class="named">$evoque{base.html#footer}</div> </div> </body> </html>
  • a negative overlay, thus using own negative space
  • redefines #content and #footer
  • evoques all 3 #footer templates in the overlay chain, showing how to:
    • start lookup from self
    • start lookup from "one level below"
    • explicitly evoque a named template

Output

Rendering the overlay:

name = "overlay_chain_neg.html" domain.get_template(name).evoque(title=name, parametrized="HAPPY")

Will give the following output:

<html> <head><title>template = overlay_chain_neg.html</title></head> <body> <span> -ve space (overlay_chain_neg.html) </span> <div class="header">+ve space: base header </div> <div class="content">overlay_chain_neg HAPPY content </div> <div class="refootered"> <div class="own"><span>overlay_chain_neg footer</span></div> <div class="overlaid"><span>overlay_mid footer</span></div> <div class="named">+ve space: base footer </div> </div> </body> </html>

autoload preferences

The $prefer{} directive is a declaration of autoload preferences, from within a template. Only one per template is allowed.

When template is fetched on the fly e.g. when evoque'd from another template, it may either have already been loaded, compiled, and cached or it may still need to be looked up and compiled -- in this latter case all parameters that may affect how the template is compiled are needed. Precisely for such a case, the $prefer{} directive provides the convenience of specifying default parameter values that will be used if corresponding values are not specified when the template is loaded.

Parameters

The parameters are a subset of those of the evoque directive. A parameter of the prefer directive is only taken into consideration if:

  • it has not been specified at template load time;
  • the preferred value is different than the cascaded default value from the collection.
$prefer{ raw=False, data=None, quoting="xml", filters=None } raw : may trigger template recompile data : may set value of template.data quoting : may trigger template recompile filters : may set value of template.filters

For details of each of these parameters, see the evoque directive page.

auto testing a template

The $test{} directive specifies test data within the template itself, that will be used to evaluate the template when template.test() is called. test directives have no effect on output generated when a template is evoque'd.

Parameters

May specify sample data, as keyword parameters that the template may need. Positional arguments are not supported -- only keyword args.

$test{ **kw }

From an application, any tests are executed with:

template.test() -> [qsclass]

Rules

  • Specify sample test data for the template, nested or not.
  • May specify as many as desired, execution will cascade through them in the specified order, i.e. if a kw arg is not specified in current test, then its last defined value will be used for this test.
  • If template defines any default data, then that data is used as initial data for the tests (see Combined with template's default data, below).
  • Executed simply with template.test(), and if any evaluation errors occur they are always raised irrespective of the setting for domain.errors.
  • template.test() returns a list of responses, one for each test directive. If no test directive is specified, a test will always be attempted with either the template's default data or with no data.
  • template.test() on a raw template returns None.
  • A test directive must occur at the template's top level.
  • May also be used to prime the template.evaluator.codes cache.

Example

$if{ is_good } <p class="yes">Good for you, ${first}!</p> $elif{ is_adult } <p class="adult">Think again, ${last}.</p> $else <p class="minor"> That is not quite right ${first}. Try again! </p> $fi $test{is_good=True, is_adult=True, first="Jo", last="Dough"} $test{is_good=False} $test{is_adult=False}

Doing a template.test() from an application will thus evaluate this template 3 times, one for each test directive specified. Any parameter not specified in a test directive will inherit its value from the previous test directive. In this case the test data provides for coverage of all the possible logical paths, so every piece of the template is exercised.

Combined with template's default data

Templates may be set-up to define and remember own default data. This may be done either at initiaization (data init parameter) or via a $prefer{data=dict(**kw)} directive. If a template's data attribute is set, the test() method will use that value as the starting point for the test data cascade. To illustrate this relation, here's the example above, adjusted with a $prefer{} directive and still giving identical test() results:

$prefer{ data=dict(first="Jo", last="Dough") } $if{ is_good } <p class="yes">Good for you, ${first}!</p> $elif{ is_adult } <p class="adult">Think again, ${last}.</p> $else <p class="minor"> That is not quite right ${first}. Try again! </p> $fi $test{is_good=True, is_adult=True } $test{is_good=False} $test{is_adult=False}

Usage from within an application

Templates are always part of a Collection in a Domain -- a Domain and a default Collection instance are always created, be it explicitly or implicitly.

The preferred way to load/retrieve a Template is via a Domain instance; the following code implicitly creates the default Collection instance:

domain = Domain("/home/user/templates") t = domain.get_template("snap.html") print t.evoque(vars())

It may sometimes be more convenient to instantiate directly; the following code implicitly creates the Domain and the default Collection instances:

t = Template("/home/user/templates", "snap.html") print t.evoque(vars())

Evoque is conceived with safety and security in mind. Under no circumstances, for example, is it ever possible to evoque a template from a file that is not within a declared collection. Given that there is no special distinction between templates and content, template collection root directories are treated in the same way that a web server treats its document-root.

Other notes

Given a template instance, you can always get the associated collection and domain with:

template.collection template.collection.domain

To create the (default) collection first, thus having an opportunity to name it explicitly, you do:

from evoque.collection import Collection collection = Collection(None, "default_collection", "/home/user/templates") domain = collection.domain assert collection is domain.get_collection("default_collection") assert collection is domain.get_collection()

domain, collection, template

A template is always part of a collection in a domain.

The implications of this are that those related objects are always created, even if we are still free to create them explicitly in any order we prefer. Another implication is consistency and simplicity of the implementation, reducing the need to handle other scenarios.

The preferred usage is to first explicitly create the Domain instance, that will by default create the default empty string named Collection.

Domain

class Domain(object): """ A registry of collections """ def __init__(self, default_dir, restricted=False, errors=3, log=get_log(), # defaults for Collections (and Templates) cache_size=0, auto_reload=60, slurpy_directives=True, quoting="xml", input_encoding="utf-8", filters=[]): """ default_dir: either(str, Collection) abs path, or actual default collection instance restricted: bool restricted evaluation namespace errors: int ["silent", "zero", "name", "render", "raise"] log: the logging.getLogger("evoque") logger; may be pre-initialized and passed in, or adjusted as needed after initialization. Default settings (via loggin.basicConfig()) are: handler=StreamHandler() level=logging.INFO format="%(asctime)s %(levelname)-5.5s [%(name)s] %(message)s" # Defaults for Collections (and Templates) cache_size: int max number loaded templates in collection 0 means unlimited loaded templates auto_reload: int min seconds to wait to check if needs reloading 0 means check on every rendering slurpy_directives: bool consume all whitespace trailing a directive quoting: either("xml", "str", type) "xml" -> h8, "str" -> unicode input_encoding: str hint for how to decode template source filters: [callable] list of template post-evaluation filter functions """
domain public methods set_on_globals(name: str, value: any) -> None set_namespace_on_globals(name: either(str, None), obj: any, no_underscored: bool = True ) -> None get_collection(name: either(None, str, Collection) = None ) -> Collection set_collection(name: str, dir: str, cache_size: int = None, auto_reload: int = None, slurpy_directives: bool = None, quoting: either(str, type) = None, input_encoding: str = None, filters:[callable] = None ) -> None has_collection(name: str) -> bool methods wrapping collection.*_template() get_template(name, src=None, collection=None, raw=None, data=None, quoting=None, input_encoding=None, filters=None ) -> Template set_template(name, src=None, collection=None, raw=None, data=None, from_string=True, quoting=None, input_encoding=None, filters=None ) -> None has_template(name, collection=None) -> bool

Collection

class Collection(object): """ A collection of templates, rooted at a directory """ def __init__(self, domain, name, path, # defaults from Domain cache_size=None, auto_reload=None, slurpy_directives=None, quoting=None, input_encoding=None, filters=None): """ domain: either(None, Domain) name: str, name by which to retrieve the collection path: str, abs path for root folder for the collection For more on defaults from Domain init parameters, see the docstring in domain.Domain.__init__(). Preferred way to create a new collection: domain.set_collection() """
collection public methods get_template(name="", src=None, raw=None, data=None, quoting=None, input_encoding=None, filters=None ) -> Template """ get / load (if file-based) a template """ set_template(name, src, raw=None, data=None, from_string=True, quoting=None, input_encoding=None, filters=None ) -> None """ typically used to set a template from a string """ has_template(name) -> bool
collection (read-only) attributes domain: Domain

Template

class Template(object): """ An Evoque template """ def __init__(self, domain, name, src=None, collection=None, raw=None, data=None, from_string=False, # defaults from Collection quoting=None, input_encoding=None, filters=None): """ domain: either(str, Domain) abs path, or actual domain instance name: str if no src this is collection-root-relative locator src: either(None, str) (file-based) collection-root-relative locator (from_string=True) template source string collection: either(None, str, Collection) None implies default collection, else str/Collection refer to an existing collection raw: bool if raw, do not compile the template source data: either(None, dict) default data for template evaluation from_string: bool when True src must specify the template source For more on defaults from Collection init parameters, see the docstring in collection.Collection.__init__(). Preferred way to create a new template: domain.{get_template(), set_template()} """
template public methods evoque(locals: dict = None, raw: bool = None, quoting: either(str, type) = None **kw) -> qsclass """ Render template, as an instance of qsclass (derived from quoting or from template.qsclass). If locals is None, use a copy of "own" locals. If raw or template.raw is True, return raw string without evaluation. Any kw args are set onto locals before evaluation. """ test() -> either(None, [qsclass]) """ Return a rendering per test directive """ unload() -> None """ Unloads self from collection """
template (read-only) attributes name: str collection: Collection raw: bool data: either(None, dict) default data for template evaluation qsclass: either(unicode, h8, type) quoted-string class, defaults to template.qsclass (determined from quoting init param) input_encoding: str encoding hint, default from collection filters: [callable] post-evaluation filters, default from collection

Note there are no instance attributes that correspond directly to the src, from_string and quoting init parameters.

managing the collection's template cache

Each collection controls its own cache, customizable via two parameters that get their default values from the domain:

  • cache_size: int = 0
    sets the maximum number of loaded templates in a collection, with a value of 0 meaning no limit.
  • auto_reload: int = 60
    sets the minimum number of seconds to wait before doing a stat on the template file to see if it has been modified. A value of 0 means to do the file system check on each request for the template.

The collection cache is always on.

If you prefer to run like there was no cache, i.e. reloading every template each time it is requested, you can set cache_size=1 and auto_reload=0.

If, on the other hand, you would like to run such that templates are virtually never reloaded, you can set cache_size=0 and auto_reload=60*60*24, i.e. a sufficiently large number of seconds to wait between checks.

template error handling

Template errors may occur in one of two contexts -- during loading or during evaluation. It is of course possible that a loading error of one template occurs during the evaluation of another, for example when a template evoques another template that is not yet loaded. Thus the big differentiator for errors is not the type of error but when an error happens.

All errors not occurring during evaluation are raised as exceptions, as any other normal application error.

The behaviour of evaluation errors may be controlled via two domain parameters:

  • errors: int = 3
    sets evaluation behaviour for errors, and may be an int between 0 and 4:
    • 4 = raise
      will re-raise the exception given by the error.
    • 3 = render
      will render the error as a 1-line descriptive summary of the error.
    • 2 = name
      will render the error as the indicative string: EvalError[expr], where expr is the actual source of the failed expression.
    • 1 = zero
      (not yet implemented) assume the zero object for the inferred python type for the failed expression being evaluated. Examples, if a str then proceed with the empty string, if a list proceed with empty list, if an int proceed with 0, etc.
    • 0 = silent
      no output is generated for the error, i.e. errors are rendered as the the empty string.
  • log: logging.getLogger("evoque")
    sets the logger to be used for all evaluation errors; independently of the setting of errors, evaluation errors are always logged. Also, the output of calling inspect(False) from within a template are sent to log.info().

restricted execution

Evoque supports python expressions only. This means that python statements are already naturally restricted as when evaluated these will always raise a SyntaxError. The problem of restricting execution is thus reduced to the problem of making the eval python built-in safe. This is by no means an easy task, but at least with its more limited scope it is simpler than attempting to generically restrict or sandbox the python interpreter.

Running in restricted mode will (supposedly) make it impossible to manipulate tangible resources, such as files and sockets, from within a template. Protection of intangible resources, such as memory and CPU usage, is not (yet) provided (see below).

restricted: bool = False
This is the one domain init parameter that when set to True will initialize the domain to run in restricted execution mode. Setting this to True has therefore the following consequences:

  • Sets a dummy __builtins__ empty dict on the domain-wide globals dict used by eval.
  • Adds a top-level entry, domain-wide globals dict used by eval, for each builtin deemed safe.
  • At runtime scans all expressions before compiling them for "__" (double underscore) and if found will raise a LookupError.

Builtins deemed unsafe

All subclasses of BaseException are considered potentially unsafe and so are not made available as entries on the globals dict. Other python 2.5 __builtins__ considered unsafe are (defined in the list Domain.DISALLOW_BUILTINS):

DISALLOW_BUILTINS = ["_", "__debug__", "__doc__", "__import__", "__name__", "buffer", "callable", "classmethod", "coerce", "compile", "delattr", "dir", "eval", "execfile", "exit", "file", "getattr", "globals", "hasattr", "id", "input", "isinstance", "issubclass", "locals", "open", "quit", "raw_input", "reload", "setattr", "staticmethod", "super", "type", "vars"]

This may seem overly restrictive, but then templating should not require more than a small subset of python's possibilities, for convenience of doing simple progamming tasks e.g. enumerating over a list.

Is this enough?

Depending on paranoia level, probably not. Besides more testing to validate that the above effectively does not allow the template programmer to access and manipulate any tangible resources, it should still be possible to bring down the interpreter via DOS maliciousness, e.g. evaluating an expression for a very large multiplication to consume all available memory, or to take a very long time to finish. Further runtime analysis of expressions will be required to protect against intangible resources such as memory and CPU usage, and how best to add such protection is future work that is still to be done. Restricted execution mode should for now best be considered experimental.

managing the namespace

Besides turning restricted mode on or off, customization control of the evaluation context is available by means of:

domain.set_on_globals(name: str, value: any) -> None domain.set_namespace_on_globals(name: either(str, None), obj: any, no_underscored: bool = True) -> None mylocals = dict() # prepared as necessary template.evoque(mylocals)

The domain methods allow adding of objects onto the domain-wide globals dict.

no_underscored: bool = True
By default, the set_namespace_on_globals() methods look for an __all__ attribute on the source object, and if one is found will only set the what is listed by it. If no one is found, it will import all attributes but by default not those starting with an underscore -- behaviour that may be changed by setting no_underscored = False.

Passing a prepared mylocals dict to template.evoque() can additionally expose specific objects per template evoque'ation.

Howto

Because everything is sometimes not immediately obvious ;-)

Sub-templates in markdown

This amounts to just specifying the markdown filter on the output of the evoque'd template. It is easy to see how it works with an example (coming straight from the unit test data included with the Evoque distribution).

The template

markdown.html

$begin{my-markdown-template} ### From a markdown template! - item one - item two ${param} $end{my-markdown-template} <div class="markdown-output"> $evoque{#my-markdown-template, filters=[markdown], param="<xml/>"} </div>

For simplicity, we just use a single file, where the top-level template evoques a locally-nested markdown template, but of course the source of the sub-template could be anything you like. Notice that all the outer template does is evoque the inner template into a <div/>, specifying the filters to use as well as some default data.

Output

We must supply the filter we would like to use, so we need to set it on the globals of the evaluation namespace. We can then render the template:

from markdown import markdown domain.set_on_globals("markdown", markdown) print domain.get_template("markdown.html").evoque()

That will produce the following output:

<div class="markdown-output"> <h3>From a markdown template!</h3> <ul> <li> item one </li> <li> item two &lt;xml/&gt; </li> </ul></div>

Notice that automatic quoting of incoming data also just happens for sub-templates in markdown or in any other format.

Alternatives

Declaring preferred filters within template itself

A template may declare itself what filters should be used. Sometimes we may not have write access to a template source, but in those cases it would be easy to just read that source and wrap it into a another template under our control. The template below issues a $prefer{} directive with filters=[markdown] as well as some default data, and gives identical results as above, but relieves the caller from the need of specifying the filters or any default data on each call.

$begin{my-markdown-template} $prefer{ filters=[markdown], data=dict(param="<xml/>") } ### From a markdown template! - item one - item two ${param} $end{my-markdown-template} <div class="markdown-output"> $evoque{#my-markdown-template} </div>

Using only python callables

Just to get a liitle closer to what is going on underneath... declaring filters on the $evoque{} call or the $prefer{} directive of a template is really just a little syntactic sugar over doing everything with pure python callables. Here's what such an alternate version would look like (here we are required to use the callable version of the evoque directive):

markdown_callables.html
$begin{my-markdown-template} ### From a markdown template! - item one - item two ${param} $end{my-markdown-template} <div class="markdown-output"> ${h8(markdown(evoque("#my-markdown-template", param="<xml/>")))} </div>

This will produce the exact same output when rendered as above.

Using without qpy and still doing html quoting

Qpy is only used when quoting="xml", thus if you run with quoting="str" you will not need to have Qpy installed, but you will need to take care of quoting yourself. As an example, let's use the code from the the evoque_mq benchmark that is included in the distribution:

First, make quoting="str" the domain-wide default, and we can also set, on the domain's globals, the quoting function we want to use:

import evoque from os.path import abspath, join, dirname DIR = abspath(join(dirname(evoque.__file__), 'bench/evoque')) from evoque.domain import Domain domain = Domain(DIR, quoting="str") import cgi domain.set_on_globals("quote", cgi.escape)

Specify the actual quoting in the template itself, template_mq.html:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <head> <title>${quote(title)}</title> </head> <body> $evoque{header_mq.html} $begin{greeting}Hello, ${quote(you)}!$end{greeting} <div>$evoque{#greeting, you=user}</div> <div>$evoque{#greeting, you="me"}</div> <div>$evoque{#greeting, you="world"}</div> <h2>Loop</h2> $if{items}<ul> $for{idx, item in enumerate(items)}\ #[ $if{ idx+1 == len(items) } class="last"$fi ]# <li${idx+1==len(items) and " class='last'" or ""}>${ quote(item)}</li> $rof </ul>$fi $evoque{footer.html} $test{ title='Just a test', user='joe', items=['a', '<b/>', 'c'] } </body> </html>

Rendering is as usual:

data = dict(title="Just a test", user="joe", items=["<n>%d</n>" % (num) for num in range(1, 15)]) domain.get_template("template_mq.html").evoque(data)

Switching site template, dynamically, per user

The use of overlays, or template inheritance, where the base template is used to define the site layout could be a simple and effective way to manage a theme for a site. In addition, multiple site themes could be managed simply via multiple site base templates. Evoque supports dynamic overlays, or template inheritance -- and dynamic in two very handy ways:

  • First, by the possibility of using logically named templates that allows assigning a name to any template, and then retrieving it by that name. The dynamic part comes from the fact that we can at runtime change which template is pointed to by a given name (by first doing a template.unload() and then loading another template under the same name).
  • Second, by the fact that a template may specify which template it overlays by means of a parameter that will be evaluated for each rendering at runtime.

For this case, the second technique makes it really trivial to parametrize the base template per user. Here's the essence of an overlay page template that does this:

a dynamic page overlay: site_dyn_page_var.html
$overlay{name=my_site_theme} $begin{content}page: ${message}$end{content}

Where, on each rendering, which base template to use is determined by the runtime value of my_site_theme. Assume we have loaded the template with:

page_overlay = domain.get_template("site_dyn_page_var.html")

We can then specify a different value for my_site_theme on each rendering:

page_overlay.evoque(my_site_theme="site_template_divs.html", ... )

Note that if you want to make sure you always have a fallback value for my_site_theme (for all renderings, all templates, and all users) then just set it on the domain's globals:

domain.set_on_globals("my_site_theme", "site_template_table.html")

example details

For completeness, let's fill in the full details for this simple example (that forms part of the Evoque test cases, and is included in the distribution).

a couple base templates as site themes

theme one: site_template_table.html
<html> <head><title>site-table: ${title}</title></head> <body> $begin{header}site-table: <h1>${title}</h1> $end{header} $begin{content}site-table: ${message} $end{content} $begin{footer}site-table: ${footer}$end{footer} <table class="layout"> <tr><th>$evoque{#header}</th></tr> <tr><td>$evoque{#content}</td></tr> <tr><td>$evoque{#footer}</td></tr> </table> </body> </html>
theme two: site_template_divs.html
<html> <head><title>site-div: ${title}</title></head> <body> $begin{header}site-div: <h1>${title}</h1>$end{header} $begin{content}site-div: ${message}$end{content} $begin{footer}site-div: ${footer}$end{footer} <div class="layout"> <div class="header">$evoque{#header}</div> <div class="content">$evoque{#content}</div> <div class="footer">$evoque{#footer}</div> </div> </body> </html>

a couple renderings of the same page overlay

using theme one:
page_overlay.evoque(my_site_theme="site_template_table.html", title="hey tabby!", message="you're kinda square!", footer="you reckon?")
gives the output:
<html> <head><title>site-table: hey tabby!</title></head> <body> <table class="layout"> <tr><th>site-table: <h1>hey tabby!</h1> </th></tr> <tr><td>page: you're kinda square!</td></tr> <tr><td>site-table: you reckon?</td></tr> </table> </body> </html>
using theme two:
page_overlay.evoque(my_site_theme="site_template_divs.html", title="howdie!", message="ya bin free floatin' good?", footer="sure thang!")
gives the output:
<html> <head><title>site-div: howdie!</title></head> <body> <div class="layout"> <div class="header">site-div: <h1>howdie!</h1></div> <div class="content">page: ya bin free floatin' good?</div> <div class="footer">site-div: sure thang!</div> </div> </body> </html>

Output the raw source of an evoque template

Just evoque() with raw=True and, if we would like to escape html source, then also specify quoting="str". It does not matter how the evoque template is defined, or what source format it is in. Here's an example, let's first define a template template.html:

$if{items} <ul> $for{ i, item in enumerate(items) } <li>${item}</li> $rof </ul> $fi

And we can evoque it as raw source from an application:

domain.get_template("template.html").evoque(raw=True, quoting="str")

Or, from another template in the same collection:

$evoque{template.html, raw=True, quoting="str"}

Notes

Either of get_template(raw=True).evoque() or get_template().evoque(raw=True) is valid, but there is a significant difference here: stating get_template(raw=True) will (when template is not yet loaded) load it and not compile it; stating evoque(raw=True) will, after loading and compiling the template if necessary, render the template's raw source.

Similarly, it is possible to do either of get_template(quoting="str").evoque() or get_template().evoque(quoting="str"): the first, used when the template is loaded, sets the template's default quoted string class to use, while the second specifies which one to use for this evoque'ation.

Using the $evoque{} form, quoting="str" and raw=True also have the implications that if the template is not yet loaded then it will be loaded with unicode as its default quoted string class, and as a raw template, i.e. it will not be compiled and any subsequent attempts of evoque(raw=False) will result in an error.

Any template may be rendered raw. Sometimes you know beforehand that a template will be almost always rendered raw. In those cases it is more practical to specify a $prefer{ raw=True } directive once in the template itself, as opposed to specify same on each evoque'ation of the template. Same suggestion applies for quoting.

Performance benchmarks

All performance benchmarks are included in the distribution -- please try out for yourself, the file bench/README.txt has some additional deatils for how to run each benchmark.

platform

The actual times are coming off a MacBook Pro with 2.4 GHz Intel Core 2 Duo, 2 GB of RAM, and Mac OS X 10.5, running python 2.5.1.

caution

Please remember that performance benchmarks are only relevant when considered within an entire context, and they may vary enormously between one environment and another. In addition, two different systems never do precisely the same thing, however simple and apparently identical the timed task may be.

general observations

Qpy is a templating system almost identical to straight python and offering convenient and fast (at least the C based version) html string building with automatic escaping. From the Qpy readme: Qpy provides a convenient mechanism for generating safely-quoted html text from python code. It does this by implementing a quoted-string data type and a modification of the python compiler.

Amongst currently popular full-featured templating systems for python: Mako is probably the fastest pure-python text-based system; Genshi is probably the feature-richest XML-based system.

The performance of the Evoque and Mako text-based systems seems to be more or less similar. Evoque seems to be faster for very simple templates, while Mako does better on loop-intensive templates -- probably because of additional work that Evoque has to do for the runtime evaluation of loop variables.

Suggestions to make these benchmarks more relevant are welcome, especially from anyone with particular knowledge of the specific templating system in question.

The numbers from these benchmarks are a tribute to python's conceptual integrity -- that rewards a straightforward implementation of a simple design with... amazing performance.

subs benchmark

For curiosity, a very simple performance look at doing only string substitutions -- thus for this one we can even compare against string.Template from the python standard library. We test a small template, mostly static text with just 6 variable substitutions.

For each run, time is averaged over 2000 renderings, and the best time of 4 runs is retained. All times are in ms. What you should want is automatic quoting and less time.

quotingtemplate.Stringqpy 1.6evoque 0.1.2mako 0.2.2genshi_text 0.5.1
automatic0.050.050.15
manual0.070.160.26
none0.060.050.12
restricted0.060.07
naturally restricted

template and data

The (automatically quoted) evoque template.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Style-Type" content="text/css; charset=UTF-8" /> <meta http-equiv="imagetoolbar" content="no" /> <style type="text/css"> .signature { color: #977; font-weight: bold; } </style> <title>${title}</title> </head> <body> <p>Welcome back ${first}, you are logged in as </code>${username}<code> (last login: ${last}).</p> <p>Your balance is: ${balance}</p> <p>${comment}</p> </body> </html>
DATA = dict(title='Your balance', first="Joey", username='joe123', last="2008-02-29", balance=789.19, comment="Thank you <b>very</b> much!")

platform

The actual times are coming off a MacBook Pro with 2.4 GHz Intel Core 2 Duo, 2 GB of RAM, and Mac OS X 10.5, running python 2.5.1.

caution

Please remember that performance benchmarks are only relevant when considered within an entire context, and they may vary enormously between one environment and another. In addition, two different systems never do precisely the same thing, however simple and apparently identical the timed task may be.

basic benchmark

A basic benchmark, consisting of a small template that does a variety of standard templating tasks such as looping, calling nested and external sub-templates, escaping of data, etc. For each run, time is averaged over 2000 renderings, and the best time of 4 runs is retained. All times are in ms. What you should want is automatic quoting and less time.

quotingqpy 1.6evoque 0.1.2mako 0.2.2genshi 0.5.1
automatic0.170.550.604.10
manual0.530.60
none0.430.43
restricted0.66
manual quoting implies no qpyxml mode

Inspiration for this benchmark is from the basic benchmark proposed by Genshi.

template and data

The (automatically quoted) evoque templates.

template.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <head> <title>${title}</title> </head> <body> $evoque{header.html} $begin{greeting}Hello, ${you}!$end{greeting} <div>$evoque{#greeting, you=user}</div> <div>$evoque{#greeting, you="me"}</div> <div>$evoque{#greeting, you="world"}</div> <h2>Loop</h2> $if{items}<ul> $for{idx, item in enumerate(items)}\ #[ can inline an if statement below (that other templates cannot do): $if{idx+1==len(items)} class="last"$fi but it is slightly slower ]# <li${idx+1==len(items) and " class='last'" or ""}>${item}</li> $rof </ul>$fi $evoque{footer.html} $test{ title='Just a test', user='joe', items=['a', '<b/>', 'c'] } </body> </html>
header.html <div id="header"> <h1>${title}</h1> </div>
footer.html <div id="footer"></div>
DATA = dict(title='Just a test', user='joe', items=['<n>%d</n>' % (num) for num in range(1, 15)])

platform

The actual times are coming off a MacBook Pro with 2.4 GHz Intel Core 2 Duo, 2 GB of RAM, and Mac OS X 10.5, running python 2.5.1.

caution

Please remember that performance benchmarks are only relevant when considered within an entire context, and they may vary enormously between one environment and another. In addition, two different systems never do precisely the same thing, however simple and apparently identical the timed task may be.

bigtable benchmark

A simple brute force generation of a 10 columns x 1000 rows table, inspired from the bigtable benchmark proposed by Genshi. For each run, time is averaged over 10 renderings, and the best time of 4 runs is retained. All times are in ms. What you should want is automatic quoting and less time.

quotingqpy 1.6evoque 0.1.2mako 0.2.2genshi 0.5.1
automatic46.35119.5942.91605.94
manual102.5277.72
none74.3622.78
none, tweaked9.819.53
restricted147.23
manual quoting implies no qpyxml mode

template and data

The (automatically quoted) evoque template.

template_string = """<table> $for{ row in table } <tr>$for{ col in row }<td>${col}</td>$rof</tr>\ $rof </table> $test{ table=[("a","b","c","d","<escape-me/>","f","g","h","i","j")] } """
BASEROW = ("a","b","c","d","<escape-me/>","f","g","h","i","j") TABLE_DATA = [ BASEROW for x in range(1000) ]

platform

The actual times are coming off a MacBook Pro with 2.4 GHz Intel Core 2 Duo, 2 GB of RAM, and Mac OS X 10.5, running python 2.5.1.

caution

Please remember that performance benchmarks are only relevant when considered within an entire context, and they may vary enormously between one environment and another. In addition, two different systems never do precisely the same thing, however simple and apparently identical the timed task may be.

Frequently asked questions

Why yet another templating system?
Hopefully for precisely the same reason as every other one? Seriously, you may wish to take a look at Evoque's list of distinguishing features.

Why the evoque name?
It is a triple trip tributing all at once the great little eval python built-in, the (optional) Qpy third-party package that guarantees all data input into html templates is not only always quoted but also quoted once and only once, and the act of evoking a document from a template and a bunch of data. Maybe they are more allusions than tributes, possibly making all this seem a little cubist. Don't call me pablo, just that, everything is related. The three are but one.

Within an h8 quoted template, how do I output escaped html?
By just declaring it unescaped, i.e. cast it to unicode. E.g. if you want to output "<tag/>" as part of the content in an html document, you can do ${unicode("<tag/>")}. Of course instead of the literal "<tag/>" string you can unicode() anything you like, even the evoque'ations of any other template.

Do I have to install Qpy?
No, but if you don't you will miss out on the killer feature of automatic cross-site scripting protection. In addition, you add to your risk of Being Called a Bozo When Producing XML. But, if you still really want to, see the no qpy howto.

Why can't I just do $variable instead of having to always specify ${variable}?
Evoque plays defensively. Anything immediately after the $ and before the opening { is reserved for the name of directives, with ${ taken to imply the start of an expression. This way, other directives could be added in the future free of any backawards compatibility issues.

Is there something like a def directive?
Yes. For evoque a def is just a sub-template, defined with a begin/end block. Further nesting of sub-templates, i.e. sub of a sub etc, is also supported. A sub-template defined anywhere may be addressed from anywhere else.

How come there is no include directive?
There is, it is called evoque. You can evoque any text file you like, or any other template or sub-template. By default evoque'd files are evaluated as if they were templates -- to just include raw source evoque with raw=True (see the raw source howto).

How come there is no rawtext directive?
There is, it is called evoque, with parameter raw=True, and if you want a different quoting you can also specify it e.g. quoting="str". You can evoque any template or sub-template, e.g. any begin/end block, or any textual file. See the raw source howto.

What about template inheritance?
Sure, see the overlay directive.

Is it possible to import anything from within a template?
Allowing arbitrary python imports from directly within a template will introduce a lot of difficult issues with restricted execution mode. To support restricted mode, a python application must retain some control on what may be added to the evaluation context and it seems that the simplest way to achieve this is to only allow the main application itself to affect such additions, using the provided methods, e.g. domain.set_namespace_on_globals(). See managing the namespace.

Why isn't there an XML-compliant syntax?
Because Evoque is a generic text templating system, not an XML-based one. However, there is conceptually no problem with providing an XML-compliant syntax, e.g using processing instructions, for directives -- it will only need a modified template parser. Expressions, being already XML-comliant, will require no change.

I do not understand the attribute descriptions I see on this site, for example filters:[callable] or file:either(None, str)?
Those descriptions mimic the syntax that the qp.lib.spec module uses to actually specify attribute allowed values and to perform the corresponding runtime validation checks. Here the clarity of the syntax is only used for documentation purposes. The examples here mean that filters is allowed to be a list of callables (taking some liberty here, as callable is not really a python type), and file may be either None or an instance of str. If you would like to know more, here's a little write-up on the wonderful spec module. Or see the spec.py source directly.

How do I specify the encoding of the output?
You don't. It is always unicode. Once you get the output, you can encode it as you please.

Can Evoque do all that other template systems are able to do?
Most probably, yes. It would help to know the specific task, in which case I'll be more than happy to help identify how it could be done with Evoque.

Integration with external systems

Descriptions, examples for using Evoque Templating with other systems.

django

Using Evoque Templating with Django is very simple. All that is needed is for a view to return a Django HTTPResponse object of an evoque'd template. The commented code recipe below shows one way how to set this up and how to use it. You would need to copy the evoque_django.py module into your django project, and customize as necessary.

Why would you want to use Evoque with Django?

Besides being a full-featured template system, and a legitimate contender for the fastest pure-python text templating engine (see benchmark), it also offers some important features not offered by other engines, such as automatic input quoting and guaranteed XSS protection, restricted template execution mode to be able to expose your templates to untrusted editors, processing is always and only done in unicode, etc. For more, see features.

The code

evoque_django.py

import sys from os.path import join, dirname, abspath import logging from evoque.domain import Domain from django.http import HttpResponse # to add to global namespace from django.core.paginator import ObjectPaginator from my_site import formatting_utils # default template collection root directory DEFAULT_DIR = abspath(join(dirname(__file__), 'templates')) # create template domain instance # here restating all defaults, for doc convenience domain = Domain(DEFAULT_DIR, restricted=False, errors=3, log=logging.getLogger("evoque"), cache_size=0, auto_reload=60, slurpy_directives=True, quoting="xml", input_encoding="utf-8", filters=[] ) # adjust global namespace as needed by application domain.set_on_globals("ObjectPaginator", ObjectPaginator) domain.set_on_globals("site_name", "mysite.net") domain.set_namespace_on_globals("formatting_utils", formatting_utils) # set up any other collection, as needed by application # here restating all defaults -- when None, domain's value is used domain.set_collection("custom", "/home/me/custom_collection/", cache_size=None, auto_reload=None, slurpy_directives=None, quoting=None, input_encoding=None, filters=None) # here we would do any other domain setup we might need # e.g. set a template default for a given collection # for details see: http://evoque.gizmojo.org/usage/ # or the source of the evoque.domain module. def evoque_django_response(request, name, src=None, collection=None, raw=None, data=None, quoting=None, input_encoding=None, filters=None, **kwargs): """ Render an evoque template to a django response object """ # get the template, loading it if necessary. # only name is required but all supported options # are passed on for completeness. template = domain.get_template(name, src=src, collection=collection, raw=raw, data=data, quoting=quoting, input_encoding=input_encoding, filters=filters) # evoque the template and create a django HTTPResponse object: # - evoque'ations are always in unicode, so we decode to utf-8 # - any and all kwargs are added to locals namespace return HttpResponse(template.evoque(raw=raw, quoting=quoting, request=request, **kwargs).encode('utf-8'))

views.py

Just an indication of what you would need to add to your views.py:

from evoque_django import evoque_django_response def some_function_named_in_urlpatterns(request): return evoque_django_response(request, 'some_template.html', title="some title!")

In addition to the title kwarg we can of course specify any others, as needed by the view. They will be transferred to the template's locals evaluation dictionary.

It is important to remember that an Evoque template collection opens up access to all files below the specified folder root. Thus you may choose to organize the templates hierarchically e.g. we could rename and place some_template.html to some_function/template.html (always relative to the collection's root folder). We can then specify to use it in this way:

evoque_django_response(request, 'some_function/template.html')

If for some reason we would wish to use a template that is not part of the default collection, we would just need to specify the collection e.g.

evoque_django_response(request, 'some_template.html', collection="custom")

gz

Gizmo(QP) is an extension of the QP Web Framework. Work on sites made with Gizmo(QP) has been one of the primary motivator for the design choices that went into Evoque. Thus Gizmo(QP) (version >= 0.8) naturally supports Evoque.

Current examples of integration of Evoque with Gizmo(QP) are the online demos for Gizmo(QP).

pylons

Pylons encourages a good separation between the model, the controllers, and the views. It also is very flexible about using different template engines for different views. This note discusses a way for how to set and use Evoque Templating as the default template engine in a Pylons (minimum version 0.9.7) application.

Let's assume we create a site from scratch, let's call it evoque_site. When asked, enter the template_engine as follows:

$ paster create --template=pylons evoque_site Selected and implied templates: Pylons#pylons Pylons application template Variables: egg: evoque_site package: evoque_site project: evoque_site Enter template_engine (mako/genshi/jinja/etc: Template language) ['mako']: evoque Enter sqlalchemy (True/False: Include SQLAlchemy 0.4 configuration) [False]:

adjust application files

We will then need to add the following codes to the files/functions indicated:

evoque_site.config.environment.load_environment(global_conf, app_conf)

# Evoque Templating: # http://evoque.gizmojo.org/ext/pylons/ # http://evoque.gizmojo.org/usage/api/ import logging from evoque.domain import Domain evoque_domain = Domain( # root folder for the default template collection, must be abspath; # as default use pylon's first templates folder os.path.join(root, app_conf.get("evoque.default_dir") or paths['templates'][0]), # whether evaluation namespace is restricted or not restricted = app_conf.get("evoque.restricted")=="true", # how should any evaluation errors be rendered # int 0 to 4, for: [silent, zero, name, render, raise] errors = int(app_conf.get("evoque.errors", 3)), # evoque logger; additional setings should be specified via the pylons # config ini file, just as for any other logger in a pylons application log = logging.getLogger("evoque"), # [collections] int, max loaded templates in a collection cache_size = int(app_conf.get("evoque.cache_size", 0)), # [collections] int, min seconds to wait between checks for # whether a template needs reloading auto_reload = int(app_conf.get("evoque.auto_reload", 60)), # [collections] bool, consume all whitespace trailing a directive slurpy_directives = app_conf.get("evoque.slurpy_directives")=="true", # [collections/templates] str or class, to specify the *escaped* # string class that should be used i.e. if any str input is not of # this type, then cast it to this type). # Builtin str key values are: "xml" -> qpy.h8, "str" -> unicode quoting = app_conf.get("evoque.quoting", "xml"), # [collections/templates] str, preferred encoding to be tried # first when decoding template source. Evoque decodes templates # strings heuristically, i.e. guesses the input encoding. input_encoding = app_conf.get("evoque.input_encoding", "utf-8"), # [collections/templates] list of filter functions, each having # the following signature: filter_func(s:basestring) -> basestring # The functions will be called, in a left-to-right order, after # template is rendered. NOTE: not settable from the conf ini. filters=[] ) # default template (optional) i.e. what to receive # when calling domain.get_template() with no params if app_conf.get("evoque.default_template"): evoque_domain.get_collection().get_template("", src=app_conf.get("evoque.default_template")) # attach evoque domain to app_globals config['pylons.app_globals'].evoque_domain = evoque_domain

development.ini

The above config load_environment() code may be customized on a per-deployment basis, by specifying any of the following parameters under the [app:main] section of the the deployment's .ini file:

# Evoque Domain : http://evoque.gizmojo.org/ext/pylons/ evoque.default_dir = evoque.default_template = evoque.restricted = false evoque.errors = 3 evoque.cache_size = 0 evoque.auto_reload = 2 evoque.slurpy_directives = true evoque.quoting = xml evoque.input_encoding = utf-8

If evoque.default_dir is not set, then Pylon's first templates folder is used.

The filters parameter (on Domain it only serves as site-wide default) is not settable in the ini. For a representative usage example of filters see the Sub-templates in markdown howto.

logging.getLogger("evoque")

Just like any other logger used in a Pylons application, the evoque logger may be adjusted via a deployment's conf ini file.

evoque_site/lib/base.py

from pylons.templating import pylons_globals, cached_template def render_evoque(template_name, extra_vars=None, cache_key=None, cache_type=None, cache_expire=None, collection=None, raw=False, quoting=None): """ Render a template with Evoque : http://evoque.gizmojo.org/ext/pylons/ Accepts the cache options ``cache_key``, ``cache_type``, and ``cache_expire``. The remaining options affect how template is located, loaded and rendered: - template_name: normally this is the collection-root-relative locator for the template; if template had been previously with location specified via the src option, then may be any arbitrary string identifier. - collection: either(None, str, Collection), an existing collection, None implies the default collection - raw: bool, render the raw template source - quoting: either(str, type), sets the quoted-string class to use, None uses the collection's default, "xml" uses qpy.h8, "str" uses unicode """ # Create a render callable for the cache function def render_template(): # Get the globals # Note: when running in restricted mode which of these globals are # to be exposed should be a lot more selective. pg = pylons_globals() # Update any extra vars if needed if extra_vars: pg.update(extra_vars) # Grab a template reference template = pg['app_globals'].evoque_domain.get_template( template_name, collection=collection, raw=raw, quoting=quoting) return template.evoque(pg, raw=raw, quoting=quoting) return cached_template(template_name, render_template, cache_key=cache_key, cache_type=cache_type, cache_expire=cache_expire) render = render_evoque

evoque_site/setup.py

install_requires=[ "Pylons>=0.9.7", ..., "evoque>=0.3" ]

add a controller and template

Let's first add and edit a simple controller and then add a template just to test:

$ paster controller hello

evoque_site/controllers/hello.py

class HelloController(BaseController): def index(self): # Return a rendered template with evoque c.title = "Hello from evoque!" return render("template.html")

evoque_site/templates/template.html

<html> <head><title>evoque + pylons : ${c.title}</title></head> <style type="text/css"> div.code { white-space: pre; font-family: monospace; line-height: 1.1em; padding: 0.6em; margin-bottom: 2em; border: 1px solid #ccc; background-color: #fafafe; } </style> <body> <h2>${c.title}</h2> <h3>template evaluation context</h3> ${inspect()} #[ outputs a <div class="code"/> ]# </body> </html>

Define a route for the hello.index() view, and load it in your web browser. You should see a simple page that dumps the template evaluation context.

qp

Integrating Evoque Templating with the QP Web Framework is pretty straightforward. As an explanation, here's the echo demo site, included in the QP distribution, modified to use Evoque. The changes are probably self-explanatory, but for the record here's a commentary on each one:

  • First of all slash.qpy is renamed to slash.py as the source code is now 100% python.
  • We import (first 2 import lines) what we need to be able to set up an Evoque domain.
  • The functions qp.pub.common.header and qp.pub.common.footer are no longer needed, so are not imported. Instead, we do import qp.pub.common.page, and the template used for each page rendering will take care of its own header and footer.
  • We override Publisher.__init__() to be able to set up an Evoque domain for the site, via the separately specified set_domain() method.
    • We designate the evoque/ sub-folder as root for our default template collection, but this can be anywhere on the file system. We may of course also specify other collections.
    • We extend the evaluation global namespace as we need to, here we only will need the pformat utility.
    • We set a template under a name that we will use as default.
  • We override Publisher.page() so that it knows how to get and evoque a template. Note that the page() API remains identical -- except for the added optional template keyword by which an export may select which template to use. If no template keyword is specified on page() then the default template name is used.
  • We migrate the presentation code from SiteDirectory.index() to the items.html template. We also tell the index() method to use the items.html template.
  • Just for the heck of it, we also make items.html an overlay, overriding only the content sub-template, on top of base.html -- that takes over the functionality of the header() and footer() QP functions while also offering additional flexibility, e.g. may have as many base templates as desired, or a base template can be more complex than just to handle a header and a footer.

The code - slash.py

import sys from os.path import join, dirname, abspath import logging from evoque.domain import Domain from qp.pub.publish import Publisher from qp.pub.common import get_request, page from qp.fill.directory import Directory from pprint import pformat class SitePublisher(Publisher): configuration = dict( http_address=('', 8001), as_https_address=('localhost', 9001), https_address=('localhost', 10001), scgi_address=('localhost', 11001), ) def __init__(self, **kwargs): super(SitePublisher, self).__init__(**kwargs) self.set_domain() def set_domain(self): default_dir = abspath(join(dirname(__file__), 'evoque')) # create template domain instance # here restating all defaults, for doc convenience self.domain = Domain(default_dir, restricted=False, errors=3, log=logging.getLogger("evoque"), cache_size=0, auto_reload=60, slurpy_directives=True, quoting="xml", input_encoding="utf-8", filters=[] ) # extensions to global namespace self.domain.set_on_globals("pformat", pformat) # preload a default template, e.g. for error pages self.domain.get_collection().get_template("", "base.html") # other setup e.g. adding other collections, template aliases # see: http://evoque.gizmojo.org/usage/ def page(self, title, *content, **kwargs): """(title, *content, **kwargs) -> h8 Return a page formatted according to the site-standard. """ # we make use of only the "template" kwarg -- there are # other possibilities, see: domain.get_template() template = kwargs.get("template", "") return self.domain.get_template(template).evoque( title=title, content=content, **kwargs) class SiteDirectory(Directory): """ This site displays the http request. """ def get_exports(self): yield '', 'index', None, None def index(self): items = get_request().__dict__.items() items.sort() return page('HTTP Request', template="items.html", items=items) def _q_traverse(self, components): return self.index()

The templates

base.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <title>${title}</title> </head> <body class="standard"> $begin{content} ${content} $end{content} $evoque{#content} </body> </html>

items.html

$overlay{base.html} $begin{content} <h1>${title}</h1> <div style="margin:1em; padding:1em; border:1px solid gray;"> <dl> $for{ key, value in items } <dt>${key}</dt> <dd style="white-space:pre">${pformat(value)}</dd> $rof </dl> </div> $end{content}

werkzeug

Werkzeug is a collection of WSGI utility modules. The werkzeug web site presents (on welcome page click on nutshell to see the code) a very simple demo application, that is described as:

A tiny WSGI application (about 50 lines of code) that just uses werkzeug’s routing system and uses template names as endpoints. These templates are then loaded with Mako, rendered and returned as responses.

It is really trivial to make an equivalent application that uses Evoque instead. The changes to the two concerned files werkzeug_nutshell.py and say_hello.html, shown further below, are superficial:

  • Initialize an Evoque domain, thus implicitly also defining the default template collection.
  • Evoque'ing the template is identical, except for a different method name.
  • In the sample say_hello.html template, remove the unnecessary h filter (Evoque automatically quotes all incoming unquoted data).
  • Make the sample say_hello.html template a valid HTML document ;-)

werkzeug_nutshell.py

#!/usr/bin/env python # -*- coding: utf-8 -*- from os import path from werkzeug import BaseRequest, BaseResponse, \ SharedDataMiddleware, responder from werkzeug.routing import Map, Rule from werkzeug.exceptions import HTTPException from evoque.domain import Domain # path for this file's folder # used as root for default template collection root_path = path.abspath(path.dirname(__file__)) # create an evoque template domain (and default collection) domain = Domain(root_path) # create the URL map. The endpoints are template names without the # .html extension, to be rendered by the `dispatch_request` function. url_map = Map([ Rule('/', endpoint='index'), Rule('/hello/', defaults={'name': 'World'}, endpoint='say_hello'), Rule('/hello/<name>', endpoint='say_hello'), Rule('/shared/<file>', endpoint='shared', build_only=True) ]) def dispatch_request(environ, start_response): """ A simple dispatch function that (if decorated with responder) is the complete WSGI application that does the template rendering and error handling. """ # first we bind the url map to the current request adapter = url_map.bind_to_environ(environ) # then we wrap all the calls in a try/except for HTTP exceptions try: # get the endpoint and the values (variable or parts) # of the adapter. If the match fails it raises a NotFound # exception which is a HTTPException which we catch endpoint, values = adapter.match() # create a new request object for the incoming WSGI environ request = BaseRequest(environ) # create an empty response object with the correct mimetype. response = BaseResponse(mimetype='text/html') # get the template and render it. Pass some useful stuff to # the template (request and response objects, the current # url endpoint, the url values and a url_for function which # can be used to generate urls template = domain.get_template(endpoint + '.html') response.write(template.evoque( request=request, response=response, endpoint=endpoint, url_for=lambda e, **v: adapter.build(e, v), url_values=values )) # return the response return response except HTTPException, e: # if an http exception is caught we can return it as # response because those exceptions render standard error # messages under wsgi return e # finish the wsgi application by wrapping it in a middleware that # serves static files in the shared folder and wrap the dispatch # function in a responder so that the return value is called as # wsgi application. application = SharedDataMiddleware(responder(dispatch_request), { '/shared': path.join(root_path, 'shared') }) # if the script is called from the command line start the # application with the development server on localhost:4000 if __name__ == '__main__': from werkzeug import run_simple run_simple('localhost', 4000, application)

say_hello.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head> <title>Hello World Fun!</title> </head><body> <h1>Hello ${url_values['name']}!</h1> <p><a href="${url_for('index')}">back to the index</a></p> </body></html>

Under consideration

For generation of XML documents, add filters for validating the generated output for both document (page) and fragment levels. Should be easy to turn on and off, e.g. for development or for deployment.

Cache the compiled template on disk, for the benefit of faster startup times, or usage in a cgi environment.

Introspection into the collections and their respective caches of loaded templates.

Consider supporting a more convenient way to specify filters on expressions, e.g. ${ expr | url, trim, etc } would do the same thing as the current ${etc(trim(url(expr)))}.

Improve ability to "extract" pieces of text from files as the source of a template, without needing that those files be touched in any way. Criteria could be pairs of regular expressions, line numbers, format-specific parsing directives, combination of criteria, etc. Could be implemented as an extension to the evoque() built-in callable, or as a new callable such as extract().

Support also for python 3.0, maybe from the same Evoque python 2.5 code base.

Evoque copyright and license

Download and support

Single-page documentation

For printing convenience, this entire site may also be evoque'd as a single page:

Typo reports, suggestions, observations are appreciated!

Download Evoque version 0.3 (2008-09-06)

You may wish to take a look at the change log for this and previous releases.

It is also highly recommended to download and install the Qpy unicode templating utility that provides the qpy.h8 quoting class for automatic input escaping. You may prefer to use a command line client such as wget to download these, as indicated below.

Installation

To install, you may either use easy_install or standard python distutils (i.e. download, unpack, install) as detailed below:

# Installing with easy_install easy_install evoque easy_install qpy # optional, recommended # Installing with distutils wget http://gizmojo.org/dist/evoque-0.3.tar.gz tar zxvf evoque-0.3.tar.gz cd evoque-0.3 python setup.py install cd .. # qpy, optional, recommended wget http://www.mems-exchange.org/software/files/qpy/qpy-1.6.tar.gz tar xzf qpy-1.6.tar.gz cd qpy-1.6 python setup.py install # Test python evoque-0.3/test/test_evoque.py

Acknowledgements

Thank you to MEMS and Nanotechnology Exchange for the great software they have made available to the python community -- the Qpy package that is used by Evoque is one of these packages. In particular, a thank you to David Binger for his intense and infectious appreciation of simplicity, and therefore complexity.

Thank you to Skip Montanaro and to Martin v. Löwis for their help with the decodeh module, now part of Evoque.

The physical source lines of code (SLOC) count is generated using SLOCCount by David A. Wheeler.

Steven Degraeve for the heading area background banner on the pages of this site.

The multitude of templating systems for python over the past years have contributed to the clarification of the ideas and priorities for Evoque. It is impossible to identify which and what -- a big thank you to the python community in general for the openness and sharing of innumerable ideas and disinterested hours of work.

Thank you to Alex Martelli for his help and collaboration on the development of XYAPTU: Lightweight XML/HTML Document Template Engine for Python. This single-module templating system, written back in 2002, is the beginning of the ideas that have evolved over the years since then and that are now manifest in Evoque.