pyTenjin User's Guide

release: 0.8.1
last update: $Date$

Table of Contents:

Introduction

Overview

pyTenjin is a very fast and lightweight template engine based on embedded Python. You can embed Python statements and expressions into your text file. pyTenjin converts it into Python script and evaluate it.

The following is an example of pyTenjin.

File 'ex.pyhtml':
Hello #{name}!
<ul>
<?py for item in items: ?>
 <li>${item}</li>
<?py #end ?>
</ul>

Here is the notation:

<?py ... ?>
Python statements.
#{...}
Python expression.
${...}
Python expression (HTML escaped)
Result of covertion into Python script:
$ pytenjin -s ex.pyhtml
_buf = []; _buf.extend(('''Hello ''', to_str(name), '''!
<ul>\n''', ));
for item in items:
    _buf.extend((''' <li>''', escape(to_str(item)), '''</li>\n''', ));
#end
_buf.extend(('''</ul>\n''', ));
print ''.join(_buf)
Output of execution with context data:
$ pytenjin -c "name='World'; items=['<AAA>','B&B','\"CCC\"']" ex.pyhtml
Hello World!
<ul>
 <li>&lt;AAA&gt;</li>
 <li>B&amp;B</li>
 <li>&quot;CCC&quot;</li>
</ul>
Example of Python script
import tenjin
from tenjin.helpers import *   # or escape, to_str
engine = tenjin.Engine()
context = { 'name': 'World', 'items': ['<AAA>', 'B&B', '"CCC"'] }
output = engine.render('ex.pyhtml', context)
print output,

Features

pyTenjin has the following features:


Comparison with other solutions

pyTenjin has advantages compared with other solutions (including other language solutions):


Benchmark

Benchmark script is contained in pyTenjin archive. The following is an example of benchmark.

MacOS X 10.4 Tiger, Intel CoreDuo 1.83GHz, Memory 2GB
$ cd pytenjin-X.X.X/benchmark
$ python -V
Python 2.5.1
$ python bench.py -q -n 10000
Compiling bench_cheetah.tmpl -> bench_cheetah.py (backup bench_cheetah.py.bak)
*** ntimes=10000
                           utime      stime      total       real
tenjin                   6.47000    0.49000    6.96000    6.98909
tenjin-reuse             5.54000    0.06000    5.61000    5.63055
tenjin-nocache          20.14000    0.41000   20.55000   20.60475
django                  69.99000    1.34000   71.33000   71.57211
django-reuse            58.92000    0.88000   59.80000   59.94480
cheetah                 20.33000    0.03000   20.36000   20.41416
cheetah-reuse           19.80000    0.02000   19.82000   19.86858
myghty                 106.25000    1.63000  107.88000  108.16097
myghty-reuse            18.70000    0.60000   19.30000   19.35395
kid                    379.64000    0.60000  380.24000  381.11728
kid-reuse              378.52000    0.44000  378.96000  379.64911
genshi                 557.29000    3.00000  560.30000  561.71955
genshi-reuse           270.47000    1.22000  271.69000  272.26885
mako                    16.82000    0.96000   17.78000   18.36388
mako-reuse              13.47000    0.02000   13.49000   13.51232
mako-nocache           236.10000    1.67000  237.77000  238.38705
templetor              424.03000    4.15000  428.19000  429.59667
templetor-reuse         61.46000    0.07000   61.53000   61.68483

Versions:

This shows the followings.



Installation

pyTenjin works on CPython (2.3 or higher, 3.0 supported).

  1. Download pyTenjin-X.X.X.tar.gz and extract it.
  2. Just type 'python setup.py install' with administrator or root user, or copy 'lib/tenjin.py' and 'bin/pytenjin' into proper directory if you don't have administrator or root priviledge.
  3. (Optional) Install PyYAML.

Designer's Guide

This section shows how to use pyTenjin for designer.

If you want to know how to use pyTenjin in your program, see Developer's Guide section.

Notation

The following is the notation of pyTenjin.

File 'example1.pyhtml':
<table>
  <tbody>
<?py i = 0 ?>
<?py for item in ['<foo>', 'bar&bar', '"baz"']: ?>
<?py     i += 1 ?>
    <tr>
      <td>#{item}</td>
      <td>${item}</td>
    </tr>
<?py #end ?>
  <tbody>
</table>

Notice that it is required to add '<?py #end ?>' line because Python doesn't have block-end mark. Block-end mark line tells pytenjin command the position of end of block. It is able to use '#endfor', '#', 'pass', and so on as block-end mark.

The following is the result of executing 'example1.pyhtml'.

Result:
$ pytenjin example1.pyhtml
<table>
  <tbody>
    <tr>
      <td><foo></td>
      <td>&lt;foo&gt;</td>
    </tr>
    <tr>
      <td>bar&bar</td>
      <td>bar&amp;bar</td>
    </tr>
    <tr>
      <td>"baz"</td>
      <td>&quot;baz&quot;</td>
    </tr>
  <tbody>
</table>

(Experimental) Since version 0.8.0, it is allowed to indent Python statements. pyTenjin inserts dummy if-statement automatically when the first Python statement is indented.

For example:

<html>
  <body>
    <ul>
      <?py for item in items: ?>
      <li>${item}</li>
      <?py #end ?>
    </ul>
  </body>
</html>

The above is regarded as:

