:mod:`ptemplate` --- lightweight, data-driven templating
========================================================
.. automodule:: ptemplate
Installing :mod:`ptemplate`
---------------------------
.. highlight:: none
You can install the latest stable version of :mod:`ptemplate` using :command:`pip`::
$ pip install ptemplate
Mirrors of the project's repository are hosted at `github.com`_ and `bitbucket.org`_
-- feel free to clone and issue pull requests at either service. For `git`_
users::
$ git clone http://github.com/wcmaier/ptemplate.git
.. _github.com: http://github.com/wcmaier/ptemplate.git
.. _bitbucket.org: http://bitbucket.org/wcmaier/ptemplate/
.. _git: http://git-scm.com/
And for `Mercurial`_ users::
$ hg clone http://bitbucket.org/wcmaier/ptemplate/
.. _Mercurial: http://mercurial.selenic.com/
Both github and bitbucket provide feeds of changes to the project; for example::
http://github.com/wcmaier/ptemplate/commits/master.atom
If you'd like to contribute an improvement to :mod:`ptemplate`, please consider using
either of the above services. Otherwise, send an email to willmaier@ml1.net.
A quick tour of :mod:`ptemplate`'s features
-------------------------------------------
.. highlight:: python
Simple extension of Python's advanced string formatting API (:pep:`3101`)::
>>> from ptemplate.formatter import Formatter
>>> formatter = Formatter()
>>> template = """normal variable substitution: {var}"""
>>> formatter.format(template, var="foo")
'normal variable substitution: foo'
>>> template = """{#section}this is {something} {/section}"""
>>> data = {"section": [{"something": "fast"}, {"something": "easy"}, {"something": "simple"}]}
>>> formatter.format(template, **data)
'this is fast this is easy this is simple '
>>> template = """{%this is a comment}"""
>>> formatter.format(template, **{})
''
Content filtering system (eg HTML sanitization)::
>>> import cgi
>>> from ptemplate.template import Template
>>> templater = Template()
>>> templater.converters["h"] = cgi.escape
>>> data = {"content": """
some HTML from the wild
"""}
>>> templater.template = """something dangerous: {content!h}"""
>>> templater.render(data)
'something dangerous: <p>some HTML from the wild</p>'
>>> data = {"section": [{"example": "paragraph
"},{"example": "no html here"}]}
>>> templater.template = """you can sanitize entire sections, too ({#section!h}{example} {/section})"""
>>> templater.render(data)
'you can sanitize entire sections, too (<p>paragraph</p> no html here )'
Basic compatibility with Google's _`ctemplate`::
>>> from ptemplate.ctemplate import CTemplate
>>> template = """Google is {{#section}}{{something}}, {{/section}}"""
>>> templater = CTemplate(template=template)
>>> data = {"section": [{"something": "not evil"},{"something": "a company"}]}
>>> templater.render(data)
'Google is not evil, a company, '
.. _ctemplate: http://code.google.com/p/google-ctemplate/
Basic usage
-----------
Despite its simple syntax, templates processed by :mod:`ptemplate` can still
produce very complex documents. Unlike most template systems, :mod:`ptemplate`
encourages you to keep application logic where it belongs -- in your
application. Expansion of the template is controlled solely by the structure of
your data. This keeps the templating engine itself simple and fast and makes
writing and debugging your templates even easier. For more on the philosophy
behind data-driven templating systems, see "`How To Use the Google Template
System `_".
All of the :mod:`ptemplate` interfaces take a single data dictionary and use it
to expand a simple string template. The data dictionary's keys are plain
strings; its values are either strings or lists of other data dictionaries.
Other objects may be used as values, but they will be converted to strings
before being inserted into the template. String values are substituted for
variables in the template. List values control the number of iterations (and
scope) of section variables.
Templates are composed of plain text with (optional) variable and section
markers. :mod:`ptemplate` markers must all be valid field names as described
in :pep:`3101` and begin and end with '{' and '}', respectively. Special
fields denoting comments and the beginning and end of sections are marked
with a single-character indicator ('!', '#', '/'). Comment fields are ignored
completely. Sections are expanded once for each data dictionary found in the
evaluation scope.
Variable expansion proceeds from the innermost context to the outermost; if no
match is found, an empty string is substituted instead. Variables defined within
a section will first attempt to match keys in that section's dictionary. If no
match is found, the dictionary in which the section is defined will be searched
for a match. This process continues until all dictionaries (or "scopes") are
exhausted.
For example, this deeply nested data dictionary produces the following::
>>> from ptemplate.template import Template
>>> templater = Template()
>>> data = {
... "outer": [
... {"foo": "foo",
... "middle": [
... {"foo": "bar",
... "inner": [
... {"foo": "baz"},
... ]},
... ]},
... ]}
>>> template = """{#outer}outer: {foo} {#middle}middle: {foo} {#inner}inner: {foo} {/inner}{/middle}{/outer}"""
>>> templater.template = template
>>> templater.render(data)
'outer: foo middle: bar inner: baz '
At each level, a new value for "foo" is defined. If the innermost value is
removed, though, the next-highest value is used::
>>> data["outer"][0]["middle"][0]["inner"][0].pop("foo")
'baz'
>>> templater.render(data)
'outer: foo middle: bar inner: bar '
ctemplate support
-----------------
Since :mod:`ptemplate` and Google's ctemplate are so similar internally,
it's fairly easy to process ctemplate templates with :mod:`ptemplate`.
In fact, with a very naive preprocessor, :mod:`ptemplate.ctemplate` can
pass many of Google's ctemplate unittests. To expand ctemplate(-like)
templates, use :class:`ptemplate.ctemplate.CTemplate` instead of
:class:`ptemplate.template.Template`.
.. note::
:class:`ptemplate.ctemplate.CTemplate` does not provide full ctemplate
compatibility. See the class documentation for specific exceptions.
API
---
You have access to the templating system at three levels. First, you can
interact directly with :class:`ptemplate.formatter.Formatter`, which
offers an interface very similar to Python's advanced string formatting
(:class:`string.Formatter`, :pep:`3101`). Second, you can work with (or
subclass) :class:`ptemplate.template.Template`, a thin wrapper around
the formatter that also adds a Buffet-style API. Lastly, you can use
:class:`ptemplate.ctemplate.CTemplate`. :class:`~ptemplate.ctemplate.CTemplate`
extends :class:`~ptemplate.template.Template` with a preprocessor that supports
a subset of Google's ctemplate system. :mod:`~ptemplate.ctemplate` also
demonstrates how to subclass and extend the basic :mod:`~ptemplate.template`.
For a typical use case, :mod:`ptemplate.template` is probably most useful as it
provides a familiar API. :mod:`ptemplate.ctemplate` should (with, perhaps, a few
changes to the preprocessor) support many simple templates written for Google's
ctemplate system. For smaller projects (or doing things like formatting error
messages), :mod:`ptemplate.formatter` may be sufficient on its own.
.. automodule:: ptemplate.formatter
:members: Formatter
:show-inheritance:
.. autoclass:: ptemplate.formatter.Section
.. autoclass:: ptemplate.formatter.Token
.. automodule:: ptemplate.template
:members:
:show-inheritance:
.. automodule:: ptemplate.ctemplate
:members:
:show-inheritance:
Running the benchmark
---------------------
The :mod:`ptemplate` repository contains a port of the `Genshi benchmark suite`_.
To run the benchmarks::
$ cd tests/bench
$ python basic.py | grep -v "not installed" | sort -n -k1.40bn
$ python bigtable.py | grep -v "not installed" | sort -k1.40bn
You can also run benchmarks against selected template engines by passing the
engine name (ie "ptemplate", "ctemplate") as an argument to the benchmark
script.
.. _Genshi benchmark suite: http://genshi.edgewall.org/wiki/GenshiPerformance
As of 2010.04.15, the benchmark produced the following results (sorted by
''bigtable.py''):
=============== ======= =============== ======================= ===============================
Engine Name Version basic.py (ms) bigtable.py (ms) Notes
=============== ======= =============== ======================= ===============================
Mako 0.3.2 0.71 190.21
Genshi_text 0.5.1 2.65 554.41
Ptemplate 0.1 3.42 1188.61
Ctemplate 0.1 3.64 1296.58
Genshi 0.5.1 7.75 1228.97 template
Genshi 0.5.1 n/a 1566.21 tag builder
Django 1.1.1 6.29 1872.76
Genshi 0.5.1 n/a 1996.71 template + tag builder
Kid 0.9.6 15.34 3508.88
=============== ======= =============== ======================= ===============================
The test system was:
* AMD Athlon(tm) 64 Processor 3200+ ("AuthenticAMD" 686-class, 512KB L2 cache)
* 1 GB RAM
* OpenBSD 4.7-current
* Python 2.6.3
See also
--------
There have been several other efforts to bring ctemplate-like templates to
Python. For example:
* `pystache`_, which implements the `Mustache`_ syntax; and
* `python-ctemplate`_, which wraps ctemplate's C++ interface.
.. _pystache: http://github.com/defunkt/pystache/
.. _Mustache: http://mustache.github.com/
.. _python-ctemplate: http://code.google.com/p/python-ctemplate/