pyTenjin User's Guide
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.
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)
$ 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)
$ pytenjin -c "name='World'; items=['<AAA>','B&B','\"CCC\"']" ex.pyhtml Hello World! <ul> <li><AAA></li> <li>B&B</li> <li>"CCC"</li> </ul>
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:
- pyTenjin runs very fast. It works about twice as fast as any other solution.
- pyTenjin doesn't break HTML design because it uses XML Processing Instructions (PI) as embedded notation for Python statements.
- pyTenjin is secure because it supports escaping expression value by default.
- pyTenjin is small and lightweight. It is very easy to include pyTenjin into your application.
- pyTenjin supports auto caching of converted Python code.
- pyTenjin supports partial template and layout template. These are very useful especially for web application.
- pyTenjin supports partial capturing of template.
- pyTenjin can load YAML file as context data. Using pyTenjin, it is able to separate template file and data file.
- pyTenjin now supports Goole AppEngine. See FAQ for details.
Comparison with other solutions
pyTenjin has advantages compared with other solutions (including other language solutions):
- Easy to design -- JSP, ePerl, or eRuby breaks HTML design because they use '
<% ... %>
' as embedded notation which is not valid in HTML. pyTenjin doesn't break HTML desgin because it uses Processing Instructions (PI) as embedded notation which is valid in HTML. - Easy to write -- In PHP, it is a little bother to write embedded expression because the notation '
<?php echo $value; ?>
' is a little long, and very bother to embed escaped expression because '<?php echo htmlspecialchars($value); ?>
' is very long. In pyTenjin, these are very easy to write because embedded expression notations ('#{value}
' and '${value}
') are very short. - Easy to learn -- Zope DTML, PageTemplate, Django Template, and other template systems are hard to learn because they are large, highly functinal, and based on non-Python syntax. pyTenjin is very easy to learn if you already know Python language because it is very small and you can embed any Python code into HTML template.
Benchmark
Benchmark script is contained in pyTenjin archive. The following is an example of benchmark.
$ 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:
- Python 2.5.1
- Tenjin 0.6.1
- Django 0.95
- Cheetah 2.0
- Myghty 1.1
- Kid 0.9.6
- Genshi 0.4.4
- Mako 0.1.9
- Templetor (web.py) 0.22
This shows the followings.
- pyTenjin is the fastest template engine.
- Cheetah's performance is good.
- Myghty's performance is good for mod_python, but is bad for CGI program.
- Django's performance is not good.
- Kid's performance is worse. It is too slow.
- Genshi's performance is also worse. It is faster than Kid, but slower than others.
- Mako's performance is very good when module caching is enabled.
- Templetor's performance is not good for mod_python and worse for CGI program.
Installation
pyTenjin works on CPython (2.3 or higher, 3.0 supported).
- Download pyTenjin-X.X.X.tar.gz and extract it.
- 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. - (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.
- '
<?py ... ?>
' : Python statement - '
#{...}
' : Python expression - '
${...}
' : Python expression with HTML escape
<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'.
$ pytenjin example1.pyhtml <table> <tbody> <tr> <td><foo></td> <td><foo></td> </tr> <tr> <td>bar&bar</td> <td>bar&bar</td> </tr> <tr> <td>"baz"</td> <td>"baz"</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:
<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.
<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.
<ul> <?py i = 0 for item in ['<foo>', 'bar&bar', '"baz"']: i += 1 ?> <li>#{item} ${item}</li> <?py #end ?> </ul>
<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.
$ 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)
- Variable
_buf
is a list. - Function
to_str()
(=tenjin.helpers.to_str()
) converts value into string except None is converted into""
(empty string). Command-line option--tostrfunc=func
makes pytenjin to usefunc()
instead ofto_str()
. - Function
escape()
(=tenjin.escape()
) escapes'& < > "'
into'& < > "'
(*1). Command-line option--escapefunc=func
makes pytenjin to usefunc()
instead ofescape()
. - End-of-line character ("\n" or "\r\n") is automatically detected by pyTenjin.
'_buf = [];
' is called as preamble and 'print ''.join(_buf)
' is called as postamble. Command-line option '-b
' removes preamble and postamble.
<?py for i in [1, 2, 3]: ?> <p>#{i}</p> <?py #end ?>
$ 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.
$ 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).
$ 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()
andcgi.escape()
is that the former escapes double-quotation mark into '"
' and the latter doesn't.
Syntax Checking
Command-line option '-z
' checks syntax error in embedded Python code.
<ul> <?py for item in items: ?> <li>${item}</li> <?py #end ?> </ul>
$ 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.
<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>
<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.
<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 ?>
text: foo num: 3.14 flag: yes items: - foo - bar - baz hash: x: 1 y: 2
$ 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>
text = "foo" num = 3.14 flag = True items = ["foo", "bar", "baz"] hash = {"x":1, "y":2}
$ 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.
text: #{text} items: <?py for item in items: ?> - #{item} <?py #end ?> hash: <?py for key, val in hash.items(): ?> #{key}: #{val} <?py #end ?>
$ 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
$ 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.
<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>
<ul> <?py for item in menu: ?> <li><a href="${item['url']}">${item['name']}</a></li> <?py #end ?> </ul>
<hr /> <address> <a href="mailto:${webmaster_email}">${webmaster_email}</a> </address>
items = [ '<FOO>', '&BAR', '"BAZ"' ] webmaster_email = 'webmaster@example.com' menu = [ {'name': 'Top', 'url': '/' }, {'name': 'Products', 'url': '/prod' }, {'name': 'Support', 'url': '/support' }, ]
$ 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><FOO></p> <p>&BAR</p> <p>"BAZ"</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.
<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>
<?py for item in items: ?> <p>${item}</p> <?py #end ?>
$ 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><FOO></p> <p>&BAR</p> <p>"BAZ"</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.
... <h1>${title}</h1> <div id="main-content"> #{_content} <div> <a href="${url}">Next page</a> ...
<?py _context['title'] = 'Document Title' ?> <?py _context['url'] = '/next/page' ?> <table> ...content... </table>
$ 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.
<?py _context['_layout'] = ":layout8_xhtml" ?> <h1>Hello World!</h1>
<html> <body> #{_content} </body> </html>
<?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <body> #{_content} </body> </html>
$ 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.
<?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© 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.
<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.
$ 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© 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.
<?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.
<?py #@ARGS x ?> <p> x = #{x} y = #{y} # NameError </p>
$ 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.
<?PY ... ?>
represents preprocessing statement.#{{...}}
represents preprocessing expression (without HTML escape).${{...}}
represents preprocessing expression (with HTML escape).
For example, assume the following template.
<?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.)
$ 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.
$ 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.
<?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"]'))}}
$ pytenjin -P example13.pyhtml <a href="/items/show/#{params["id"]}">Show ${params["name"]}</a>
$ 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.
link_to()
or_()
(function for M17N) return the same result when the same arguments are passed. These functions can be expanded by preprocessor.- Some functions return the different result even if the same arguments are passed. These functions can't be expaned by preprocessor.
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)
## 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
- Command-line option '
-i N
' or '--indent=N
' changes indent depth toN
(default 4). - Command-line option '
-m mod1,mod2,mod3
' loads modules mod1, mod2, and mod3. This option is equivarent to Python code 'import mod1, mod2, mod3
'. - Command-line option '
--escapefunc=func1
' changesescape()
function name tofunc1
and '--tostrfunc=func2
' changesto_str()
function name tofunc2
. - Command-line option '
--path=dir1,dir2,...
' sepcifies template directory path. - Command-line option '
-k encoding
' or '--encoding=encoding
' specifies encoding name (default None).
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.
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:
- Convert template file into python script
- Render converted python script with context data
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.
<h1>#{title}</h1> <ul> <?py for item in items: ?> <li>${item}</li> <?py #end ?> </ul>
## 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,
$ 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><AAA></li> <li>B&B</li> <li>"CCC"</li> </ul>
Constructor of tenjin.Template can take the follwoing options.
- '
encoding
' (string) specifies encoding name of template file. If you specifies encoding, template file string is converted into unicode object. Template.render() will return unicode object if encoding is specified else return str object. Default is None.
- '
escapefunc
' (string) specifies function name to escape string. Default is 'escape
' (=tenjin.helpers.escape
).
- '
tostrfunc
' (string) specifies function name to convert value into string. Default is 'to_str
' (=tenjin.helpers.to_str
).
- '
indent
' (integer) specifies width of indentation. Default is 4.
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.
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,
$ 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><foo></li> <li>&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:
- Search template files
- Nestable layout template
- Partial template
File caching(caching feature is separated to tenjin.CacheStorage class.)- Preprocessing
- and so on
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.
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.
- If
- 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.
<?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>
<?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>
<?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>
<?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>
<?py #@ARGS ?> <hr /> <address> <a href="mailto:webmaster@example.com">webmaster@example.com</a> </address>
#!/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,
$ 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) byto_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 into_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.
<ul> <li>#{link_to(label, url)}</li> </ul>
(A) Define helper functions as global.
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,
$ python example18a.py <ul> <li><a href="/">Top</a></li> </ul>
(B) Add helper functions into context.
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,
$ python example18b.py <ul> <li><a href="/">Top</a></li> </ul>
(C) Add functions to a dict object and pass it as global variables.
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,
$ python example18c.py <ul> <li><a href="/">Top</a></li> </ul>
Other Topics
- tenjin.Template detects newline character ("\n" or "\r\n") automatically. If input file contains "\r\n", pyTenjin generates output which contains "\r\n".
- tenjin.Template.render() can be called many times. If you create a tenjin.Template object, you can call render() method many times.
- tenjin.Template.convert() also can be called many times. If you create a tenjin.Template object, you can call convert() (and also render()) method many times.