<html>
  <body>
    <ul>
<?py if True: ?>
      <?py for item in items: ?>
      <li>${item}</li>
      <?py #end ?>
    </ul>
  </body>
</html>

Notice: dummy if-statement changes error line number. For example, if got error report on line 6, the real line number in template file is 5.


Embedded Statement Styles

Two styles of embedded statement are available. The first style is shown in the previous section. In this style, it is able to put indent spaces before '<?py' like the following:

File 'example1a.pyhtml':
<table>
  <tbody>
<?py i = 0 ?>
<?py for item in ['<foo>', 'bar&bar', '"baz"']: ?>
    <?py i += 1 ?>
    <tr>
      <td>#{item}</td>
      <td>${item}</td>
    </tr>
<?py #end ?>
  </tbody>
</table>

The second style is shown in the following. This style is convenient for a lot of statements.

File 'example1b.pyhtml':
<table>
  <tbody>
<?py
i = 0
for item in ['<foo>', 'bar&bar', '"baz"']:
    i += 1
?>
    <tr>
      <td>#{item}</td>
      <td>${item}</td>
    </tr>
<?py
#end
?>
  </tbody>
</table>

It is able to mix two styles in a file, but the following styles are NOT available.

Invalid example #1:
<ul>
<?py i = 0
     for item in ['<foo>', 'bar&bar', '"baz"']:
     i += 1 ?>
 <li>#{item}
     ${item}</li>
<?py #end ?>
</ul>
Invalid example #2:
<ul>
<?py
    i = 0
    for item in ['<foo>', 'bar&bar', '"baz"']:
        i += 1
?>
 <li>#{item}
     ${item}</li>
<?py
    #end
?>
</ul>

Convert into Python Code

Command-line option '-s' converts embedded files into Python code.

Result:
$ pytenjin -s example1.pyhtml
_buf = []; _buf.extend(('''<table>
  <tbody>\n''', ));
i = 0
for item in ['<foo>', 'bar&bar', '"baz"']:
    i += 1
    _buf.extend(('''    <tr>
      <td>''', to_str(item), '''</td>
      <td>''', escape(to_str(item)), '''</td>
    </tr>\n''', ));
#end
_buf.extend(('''  <tbody>
</table>\n''', ));
print ''.join(_buf)

'_buf = [];' is called as preamble and 'print ''.join(_buf)' is called as postamble. Command-line option '-b' removes preamble and postamble.

File 'example2.pyhtml'
<?py for i in [1, 2, 3]: ?>
<p>#{i}</p>
<?py #end ?>
Result:
$ pytenjin -s example2.pyhtml
_buf = []
for i in [1, 2, 3]:
    _buf.extend(('''<p>''', to_str(i), '''</p>\n''', ));
#end
print ''.join(_buf)
$ pytenjin -sb example2.pyhtml
for i in [1, 2, 3]:
    _buf.extend(('''<p>''', to_str(i), '''</p>\n''', ));
#end

Command-line option '-S' also show converted Python code but it doesn't print text part. This is useful to check Python code for debugging.

Result:
$ pytenjin -S example1.pyhtml
_buf = []; 

i = 0
for item in ['<foo>', 'bar&bar', '"baz"']:
    i += 1

          to_str(item); 
          escape(to_str(item)); 

#end


print ''.join(_buf)

In addition, the following command-line options are available.

-N
Add line number.
-X
Delete expressions.
-C
Remove empty lines (compact-mode).
-U
Compress empty lines to a line (uniq-mode).
Result:
$ pytenjin -SUNX example1.pyhtml
    1:  _buf = []; 

    3:  i = 0
    4:  for item in ['<foo>', 'bar&bar', '"baz"']:
    5:      i += 1

   10:  #end

   13:  print ''.join(_buf)
(*1)
Difference between escape() and cgi.escape() is that the former escapes double-quotation mark into '&quot;' and the latter doesn't.

Syntax Checking

Command-line option '-z' checks syntax error in embedded Python code.

File example3.pyhtml:
<ul>
<?py for item in items: ?>
 <li>${item}</li>
<?py   #end ?>
</ul>
Result:
$ pytenjin -z example3.pyhtml
example3.pyhtml:4:3: unindent does not match any outer indentation level
  4:   #end
       ^

Error message is the same format as gcc compiler or java compiler. Error jump in Emacs or other editor is available.

Command-line option '-q' (quiet-mode) prints nothing if it has no errors.


HTML Helper Functions

pyTenjin provides small but useful HTML helper functions. See this section for details.

example of HTML helper functions:
<table>
<?py cycle = new_cycle('odd', 'even') ?>
<?py curr_id = params['item_id'] ?>
<?py for item in items: ?>
  <tr class="#{cycle()}" #{tagattrs(style=(item.readonly and 'color:gray'))}>
    <td><input type="checkbox" name="items" value="#{item.id}" #{checked(curr_id==item.id)} /></td>
    <td>${item.name}</td>
  </tr>
<?py #endfor ?>
</table>
output example:
<table>
  <tr class="odd" >
    <td><input type="checkbox" name="items" value="1"  /></td>
    <td>Foo</td>
  </tr>
  <tr class="even"  style="color:gray">
    <td><input type="checkbox" name="items" value="2"  checked="checked" /></td>
    <td>Bar</td>
  </tr>
</table>

Context Data File

pyTenjin allows you to specify context data by YAML file or Python script.

