PyconIE 2016 - Kajiki, the fast and validated template engine your were looking for

Preview:

Citation preview

KAJIKITHE FAST AND VALIDATED TEMPLATE

ENGINE YOU WERE LOOKING FOR

Alessandro Molina@__amol__

amol@turbogears.org

Who am I

● Passionate Python Developer

● TurboGears2 core team member

● Beaker caching/session framework current maintainer

● Author of DukPy js env for Python and DEPOT file storage framework

● Contributor to Ming, ToscaWidgets2, Formencode, WebOb, Kajiki, ...

Why?

● There are tens of template engines out there, but people often only know the default one of their web framework.

● Different template engines can differ in important features, not just syntax.

● Working on one has been fun and interesting.

Template Engines

TYPE NAME URL

Markup + Streamed Genshi http://genshi.edgewall.org/

Text + Compiled Mako http://www.makotemplates.org/

Text + Compiled Jinja http://jinja.pocoo.org/

Markup + Compiled Kajiki http://kajiki.readthedocs.io/

One more Template Engine?

● We loved the fact that Genshi templates could by opened with any HTML editor

● We loved Genshi syntax, concise and reflecting the output itself.

● We loved Genshi so much TG2.2 included it in all projects, to make it available for pluggable apps and extensions.

Right Timing...

How I felt...

Well...

● Genshi was pretty complex. We didn’t want to maintain such complexity.

● Streaming was powerful but hard to use, very few people understood genshi inheritance. And it was really slow.

● A TG team member was experimenting with the idea of a similar engine.

Kajiki<html>

<head>

<title py:content="title">This is replaced.</title>

</head>

<body>

<p>These are some fruits:</p>

<ul>

<li py:for="fruit in fruits">

I like ${fruit}s

</li>

</ul>

</body>

</html>

Directives● py:if & py:else● py:switch & py:case & py:else● py:for● py:def● py:strip● py:with● py:attrs● py:block & py:extends● ...

How It Works

● Your template is parsed and converted to Python code.

● Whenever the template is rendered the generated python code is executed.

● The python code is yielded by a generator that resembles the DOM of the document.

Architecture

PARSER

COMPILER

INTERMEDIATE REPRESENTATION

PYTHON

READ XML AND GENERATE DOM OUT OF IT WITH SAX

NAVIGATE DOM AND CREATE IR FOR ITS NODES

ITERATE THE IR NODES TO GENERATE PYTHON CODE

RUN PYTHON CODE TO RENDER TEMPLATE

Compiled Kajikiclass template:

@kajiki.expose

def __main__():

yield u'<html>\n <head>\n <title>'

yield self.__kj__.escape(title)

yield u'</title>\n </head>\n <body>\n <p>'

yield local.__kj__.gettext(u'These are some fruits:')

yield u'</p>\n <ul>\n '

for fruit in fruits:

yield u'<li>'

yield local.__kj__.gettext(u'\n I like ')

yield self.__kj__.escape(fruit)

yield local.__kj__.gettext(u's\n ')

yield u'</li>'

yield u'\n </ul>\n </body>\n</html>'

Rendered Kajiki<html>

<head>

<title>A Kajiki Template</title>

</head>

<body>

<p>These are some fruits:</p>

<ul>

<li>

I like oranges

</li><li>

I like apples

</li>

</ul>

</body>

</html>

That makes it pretty Fast

● Mako

○ Rendered 1M mako templates in 22.217651844

● Kajiki

○ Rendered 1M Kajiki templates in 11.8710489273

%for user in users:

<span>Hello {{ user }}!</span>

%endfor

<span py:for="user in users">

Hello ${user}!

</span>

It’s really just python! Mad science included!import kajiki

loader = kajiki.FileLoader('.', force_mode='xml')

tmpl = loader.load('mypage.kajiki')

with open('cython_test.pyx', 'wb') as pyx:

py_text = tmpl.py_text.replace('@kajiki.expose', '@staticmethod')

py_text = py_text.replace('__main__():', '__main__(self, local, users):')

py_text = py_text.replace('template = kajiki.Template(template)', '')

pyx.write(py_text)

import pyximport; pyximport.install()

import cython_test

class template:

@kajiki.expose

def __main__():

return cytmpl.__main__(self, local, users)

template = kajiki.Template(template)

print template(dict(cytmpl=cython_test.template, users=range(100000))).render()

Don’t try this at home

● Huge hack, but theoretically it can work. With minor tweaks Kajiki itself could generate cython compatible code.

● Rendering python 11.86215686798

● Rederning cython 9.07893800735

But being Python is easy to debug

Integrates with Python debuggers

Python Syntax Checking

kajiki.template.KajikiSyntaxError: [<string>:9]

invalid syntax

yield local.__kj__.gettext(u'These are some of

my favorite fruits:')

yield u'</p>\n <ul>\n '

--> for fruit on fuits:

yield u'<li>'

yield local.__kj__.gettext(u'\n I like ')

Validated Templates

● As Kajiki understands the document you are writing (it’s not just text) it can take steps specific to HTML generation:○ Error Reporting○ Escaping○ Automatic i18n○ Minification

Malformed HTML Detection

kajiki.xml_template.XMLTemplateParseError:

[./plain.kajiki:10] mismatched tag

<li py:for="fruit in fruits">

I like ${fruit}s

--> </span>

</ul>

</body>

Escaping handled for us<li py:for="fruit in fruits">

I like ${fruit}s

</li>

template(dict(

fruits=['<apple>'],

title='A Kajiki Template'

)).render()

<li>

I like &lt;apple&gt;s

</li>

Easy Translations<li py:for="fruit in fruits">

I like ${fruit}s

</li>

for fruit on fuits:

yield u'<li>'

yield local.__kj__.gettext(u'\n I like ')

Minificationtmpl = loader.load('plain.kajiki', strip_text=False)

<ul>

<li>

I like oranges

</li><li>

I like apple

</li><li>

I like kiwis

</li>

</ul>

tmpl = loader.load('plain.kajiki', strip_text=True)

<ul><li>I like oranges</li><li>I like apple</li><li>I like

kiwis</li></ul>

Text engine available too>>> Template = kajiki.TextTemplate('''

... {%if foo %}

... bar

... {%else%}

... baz

... {%end%}

... ''')

>>> print(Template(dict(foo=True)).render())

bar

>>> print(Template(dict(foo=False)).render())

baz

Feel free to try it!

● Python 2.6, 2.7, 3.2, 3.3, 3.4 and 3.5

● pip install kajiki

● Come and try it!

https://github.com/nandoflorestan/kajiki

● Still young! Feel free to open issues, send

pull requests, suggest features!

Questions?

Recommended