pyTenjin User's Guide

last update: $Date: 2007-08-04 13:23:33 +0900 (Sat, 04 Aug 2007) $

Release: 0.6.1

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:

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).

  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>

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.

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.


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.iteritems(): ?>
<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 '-z' 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.iteritems(): ?>
  #{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 _context.has_key('name'): ?>
<?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':
<p>
<?py #@ARGS x ?>
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.

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,

Classes and Functions in pyTenjin

pyTenjin has the follwoing classes.

tenjin.Template
This class represents a template file. An object of tenjin.Template correspond to a template file.
tenjin.Engine
This class represents some template objects. It can handle nested template and layout template. Using tenjin.Engine class, you can use pyTenjin as a template engine for web application.

pyTenjin has the following utility functions. These are defined at tenjin.helpers.html module.


Class tenjin.Template

tenjin.Template class represents a template file. An object of tenjin.Template corresponds to a template file. It doesn't support nested template nor layout template (use tenjin.Engine class instead).

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>

Class tenjin.Engine

tenjin.Engine class contains some template objects. It can handle nested template and layout template. Using tenjin.Engine class, you can use pyTenjin as a template engine for web application.

This class has the following methods.

tenjin.Engine(str prefix='', str postfix='', str layout=None, list path=None, bool cache=True, bool preprocess=False, type templateclass=Template, **init_opts_for_template)
Create Engine object. Argument 'cache' controls caching policy (caches bytecode when True, caches python code when None, caches nothing when False). Arguments init_opts_for_template 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 is used as layout template, else if False then layout template is not used, else if 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'.

In template file, the followings are available.

_content
This variable represents the result of evaluation of other template. This is available only 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 gender = params['gender'] ?>
<?py chk = { True: ' checked="checked"', False: '' } ?>
  <input type="radio" name="gender" value="m" #{chk[gender=='m']} />Male
  <input type="radio" name="gender" value="f" #{chk[gender=='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>

Template Initialize Options

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>

Template Encoding

pyTenjin provides two approaches for encoding.

One approach is to handle template content as string (byte sequence), and convert context data into string by extended to_str() function if context data is unicode object.

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

## Create to_str() function which encodes unicode object into string
## according to encoding.
to_str = tenjin.generate_tostrfunc('euc-jp')
         #  ex.  to_str(u'\u65e5\u672c\u8a9e')
         #         =>  '\xc6\xfc\xcb\xdc\xb8\xec' (euc-jp string)

engine = tenjin.Engine()
context = { 'name': u'\u65e5\u672c\u8a9e' }  # non-ascii characters
output = engine.render('encoding1.pyhtml', context)
print output,

The other approach is to convert template content into unicode object.

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

## If you specify encoding option for Template() or Engine(),
## content of template will be converted into unicode object.
engine = tenjin.Engine(encoding='euc-jp')
## If you want to see python code, try the following:
##   template = engine.get_template(filename)
##   print template.script

## You must encode output into string because output will be unicode.
context = { 'name': u'\u65e5\u672c\u8a9e' }  # non-ascii characters
output = engine.render('encoding1.pyhtml', context)
if isinstance(output, unicode):
   output = output.encode('euc-jp')
print output,

Add Your Helper Functions

There are several ways to use helper functions.

Assume the following template.

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

(A) Define helper functions as global.

File 'example19a.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('example19.pyhtml', context)
print output,
Result:
$ python example19a.py
<ul>
  <li><a href="/">Top</a></li>
</ul>

(B) Add helper functions to context.

File 'example19b.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('example19.pyhtml', context)
print output,
Result:
$ python example19b.py
<ul>
  <li><a href="/">Top</a></li>
</ul>

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

File 'example19c.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('example19.pyhtml', context, globals=globals)
print output,
Result:
$ python example19c.py
<ul>
  <li><a href="/">Top</a></li>
</ul>

Other Topics