File 'example4.pyhtml':
<p>
  ${text}
  #{num}
  #{flag}
</p>

<?py for item in items: ?>
<p>${item}</p>
<?py #end ?>

<?py for key, value in hash.items(): ?>
<p>#{key} = ${value}</p>
<?py #end ?>
File 'datafile.yaml':
text:   foo
num:    3.14
flag:   yes
items:
  - foo
  - bar
  - baz
hash:
  x: 1
  y: 2
Result:
$ pytenjin -f datafile.yaml example4.pyhtml
<p>
  foo
  3.14
  True
</p>

<p>foo</p>
<p>bar</p>
<p>baz</p>

<p>y = 2</p>
<p>x = 1</p>
File 'datafile.py':
text  = "foo"
num   = 3.14
flag  = True
items = ["foo", "bar", "baz"]
hash  = {"x":1, "y":2}
Result:
$ pytenjin -f datafile.py example4.pyhtml
<p>
  foo
  3.14
  True
</p>

<p>foo</p>
<p>bar</p>
<p>baz</p>

<p>y = 2</p>
<p>x = 1</p>

You must install PyYAML if you want to use YAML-format context data file.


Command-line Context Data

Command-line option '-c' specifies context data in YAML format or Python code.

File 'example5.pyhtml':
text:  #{text}
items:
<?py for item in items: ?>
  - #{item}
<?py #end ?>
hash:
<?py for key, val in hash.items(): ?>
  #{key}: #{val}
<?py #end ?>
Result of context data in python code:
$ pytenjin -c 'text="foo"; items=["a","b","c"]; hash={"x":1,"y":2}' example5.pyhtml
text:  foo
items:
  - a
  - b
  - c
hash:
  y: 2
  x: 1
Result of context data in yaml format:
$ pytenjin -c '{text: foo, items: [a, b, c], hash: {x: 1, y: 2} }' example5.pyhtml
text:  foo
items:
  - a
  - b
  - c
hash:
  y: 2
  x: 1

You must install PyYAML at first if you want to specify context data in YAML format.


Nested Template

Template can include other templates. Included templates can also include other templates.

The following function is available to include other templates.

include(str template_name)
Include other template.
File 'example6.pyhtml':
<html>
  <body>

    <div id="sidemenu">
<?py include('sidemenu.pyhtml') ?>
    </div>

    <div id="main-content">
<?py for item in items: ?>
      <p>${item}</p>
<?py #end ?>
    </div>

    <div id="footer">
#{include('footer.pyhtml', False)}
    </div>

  </body>
</table>
File 'sidemenu.pyhtml':
<ul>
<?py for item in menu: ?>
  <li><a href="${item['url']}">${item['name']}</a></li>
<?py #end ?>
</ul>
File 'footer.pyhtml':
<hr />
<address>
  <a href="mailto:${webmaster_email}">${webmaster_email}</a>
</address>
File 'contextdata.py':
items = [ '<FOO>', '&BAR', '"BAZ"' ]
webmaster_email = 'webmaster@example.com'
menu  = [
    {'name': 'Top',      'url': '/' },
    {'name': 'Products', 'url': '/prod' },
    {'name': 'Support',  'url': '/support' },
]
Result:
$ pytenjin -f contextdata.py example6.pyhtml
<html>
  <body>

    <div id="sidemenu">
<ul>
  <li><a href="/">Top</a></li>
  <li><a href="/prod">Products</a></li>
  <li><a href="/support">Support</a></li>
</ul>
    </div>

    <div id="main-content">
      <p>&lt;FOO&gt;</p>
      <p>&amp;BAR</p>
      <p>&quot;BAZ&quot;</p>
    </div>

    <div id="footer">
<hr />
<address>
  <a href="mailto:webmaster@example.com">webmaster@example.com</a>
</address>

    </div>

  </body>
</table>

Function 'include()' can take template filename (ex. 'user_main.pyhtml') or template short name (ex. ':main'). Template short name represents a template in short notation. It starts with colon (':').

To make template short name available, command-line option '--prefix' and '--postfix' are required. For example, 'include("user_main.pyhtml")' can be described as 'include(":main")' when '--prefix="user_"' and '--postfix=".pyhtml"' are specified in command-line.


Layout Template

Command-line option '--layout=templatename' specifies layout template name.

For example, 'exmample6.pyhtml' template in the previous section can be divided into layout file 'layout6.pyhtml' and content file 'content6.pyhtml'. Variable '_content' in layout template represents the result of content file.

File 'layout6.pyhtml':
<html>
  <body>

    <div id="sidemenu">
<?py include('sidemenu.pyhtml') ?>
    </div>

    <div id="main-content">
#{_content}
    </div>

    <div id="footer">
#{include('footer.pyhtml', False)}
    </div>

  </body>
</table>
File 'content6.pyhtml':
<?py for item in items: ?>
  <p>${item}</p>
<?py #end ?>
Result:
$ pytenjin -f contextdata.py --layout=layout6.pyhtml content6.pyhtml
<html>
  <body>

    <div id="sidemenu">
<ul>
  <li><a href="/">Top</a></li>
  <li><a href="/prod">Products</a></li>
  <li><a href="/support">Support</a></li>
</ul>
    </div>

    <div id="main-content">
  <p>&lt;FOO&gt;</p>
  <p>&amp;BAR</p>
  <p>&quot;BAZ&quot;</p>

    </div>

    <div id="footer">
