Skip to main content

Python templating engine - the one ton solution

Project description

Tonnikala

Join the chat at https://gitter.im/tetframework/Tonnikala https://next.travis-ci.org/tetframework/Tonnikala.svg?branch=master https://coveralls.io/repos/github/tetframework/Tonnikala/badge.svg?branch=master

Tonnikala is the latest reincarnation among the Python templating languages that feed on Kid-inspired XML syntax. It doesn’t use the tagstreams and trees of Genshi or Kid, but follows in footsteps of Chameleon and Kajiki in making the template to compile into Python bytecode directly. The syntax is very close to that of Kajiki, but the internals are very different: Tonnikala writes code as Abstract Syntax Trees and optimizes the resulting trees extensively. In addition, there is an optional speed-up module, that provides a specialized class used for output buffering.

Examples

from tonnikala.loader import Loader

template_source = u"""
<table>
    <tr py:for="row in table">
        <py:for each="key, value in row.items()"
            ><td>$key</td><td>$literal(value)</td></py:for>
    </tr>
</table>
"""

template = Loader().load_string(template_source)

ctx = {
    'table': [dict(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9,j=10)
        for x in range(1000)]
}

print(template.render(ctx))

Variable interpolation

Within attributes and text, all contents starting with $ followed by a {, an alphabetic character or _ is considered an interpolated expression. If the interpolated expression starts with ${, the expression continues until the matching } token. Otherwise the interpolation consists of an identifier, followed by any number of attribute accesses, indexing brackets [...], and method call operators (...), without any intervening whitespace (except within the brackets). The expression parsing stops whenever the next token cannot match this rule anymore.

While the form

HELLO, ${user.name.upper()}.

is accepted, it is also perfectly OK to write

HELLO, $user.name.upper().

In the above code, user is an object with name attribute or property, which evaluates to a string; upper() method is called on the resulting string. Suppose the user’s name is Antti Haapala, the resulting output would be HELLO, ANTTI HAAPALA..

The rules also ensure that you can do an interpolation as follows:

Your word $digit has the integer value ${{'one': 1, 'two': 2}[digit]}

Now, if digit == 'one', the output of this fragment would be

Your word one has the integer value 1.

An interpolated expression is auto-escaped appropriately for its context. If you do not want to be the expression to be escaped you can bracket it with a function call to literal(), or in markupsafe.Markup. The literal is especially efficient as it is optimized away in the compile time whenever possible.

Control tags/attributes

Most of the control tags and attributes have a reach of one element (those which do not, have an effect for the whole file). For all these you have the choice of using them as an attribute or as an element; e.g.

<py:for each="i in iterable"></py:for>

or

<div py:for="i in iterable"></div>

The latter attribute form is preferred as they are more concise, but sometimes clarity or structure necessitates the use of the element form.

py:if

<py:if test="condition"><span>the condition was true</span></py:if>

or

<span py:if="condition">the condition was true</span>

results in the output

<span>the condition was true</span>

if the condition was true

py:unless

py:unless="expression" is an alternative way to type py:if="not expression".

py:for

<py:for each="i in range(5)"><td>$i</td></py:for>

or

<td py:for="i in range(5)">$i</td>

results in the output

<td>0</td><td>1</td><td>2</td><td>3</td><td>4</td>

py:strip

Strips the tag if the expression is true; keeping the contents. Keeps the tag if the expression evaluates to false.

<div py:strip="True">content</div>

results in rendered output

content

py:strip="" is equivalen to py:strip="True".

Warning: py:strip will evaluate the expression twice: once for the opening and once for the closing tag.

py:def

Declares a callable function with optional arguments. The function, when called, will return the rendered contents of the py:def tag.

For example a function without argments (you can omit the empty parentheses ()):

<!-- define a function -->
<py:def function="copyright">(C) 2015 Tonnikala contributors</py:def>

<!-- call the function -->
$copyright()

With arguments:

<button
     py:def="button(caption, type='submit' cls='btn-default', id=None)"
     class="btn $btn_cls"
     type="$type"
     id="$id">$caption</button>

$button('Cancel', id='cancel')
$button('OK', cls='btn-primary', id='ok')
$button('Reset', type='reset')

Will render to

<button class="btn btn-default" type="submit" id="cancel">Cancel</button>
<button class="btn btn-primary" type="submit" id="ok">OK</button>
<button class="btn btn-default" type="reset">Reset</button>

The functions created by py:def form closures - that is they remember the variable values from the context where they were created.

<li py:def="li_element(content)">$content</li>

<ul py:def="make_list(elements, format_item=li_element)">
    <py:for each="item in elements">$format_item(item)</py:for>
</ul>

<py:def function="make_color_list(elements, color='#ccc')">
    <li py:def="colorized_li_element(content)" style="color: $color">$content</li>
    $make_list(elements, format_item=colorized_li_element)
</py:def>

$make_list(plain)
$make_color_list(good, color="#0F0")
$make_color_list(bad, color="#F00")

might render to

<ul>
    <li>Plain item 0</li>
    <li>Plain item 1</li>
    <li>Plain item 2</li>
</ul>
<ul>
    <li style="color: #0F0">Good item 0</li>
    <li style="color: #0F0">Good item 1</li>
    <li style="color: #0F0">Good item 2</li>
    <li style="color: #0F0">Good item 3</li>
</ul>
<ul>
    <li style="color: #F00">Bad item 0</li>
    <li style="color: #F00">Bad item 1</li>
    <li style="color: #F00">Bad item 2</li>
</ul>

py:with

py:with declares one or more lexical variable bindings to be available within the element. This is useful in eliminating repeated calculations in a declarative context

<py:with vars="a = 5; b = 6"><span>$a * $b = ${a * b}</span></py:with>

or

<span py:with="a = 5; b = 6">$a * $b = ${a * b}</span>

results in the output

<span>5 * 6 = 30</span>

Template inheritance

base.tk

<html>
<title><py:block name="title_block">I am $title</py:block></title>
<py:def function="foo()">I can be overridden too!</py:def>
<h1>${title_block()}</h1>
${foo()}
</html>

child.tk

<py:extends href="base.tk">
<py:block name="title_block">But I am $title instead</py:block>
<py:def function="foo()">I have overridden the function in parent template</py:def>
</py:extends>

Template imports

importable.tk

<html>
<py:def function="foo()">I am an importable function</py:def>
</html>

importer.tk

<html>
<py:import href="importable.tk" alias="imp" />
${imp.foo()}
</html>

FileLoader

To load templates from files, use the tonnikala.FileLoader class:

loader = FileLoader(paths=['/path/to/templates'])
template = loader.load('child.tk')

A FileLoader currently implicitly caches all loaded templates in memory.

Template

To render the template:

result = template.render(ctx)

You can specify a block, or no-argument def to render explicitly:

result = template.render(ctx, funcname='title_block')

Pyramid integration

Include ‘tonnikala.pyramid’ into your config to enable Tonnikala. When included, Tonnikala adds the following configuration directives:

add_tonnikala_extensions(*extensions)

Registers Tonnikala renderer for these template extensions. By default Tonnikala is not registered as a renderer for any extension. For example: config.add_tonnikala_extensions('.html', '.tk') would enable Tonnikala renderer for templates with either of these extensions.

add_tonnikala_search_paths(*paths)

Adds the given paths to the end of Tonnikala search paths that are searched for templates. These can be absolute paths, or package.module:directory/subdirectory-style asset specs. By default no search path is set (though of course you can use an asset spec for template).

set_tonnikala_reload(reload)

If True, makes Tonnikala not cache templates. Default is False.

set_tonnikala_l10n(reload)

If True, makes Tonnikala translate templates. Default is False.

These 4 can also be controlled by tonnikala.extensions, tonnikala.search_paths, tonnikala.reload and tonnikala.l10n respectively in the deployment settings (the .ini files). If tonnikala.reload is not set, Tonnikala shall follow the pyramid.reload_templates setting.

set_debug_templates(debug)

If True, makes Tonnikala skip some optimizations that make debugging harder.

Status

Beta, working features are

  • Structural elements py:if, py:unless, py:def, py:for, py:replace, py:content

  • Basic template inheritance: py:extends and py:block; the child template also inherits top level function declarations from the parent template, and the child can override global functions that the parent defines and uses.

  • Expression interpolation using $simple_identifier and ${complex + python + "expression"}

  • Boolean attributes: <tag attr="${False}">, <tag attr="$True">

  • Implicit escaping

  • Disabling implicit escaping (literal())

  • C speedups for both Python 2 and Python 3

  • Importing def blocks from another template: py:import

  • Basic I18N using gettext.

  • Pyramid integration

  • Javascript as the target language (using js: prefix)

  • Overriding attributes, setting attrs from dictionary: py:attrs

  • Understandable exceptions and readable tracebacks on CPython

  • Lexical variable assignments with py:with

Upcoming features:

  • Structural elements: py:switch, py:case; py:else for for, if and switch.

  • Custom tags mapping to py:def

  • I18N with optional in-parse-tree localization (partially done)

  • Pluggable frontend syntax engines (partially done)

  • METAL-like macros

  • Pluggable expression languages akin to Chameleon

  • Even better template inheritance

  • Better documentation

Contributors

  • Antti Haapala

  • Ilja Everilä

  • Pete Sevander

  • Hiếu Nguyễn

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tonnikala-1.0.0b4.tar.gz (56.6 kB view details)

Uploaded Source

Built Distributions

tonnikala-1.0.0b4-cp36-cp36m-manylinux1_x86_64.whl (76.3 kB view details)

Uploaded CPython 3.6m

tonnikala-1.0.0b4-cp35-cp35m-manylinux1_x86_64.whl (76.3 kB view details)

Uploaded CPython 3.5m

tonnikala-1.0.0b4-cp34-cp34m-manylinux1_x86_64.whl (76.1 kB view details)

Uploaded CPython 3.4m

tonnikala-1.0.0b4-cp33-cp33m-manylinux1_x86_64.whl (75.8 kB view details)

Uploaded CPython 3.3m

tonnikala-1.0.0b4-cp27-cp27mu-manylinux1_x86_64.whl (75.3 kB view details)

Uploaded CPython 2.7mu

tonnikala-1.0.0b4-cp27-cp27m-manylinux1_x86_64.whl (75.3 kB view details)

Uploaded CPython 2.7m

File details

Details for the file tonnikala-1.0.0b4.tar.gz.

File metadata

  • Download URL: tonnikala-1.0.0b4.tar.gz
  • Upload date:
  • Size: 56.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for tonnikala-1.0.0b4.tar.gz
Algorithm Hash digest
SHA256 ab74605dd7c2280bba42c418a474e4d91e6a2da68490db981b8e38dcc024cab7
MD5 44e234094e7085b908819f057fab9b1d
BLAKE2b-256 e5d1d0af4a6fc3cafde02933023f50fe5bccc9cdc6645eb2667fe4d96447b93d

See more details on using hashes here.

File details

Details for the file tonnikala-1.0.0b4-cp36-cp36m-manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for tonnikala-1.0.0b4-cp36-cp36m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 22bb9b23fdc3d311d74c18b12b9bf57f08bed4b0381e3343c1f6b60c551fa1c7
MD5 e2dbfcabeb28fbcbb98ab7995485772e
BLAKE2b-256 6d41f07c4d664a5239c3c9bbd5533150010ffd36a43b88ba3d40bfdade50f61b

See more details on using hashes here.

File details

Details for the file tonnikala-1.0.0b4-cp35-cp35m-manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for tonnikala-1.0.0b4-cp35-cp35m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 a66ca214d122c3ee9409fe2d2f10cd5fb7885b723c4eab4a742ac342e5ade20c
MD5 83e451107e0e4b1b8d703e5132a2a1f4
BLAKE2b-256 c85595984f51d7af92cdd2371a8e25d14e64459b8c0c91062aa3346a2c17bcd8

See more details on using hashes here.

File details

Details for the file tonnikala-1.0.0b4-cp34-cp34m-manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for tonnikala-1.0.0b4-cp34-cp34m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 e9ec5058cfee271cb0426dd4360aad6e107575ae58ad74c508e1946da4c19120
MD5 3308105478b6b3814f5f49ee92983e50
BLAKE2b-256 c0e0324342a71b9e8f4c815b6dbcf5020d6aaf97fb62824410604e004e01fe00

See more details on using hashes here.

File details

Details for the file tonnikala-1.0.0b4-cp33-cp33m-manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for tonnikala-1.0.0b4-cp33-cp33m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 4c3b4b79ba9b06e7ff538a1962acd00128388e5e2ca5f31f0afa821e9f350cf9
MD5 e836fad4f77ae15e033fe5b517ad61b2
BLAKE2b-256 0460c104ad2b4912481e0ac6fe80226dab59ea6fe207e7ca0c661df92fdbe7bd

See more details on using hashes here.

File details

Details for the file tonnikala-1.0.0b4-cp27-cp27mu-manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for tonnikala-1.0.0b4-cp27-cp27mu-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 9b34a89ebd2449dce756a65ce3e1a389a7cf0c836ca09936e71694ad41b20676
MD5 47d0e38fca620c30686ab453e97097ee
BLAKE2b-256 b6e8fb49855c4d66d353565f985d0252f1b3dcc8f794fee703e722dd5dc143d2

See more details on using hashes here.

File details

Details for the file tonnikala-1.0.0b4-cp27-cp27m-manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for tonnikala-1.0.0b4-cp27-cp27m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 ec94e28e49e38575c3a084175694ee69199e273755154d7bea550fa7e6ee1241
MD5 e346d1e48aa807398ca893ef09c8cd49
BLAKE2b-256 e325194406edea8bcefe93e328ebce69279fcab0038b5433ebcabafa186fc48d

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page