Symplate
Symplate, a simple and fast Python template language (NOTE: no longer maintained; use Jinja2 or Mako instead)
Install / Use
/learn @benhoyt/SymplateREADME
Symplate, the Simple pYthon teMPLATE renderer
NOTE: I haven't maintained Symplate for years, and don't recommend its use anymore. Use the ubiquitous Jinja2 or the solid and fast Mako instead.
Symplate is a very simple and very fast Python template language.
- Background
- FAQ -- Who | Why | Performance
- Basic usage
- Compiled Python output
- Syntax -- Directives | Whitespace | Comments | Literals
- Filters -- Default | Raw | Setting | Overriding
- Including sub-templates
- Customizing Renderer
- Unicode handling
- Command line usage
- Meta -- Bottle | To-do | Feedback
Background
When I got frustrated with the complexities and slow rendering speed of Cheetah, I started wondering just how simple a template language could be.
You could write templates in pure Python, but that's somewhat painful -- code and text are hard to intersperse, and you don't get auto-escaping. But why not a KISS template-to-Python translator? Enter Symplate:
textbecomes_write('text'){{ expr }}becomes_write(filt(expr)){% code %}becomescodeat the correct indentation level- indentation increases when a code line ends with a colon, as in
{% for x in lst: %} - indentation decreases when you say
{% end %}
That's about all there is to it. All the rest is detail.
FAQ
Who uses Symplate?
Only me ... so far. It started as my experiment. That said, Symplate is now a proper library, and fairly well tested. I also "ported" my GiftyWeddings.com website from Cheetah to Symplate, and it's working very well.
Why use Symplate?
If you care about raw performance or simplicity of implementation, Symplate might be for you. I care about both, and I haven't needed some of the extra features other systems provide, such as sandboxed execution and template inheritance. If you want a Porsche, use Symplate. If you'd prefer a Volvo or BMW, I'd recommend Jinja2 or Mako.
Symplate is dead simple: a couple of pages of code translate your templates to
Python .py files, and render() imports and executes the compiled output.
Symplate's also about as fast as a pure-Python template language can be. Partly because it's simple, it produces Python code as tight as you'd write it by hand.
Isn't worrying about performance silly?
Yes, I know, worrying about template performance is silly. Some of the time. But when you're caching everything to avoid database requests, and your "business logic" is pretty tight, template rendering is all that's left. And Cheetah (not to mention Django!) are particlarly slow.
If you're running a large-scale website and you're caching things so that template rendering is your bottleneck (yes, I've been there) ... then if you can take your render times down from 100ms to 20ms, you can run your website on 1/5th the number of servers.
So how fast is Symplate? As mentioned, it's about as fast as you can hand-code Python. Here's the Symplate benchmark showing compile and render times for some of the fast or popular template languages.
Times are normalized to the HandCoded render time:
engine compile render
----------------------------------
HandCoded 0.002 1.000
Symplate 1.0 13.471 1.178
Wheezy 30.173 1.398
Bottle 0.11.rc1 10.556 2.664
Mako 0.7.2 62.982 3.886
Jinja2 2.6 68.146 5.713
Cheetah 2.4.4 124.748 5.986
Django 1.3.3 7.919 21.296
I ran these benchmarks on my Intel Core i5-2450 on Windows 7, running CPython 2.7.3 64-bit.
Basic usage
Let's start with a simple example that uses more or less all the features of
Symplate. Our main template file is blog.symp:
{% template entries, title='My Blog' %}
{{ !render('inc/header', title) }}
<h1>This is {{ title }}</h1>
{% for entry in entries: %}
<h2><a href="{{ entry.url }}">{{ entry.title.title() }}</a></h2>
{{ !entry.html_body }}
{% end for %}
</ul>
{{ !render('inc/footer') }}
In true Python style, everything's explicit. We explicitly specify the
parameters the template takes in the {% template ... %} line, including the
default parameter title.
For simplicity, there's no special "include" directive -- you just render()
a sub-template -- usually with the ! prefix to
mean don't filter the rendered output.
In this example, entry.html_body contains pre-rendered HTML, so this
expression is also prefixed with ! -- it will output the HTML body as a
raw, unescaped string.
Then inc/header.symp looks like this:
{% template title %}
<html>
<head>
<meta charset="UTF-8" />
<title>{{ title }}</title>
</head>
<body>
And inc/footer.symp is of course:
{% template %}
</body>
</html>
To compile and render the main blog template in one fell swoop, set entries
to a list of blog entries with the url, title, and html_body attributes,
and you're away:
renderer = symplate.Renderer(template_dir)
def homepage():
return renderer.render('blog', entries, title="Ben's Blog")
You can customize your Renderer to specify a different output directory, or to turn on checking of template file mtimes for debugging. For example:
renderer = symplate.Renderer(template_dir, output_dir='out',
check_mtime=settings.DEBUG)
def homepage():
entries = load_blog_entries()
return renderer.render('blog', entries, title="Ben's Blog")
Compiled Python output
Symplate is a leaky abstraction, but is somewhat proud of that fact. I already knew Python well, so my goal was to be as close to Python as possible -- I don't want to learn another language just to produce some escaped HTML.
In any case, you're encouraged to look at the compiled Python output produced
by the Symplate compiler (usually placed in a symplouts directory at the
same level as your template directory).
You might be surprised how simple the compiled output is. Symplate tries to
make the compiled template look much like it would if you were writing it by
hand -- for example, short strings are output as 'shortstr', and long,
multi-line strings as """long, multi-line strings""".
The blog.symp example above produces this in blog.py:
import symplate
def _render(_renderer, entries, title='My Blog'):
filt = symplate.html_filter
render = _renderer.render
_output = []
_writes = _output.extend
_writes((
render('inc/header', title),
u'\n<h1>This is ',
filt(title),
u'</h1>\n',
))
for entry in entries:
_writes((
u' <h2><a href="',
filt(entry.url),
u'">',
filt(entry.title.title()),
u'</a></h2>\n ',
entry.html_body,
u'\n',
))
_writes((
u'</ul>\n',
render('inc/footer'),
u'\n',
))
return u''.join(_output)
As you can see, apart from a tiny premable, it's about as fast and direct as it could possibly be in pure Python.
Basic Symplate syntax errors like mismatched {%'s are raised as
symplate.Errors when the template is compiled. However, most Python
expressions are copied directly to the Python output, so you'll only get a
Python SyntaxError when the compiled template is imported at render time.
(Yes, this is a minor drawback of Symplate's KISS approach. However, because Symplate is such a direct mapping to Python, it's usually easy to find errors in your templates.)
Syntax
Symplate has very little syntax of its own, but here's what you need to know:
Directives
The only directives or keywords in Symplate are template and end. Oh, and
"colon at the end of a code line".
{% template [args] %} must appear at the start of a template before any
output. args is the argument specification including positional and
keyword/default arguments, just as if it were a function definition. In fact,
it is -- {% template [args] %} gets compiled to
def render(_renderer, args): ....
If you need to import other modules, do so at the top of your template, above
the template directive (just like how in Python you import before writing
code).
{% end [...] %} ends a code indentation block. All it does is reduce the
indentation level in the compiled Python output. The ... is optional, and
acts as a comment, so you can say {% end for %} or {% end if %} if you
like.
A : (colon) at the end of a code block starts a code indentation block, just
like in Python. However, there's a special case for the elif, else,
except and finally keywords -- they dedent for the line the keyword is on,
and then indent again (just like you would when writing Python).
Whitespace handling
Symplate has some very simple rules for whitespace handling. The idea is to do what's normal for the common case, but you can always insert extra whitespace to get