<hr />
<address>
  <a href="mailto:webmaster@example.com">webmaster@example.com</a>
</address>

    </div>

  </body>
</table>

Target template and layout template don't share local variables. It means that local variables set in a template are not available in layout template.

If you want variables set in a temlate to be available in layout template, you should use '_context' dict.

File 'layout7.pyhtml':
...
<h1>${title}</h1>

<div id="main-content">
#{_content}
<div>

<a href="${url}">Next page</a>
...
File 'content7.pyhtml':
<?py _context['title'] = 'Document Title' ?>
<?py _context['url'] = '/next/page' ?>
<table>
  ...content...
</table>
Result:
$ pytenjin --layout=layout7.pyhtml content7.pyhtml
...
<h1>Document Title</h1>

<div id="main-content">
<table>
  ...content...
</table>

<div>

<a href="/next/page">Next page</a>
...

Using '_context["_layout"]', it is able to specify layout template name in each template file. If you assigned False to '_context["_layout"]', no layout template is used.

File 'content8.pyhtml':
<?py _context['_layout'] = ":layout8_xhtml" ?>
<h1>Hello World!</h1>
File 'layout8_html.pyhtml':
<html>
  <body>
#{_content}
  </body>
</html>
File 'layout8_xhtml.pyhtml':
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <body>
#{_content}
  </body>
</html>
Result: ':layout8_html' is specified in command-line option but ':layout8_xhtml' is used
$ pytenjin --postfix='.pyhtml' --layout=':layout8_html' content8.pyhtml
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <body>
<h1>Hello World!</h1>

  </body>
</html>

Capturing

It is able to capture any part of template.

File 'example9.pyhtml':
<?py _context['title'] = 'Capture Test' ?>
<html>
  <body>

<?py start_capture('content_part') ?>
    <ul>
<?py for i in [0, 1, 2]: ?>
      <li>i = #{i}</li>
<?py #endfor ?>
    </ul>
<?py stop_capture() ?>

<?py start_capture('footer_part') ?>
    <div class="footer">copyright&copy; 2007 kuwata-lab.com</div>
<?py stop_capture() ?>

  </body>
</html>

Captured strings are accessable as local variables. For example, you can get captured string as a variable 'content_part' in the above example.

A template can contain several capturing. It is not able to nest capturing.

In layout file, it is able to use strings captured in templates.

File 'layout9.pyhtml':
<html lang="en">
  <head>
    <title>${title}</title>
  </head>
  <body>

    <!-- HEADER -->
<?py if not captured_as('header_part'): ?>
    <h1>${title}</h1>
<?py #endif ?>
    <!-- /HEADER -->

    <!-- CONTENT -->
#{content_part}
    <!-- /CONTENT -->

    <!-- FOOTER -->
<?py if not captured_as('footer_part'): ?>
    <hr />
    <address>webmaster@localhost</address>
<?py #endif ?>
    <!-- /FOOTER -->

  </body>
</html>

'if not captured_as("name"): ... #endif' is equivarent to the following.

<?py if 'name' in _context: ?>
<?py     _buf.append(_context['name']) ?>
<?py else: ?>
         ...
<?py #endif ?>

The following result shows that content part and footer part are overrided by capturing in content template but header part is not.

Result:
$ pytenjin --layout=layout9.pyhtml example9.pyhtml
<html lang="en">
  <head>
    <title>Capture Test</title>
  </head>
  <body>

    <!-- HEADER -->
    <h1>Capture Test</h1>
    <!-- /HEADER -->

    <!-- CONTENT -->
    <ul>
      <li>i = 0</li>
      <li>i = 1</li>
      <li>i = 2</li>
    </ul>

    <!-- /CONTENT -->

    <!-- FOOTER -->
    <div class="footer">copyright&copy; 2007 kuwata-lab.com</div>
    <!-- /FOOTER -->

  </body>
</html>

Template Arguments

It is able to specify template arguments in template files. Template arguments are variables which are passed by main program via context object. In the following example, 'title' and 'name' are template arguments.

File 'example10.pyhtml':
<?xml version="1.0"?>
<?py #@ARGS title, name ?>
<h1>${title}</h1>
<p>Hello ${name}!</p>

Template arguments line is converted into assignment statements of local variables.

$ pytenjin -s example10.pyhtml
_buf = []; _buf.extend(('''<?xml version="1.0"?>\n''', ));
title = _context.get('title'); name = _context.get('name'); 
_buf.extend(('''<h1>''', escape(to_str(title)), '''</h1>
<p>Hello ''', escape(to_str(name)), '''!</p>\n''', ));
print ''.join(_buf)

If template arguments are specified, other variables passed by context object are not set.

File 'example11.pyhtml':
<?py #@ARGS x ?>
<p>
x = #{x}
y = #{y}   # NameError
</p>
Result:
$ pytenjin -c 'x=10;y=20' example11.pyhtml
NameError: name 'y' is not defined

Special variable '_context' which represents context object is always available whether template arguments are specified or not.


Preprocessing

pyTenjin supports preprocessing of template. Preprocessing executes some logics when templates are loaded and that logics are not executed when rendering. Preprocessing makes your application much faster.

Notation of preprocessing is the following.

For example, assume the following template.

File 'example12.pyhtml':
<?PY states = { "CA": "California", ?>
<?PY            "NY": "New York", ?>
<?PY            "FL": "Florida",  ?>
<?PY            "TX": "Texas",  ?>
<?PY            "HI": "Hawaii", } ?>
<?PY # ?>
<?py chk = { params['state']: ' selected="selected"' } ?>
<?PY codes = states.keys() ?>
<?PY codes.sort() ?>
<select name="state">
  <option value="">-</option>
<?PY for code in codes: ?>
  <option value="#{{code}}"#{chk.get(#{{repr(code)}}, '')}>${{states[code]}}</option>
<?PY #endfor ?>
</select>

If preprocessing is activated, the above will be converted into the following when template is loaded. (Command-line option -P shows the result of preprocessing.)

Result of preprocessing:
$ pytenjin -P example12.pyhtml
<?py chk = { params['state']: ' selected="selected"' } ?>
<select name="state">
  <option value="">-</option>
  <option value="CA"#{chk.get('CA', '')}>California</option>
  <option value="FL"#{chk.get('FL', '')}>Florida</option>
  <option value="HI"#{chk.get('HI', '')}>Hawaii</option>
  <option value="NY"#{chk.get('NY', '')}>New York</option>
  <option value="TX"#{chk.get('TX', '')}>Texas</option>
</select>

This means that for-loop is executed only once when template is loaded and is not executed when rendering. In the result, rendering speed becomes to be much faster.

And the Python code is here. This shows that there is no for-loop.

Translated script code:
$ pytenjin --preprocess -sb example12.pyhtml
chk = { params['state']: ' selected="selected"' }
_buf.extend(('''<select name="state">
  <option value="">-</option>
  <option value="CA"''', to_str(chk.get('CA', '')), '''>California</option>
  <option value="FL"''', to_str(chk.get('FL', '')), '''>Florida</option>
  <option value="HI"''', to_str(chk.get('HI', '')), '''>Hawaii</option>
  <option value="NY"''', to_str(chk.get('NY', '')), '''>New York</option>
  <option value="TX"''', to_str(chk.get('TX', '')), '''>Texas</option>
</select>\n''', ));

If you have errors on preprocessing, you should check source script by -Ps option(*2).

The following is an another example. Assume that link_to() is a helper method which takes label and url and generate <a></a> tag. In this case, label and url can be parameterized by _p("...") and _P("..."). The former is converted into #{...} and the latter converted into ${...} by preprocessor.

File 'example13.pyhtml':
<?PY ## ex. link_to('Show', '/show/1')  => <a href="/show/1">Show</a> ?>
<?PY def link_to(label, url): ?>
<?PY     import urllib ?>
<?PY     return '<a href="%s">%s</a>' % (urllib.quote(url), label) ?>
<?PY # ?>
#{{link_to('Show '+_P('params["name"]'), '/items/show/'+_p('params["id"]'))}}
Preprocessed template:
$ pytenjin -P example13.pyhtml
<a href="/items/show/#{params["id"]}">Show ${params["name"]}</a>
Translated script code:
$ pytenjin --preprocess -sb example13.pyhtml
_buf.extend(('''<a href="/items/show/''', to_str(params["id"]), '''">Show ''', escape(to_str(params["name"])), '''</a>\n''', ));

There are many web-application framework and they provides helper functions. These helper functions are divided into two groups.

Preprocessor has the power to make your application much faster, but it may make the debugging difficult. You should use it carefully.

(*2)
Command-line option '-Ps' is available but '-PS' is not availabe. This is a current restriction of pytenjin.

Template Encoding

It is able to specify template encoding by command-line option '-k encoding' or '--encoding=encoding'.

Notice that these options are not equal.(*3)

File 'example14.pyhtml':
## The following is OK for '-k encoding' option,
## but is NG for '--encoding=encoding' option.
<?py val1 = '...non-ascii-characters...' ?>
val1 = #{val1}

## The following is OK for '--encoding=encoding' option,
## but is NG (UnicodeDecodeError) for '-k encoding' option.
<?py val2 = u'...non-ascii-characters...' ?>
val2 = #{val2}
(*3)
(Internally) If command-line option '-k encoding' is specified, pytenjin command executes 'to_str = tenjin.generate_tostrfunc("encoding")'. If command-line option '--encoding=encoding' is specified, pytenjin command pass 'encoding=encoding' option to tenjin.Engine(). See Developer's Guide for details.

Other Options



Developer's Guide

This section shows how to use pyTenjin in your Python script.

If you want to know the notation or features of pyTenjin, see Designer's Guide section.

If you want to handle non-ascii characters, see Template Encoding section.

If you got UnicodeDecodeError, see FAQ.

If you are interested in Google AppEngine, see FAQ.

An Example

The following is an example to use pyTenjin in Python.

Example:
import tenjin
from tenjin.helpers import *   # or escape, to_str
engine = tenjin.Engine()
context = { 'title': 'pyTenjin Example', 'items': ['AAA', 'BBB', 'CCC'] }
filename = 'file.pyhtml'
output = engine.render(filename, context)
print output,

If you want to define helper functions for template, see Add Your Helper Functions section.


Class tenjin.Template

tenjin.Template class represents a template file. An object of tenjin.Template corresponds to a template file. Main responsibilities of this class are:

This class has the following methods and attributes.

tenjin.Template(filename=None, encoding=None, escapefunc='escape', tostrfunc='to_str', indent=4)
Create template object. If filename is given, read and convert it to Python code.
tenjin.Template.convert(str input, str filename=None)
Convert input text into Python code and return it.
tenjin.Template.convert_file(str filename)
Convert file into Python code and return it. This is equivarent to tenjin.Template.convert(open(filename).read(), filename)
tenjin.Template.render(dict context=None)
Compile Python code, evaluate it with context data, and return the result of evaluation. If encoding name is specified when creating template object then render() method will return unicode object, else return str object.
tenjin.Template.script
Converted Python code
tenjin.Template.bytecode
Compiled Python code

The followings are examples to use tenjin.Template in Python script.

File 'example16.pyhtml':
<h1>#{title}</h1>
<ul>
<?py for item in items: ?>
 <li>${item}</li>
<?py #end ?>
</ul>
File 'example16.py':
## template
filename = 'example16.pyhtml'

## convert into python code
import tenjin
from tenjin.helpers import *   # or escape, to_str
template = tenjin.Template(filename)
script = template.script
## or
# template = tenjin.Template()
# script = template.convert_file(filename)
## or
# template = tenjin.Template()
# input = open(filename).read()
# script = template.convert(input, filename)  # filename is optional

## show converted python code
print "---- python code ----"
print script,

## evaluate python code
context = {'title': 'pyTenjin Example', 'items': ['<AAA>','B&B','"CCC"']}
output = template.render(context)
print "---- output ----"
print output,
Result:
$ python example16.py
---- python code ----
_buf.extend(('''<h1>''', to_str(title), '''</h1>
<ul>\n''', ));
for item in items:
    _buf.extend((''' <li>''', escape(to_str(item)), '''</li>\n''', ));
#end
_buf.extend(('''</ul>\n''', ));
---- output ----
<h1>pyTenjin Example</h1>
<ul>
 <li>&lt;AAA&gt;</li>
 <li>B&amp;B</li>
 <li>&quot;CCC&quot;</li>
</ul>

Constructor of tenjin.Template can take the follwoing options.

Constructor of tenjin.Engine can also take the same options as above. These options given to constructor of tenjin.Engine are passed to constructor of tenjin.Template internally.

File 'example17.py':
filename = 'example16.pyhtml'
import tenjin
from tenjin.helpers import escape, to_str
template = tenjin.Template(filename, escapefunc='cgi.escape', tostrfunc='str')
print template.script

import cgi
title = 'pyTenjin Example'
items = ['<foo>', '&bar', '"baz"', None, True, False]
output = template.render({'title':title, 'items':items})
print output,
Result:
$ python example17.py
_buf.extend(('''<h1>''', str(title), '''</h1>
<ul>\n''', ));
for item in items:
    _buf.extend((''' <li>''', cgi.escape(str(item)), '''</li>\n''', ));
#end
_buf.extend(('''</ul>\n''', ));

<h1>pyTenjin Example</h1>
<ul>
 <li>&lt;foo&gt;</li>
 <li>&amp;bar</li>
 <li>"baz"</li>
 <li>None</li>
 <li>True</li>
 <li>False</li>
</ul>

Class tenjin.Engine

tenjin.Engine class contains some template objects. It can handle nested template and layout template. This class provides many features such as:

This class has the following methods.

tenjin.Engine(str prefix='', str postfix='', str layout=None, list path=None, object cache=True, bool preprocess=False, type templateclass=Template, **kwargs)
Create Engine object. Argument 'cache' controls caching policy.
  • If instance object of CacheStorage class, use it as cache storage.
  • If True, new MarshalCacheStorage object is created and used. In the result, template objects are cached in both memory and marshal-base cache file.
  • If None, new MemoryCacheStorage object is created and used. In the result, template objects are cached in memory but not saved in cache file.
  • If False, nothing is done for caching. Template objects are not cached in both memory nor file.
Arguments kwargs are passed to tenjin.Template() internally.
tenjin.Engine#render(str template_name, dict context=None, dict globals=None, str layout=None)
Convert template into Python code, evaluate it with context data, and return the result of it.
  • If layout is True or None then layout template name specified by constructor option will be used as layout template.
  • Else if layout is False then layout template will be not used,
  • Else if layout is string then it is regarded as layout template name.
tenjin.Engine#include(str template_name)
Include and evaluate other template.

Argument template_name in render() methods is filename or short name of template. Template short name is a string starting with colon (':'). For example, 'render(":list", context)' is equivarent to 'render("user_list.pyhtml", context)' if prefix option is 'user_' and postfix option is '.pyhtml'.


Template Caching

pyTenjin provides some classes which caches template objects. These classes and their objects are called as cache storage.

MarshalCacheStorage
Caches template objects in both memory and cache file. Cache file is based on marshal format. This is the default cache storage in pyTenjin because marshal can save bytecode object into file.
PickleCacheStorage
Caches template objects in both memory and cache file. Cache file is based on pickle format. Notice that pickle can't save bytecode object, so it is a little slower than MarshalCacheStorage. In this reason, it is recommended to use MarshalCacheStorage if it is available.
TextCacheStorage
(experimental) Caches template objects in both memory and cache file. Cache file is based on normal text file. This may be useful when neither marshal nor pickle are available, such as Jython or so.
MemoryCacheStorage
Caches template objects in memory, but not in file. This doesn't create cache file at all.
GaeMemcacheCacheStorage
Caches template objects in both memory and memcache. This is available only in Google AppEngine environment. See FAQ for details.

CacheStorage uses full-path of template files as key of cache. So you can share a cache storage between engine objects.

## shared cache storage
shared = MarshalCacheStorage()

## create an engine for BooksController
path1 = ["views/books", "views"]
engine1 = tenjin.Engine(path=path1, cache=shared)

## create an engine for AuthorsController.
## this engine has different parameter value,
## but it is able to share cache storage.
path2 = ["views/authors", "views"]
engine2 = tenjin.Engine(path=path2, cache=shared)

Variables and Functions

In template file, the following variables and functions are available.

_content
This variable represents the result of evaluation of other template. This is available only in layout template file.
_context
This variable represents context data dictionary and same as the 2nd argument of tenjin.Engine.render(). You can tweak this dict. For example if you set _context['title']="Example" in your template file, title variable will be available in layout template file.
include(str template_name)
Include and evaluate other template. This is an alias of tenjin.Engine.include().
start_capture(name)
Start capturing. Result will be stored into _context['name'].
stop_capture()
Stop capturing.
captured_as(varname)
If captured string as varname is exist then append it into _buf and return True, else return False. This is a helper function for layout template.

The followings are example of tenjin.Engine class.

File 'user_form.pyhtml':
<?py #@ARGS params ?>
<p>
  Name:  <input type="text" name="name"  value="${params['name']}" /><br />
  Email: <input type="text" name="email" value="${params['email']}" /><br />
  Gender:
<?py chk = { params['gender'] : ' checked="checked"' } ?>
  <input type="radio" name="gender" value="m" #{chk.get('m')} />Male
  <input type="radio" name="gender" value="f" #{chk.get('f')} />Female
</p>
File 'user_create.pyhtml':
<?py #@ARGS ?>
<form action="user_app.cgi" method="post">
  <input type="hidden" name="action" value="create" />
<?py include(':form') ?>
  <input type="submit" value="Create" />
</form>
File 'user_edit.pyhtml':
<?py #@ARGS params ?>
<form action="user_app.cgi" method="post">
  <input type="hidden" name="action" value="edit" />
  <input type="hidden" name="id" value="${params['id']}" />
<?py include(':form') ?>
  <input type="submit" value="Edit" />
</form>
File 'user_layout.pyhtml':
<?py #@ARGS _content, title ?>
<html>
  <body>

    <h1>${title}</h1>

    <div id="main-content">
#{_content}
    </div>

    <div id="footer">
<?py include('footer.html') ?>
    </div>

  </body>
</html>
File 'footer.html':
<?py #@ARGS ?>
<hr />
<address>
  <a href="mailto:webmaster@example.com">webmaster@example.com</a>
</address>
File 'user_app.cgi':
#!/usr/bin/env python

## set action ('create' or 'edit')
import sys, os, cgi
action = None
form = None
if os.getenv('REQUEST_METHOD'):
    form = cgi.FieldStorage()
    action = form.getFirst('action')
elif len(sys.argv) >= 2:
    action = sys.argv[1]
if action is None or action not in ['create', 'edit']:
    action = 'create'

## set context data
if action == 'create':
    title = 'Create User'
    params = {'name': None, 'email': None, 'gender': None}
else:
    title = 'Edit User'
    params = {'name': 'Margalette',
              'email': 'meg@example.com',
              'gender': 'f',
              'id': 123 }
context = {'title': title, 'params': params}

## create engine object
import tenjin
from tenjin.helpers import *
from tenjin.helpers.html import *
layout = ':layout'   # or 'user_layout.pyhtml'
engine = tenjin.Engine(prefix='user_', postfix='.pyhtml', layout=layout)

## evaluate template
template_name = ':' + action   # ':create' or ':edit'
output = engine.render(template_name, context)
if form:
    print "Content-Type: text/html\r\n\r\n",
print output,
Result:
$ python user_app.cgi create
<html>
  <body>

    <h1>Create User</h1>

    <div id="main-content">
<form action="user_app.cgi" method="post">
  <input type="hidden" name="action" value="create" />
<p>
  Name:  <input type="text" name="name"  value="" /><br />
  Email: <input type="text" name="email" value="" /><br />
  Gender:
  <input type="radio" name="gender" value="m"  />Male
  <input type="radio" name="gender" value="f"  />Female
</p>
  <input type="submit" value="Create" />
</form>

    </div>

    <div id="footer">
<hr />
<address>
  <a href="mailto:webmaster@example.com">webmaster@example.com</a>
</address>
    </div>

  </body>
</html>

HTML Helper Functions

pyTenjin provides some useful helper functions for HTML. These are defined in tenjin.helpers.html module.

checked(expr)
Return ' checked="checked"' if expr is true.
selected(expr)
Return ' selected="selected"' if expr is true.
disabled(expr)
Return ' disabled="disabled"' if expr is true.
nl2br(str)
Relplace "\n" in str into "<br />\n" and return it.
tagattr(name, expr, value=None, escape=True)
(experimental) Return ' name="value"' string if expr is true value, else return '' (empty string). If value is not specified then expr is used as value instead.
>>> from tenjin.helpers.html import *
>>> tagattr('size', 20)
' size="20"'
>>> tagattr('size', 0)
''
>>> tagattr('size', 0, 'large')
' size="large"'
tagattrs(**kwargs)
(experimental) Return ' name1="value2" name2=value2 ...'.
If valueN is false value, both key and value are ignored.
If keyN is 'klass' then it will be converted into 'class'.
If keyN is 'checked', 'selected', or 'disabled', then keyN is used as value. For example, tagattrs(checked=1) returns ' checked="checked"'.
>>> from tenjin.helpers.html import *
>>> tagattrs(klass='error', size=20, checked=True)
' class="error" size="20" checked="checked"'
>>> tagattrs(klass='', size=0, checked=False)
''
Tips: 'and' operator may help you.
>>> from tenjin.helpers.html import *
>>> color = 'red'
>>> tagattrs(style=(color and 'color:%s' % color))
' style="color:red"'
>>> color = None
>>> tagattrs(style=(color and 'color:%s' % color))
''
nv(name, value, sep=None, **kwargs)
Return ' name="name" value="value"' string.
If sep is specified, ' id="name sep value' will be added.
kwargs are converted into tag attributes by tagattrs() function.
>>> from tenjin.helpers.html import *
>>> nv('gender', 'F')
' name="gender" value="F"'
>>> nv('gender', 'F', '.')
' name="gender" value="F" id="gender.F"'
>>> nv('gender', 'F', '.', klass='error', checked=True, style="color:red")
' name="gender" value="F" id="gender.F" class="error" checked="checked" style="color:red"'
new_cycle(*values)
Cycle each value in values.
>>> from tenjin.helpers.html import *
>>> cycle = new_cycle('odd', 'even')
>>> cycle()
'odd'
>>> cycle()
'even'
>>> cycle()
'odd'
>>> cycle()
'even'

Template Encoding

If you got UnicodeDecodeError, see FAQ at first.

Python 2.x

pyTenjin provides two approaches for encoding in Python 2.x.

(A) Binary-based approach

pyTenjin handles templates as binary file. For example, text "foo" will be converted into _buf << 'foo'. In this approach, unicode object should be encoded into binary(=str) by to_str().

to_str = tenjin.generate_tostrfunc(encode='utf-8')
   # ex. to_str(u'\u65e5\u672c\u8a9e') => '\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e'
engine = tenjin.Engine()

If you have some troubles about encoding in this approach, try to add encoding declaration in your templates if your templates contain non-ascii characters.

<?py # -*- coding: utf-8 -*- ?>
<h1>Hello</h1>
(B) Unicode-based approach

You can hanlde templates as unicode string with encoding option for tenjin.Engine(). For example, text "foo" will be converted into _buf << u'foo'. In this approach, binary(=str) should be decoded into unicode in to_str().

to_str = tenjin.generate_tostrfunc(decode='utf-8')
   # ex. to_str('\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e')  #=> u'\u65e5\u672c\u8a9e'
engine = tenjin.Engine(encoding='utf-8')
html = engine.render('index.pyhtml')
if isinstance(html, unicode): html = html.encode('utf-8')
print(html)

You should not add encoding declaration in your templates, or you will get the following error.

SyntaxError: encoding declaration in Unicode string

In Python 2.x, binary-based approach is faster than unicode-based approach. If you don't have any special reason, it is recommend to be with binary-based approach.


Python 3.0

In Python 3.0, string is treated as unicode object. So pyTenjin handles all templates in string base (= unicode base), and bytes data is converted into str by to_str() function. If you don't specify any encoding, pyTenjin uses 'utf-8' encoding as default.

to_str = tenjin.generate_tostrfunc('utf-8')  ## decode bytes into unicode
engine = tenjin.Engine(encoding='utf-8')     ## handle template as unicode


Add Your Helper Functions

There are several ways to use helper functions.

Assume the following template.

File 'example18.pyhtml':
<ul>
  <li>#{link_to(label, url)}</li>
</ul>

(A) Define helper functions as global.

File 'example18a.py':
import tenjin
from tenjin.helpers import *

def link_to(label, url):
    return '<a href="%s">%s</a>' % (escape(url), escape(label))

engine = tenjin.Engine()
context = { 'label': 'Top', 'url': '/' }
output = engine.render('example18.pyhtml', context)
print output,
Result:
$ python example18a.py
<ul>
  <li><a href="/">Top</a></li>
</ul>

(B) Add helper functions into context.

File 'example18b.py':
import tenjin
from tenjin.helpers import *

def f1(label, url):
    return '<a href="%s">%s</a>' % (escape(url), escape(label))

engine = tenjin.Engine()
context = { 'label': 'Top', 'url': '/', 'link_to': f1 }
output = engine.render('example18.pyhtml', context)
print output,
Result:
$ python example18b.py
<ul>
  <li><a href="/">Top</a></li>
</ul>

(C) Add functions to a dict object and pass it as global variables.

File 'example18c.py':
import tenjin
from tenjin.helpers import *

def f1(label, url):
    return '<a href="%s">%s</a>' % (escape(url), escape(label))

engine = tenjin.Engine()
context = { 'label': 'Top', 'url': '/' }
globals = {'escape':escape, 'to_str':to_str, 'link_to':f1}
output = engine.render('example18.pyhtml', context, globals=globals)
print output,
Result:
$ python example18c.py
<ul>
  <li><a href="/">Top</a></li>
</ul>

Other Topics