Pyjo.Template - Python-ish templates

from Pyjo.Template import Template

# Use Python modules
mt = Template()
print(mt.render('''
% import time
<div>
  % now = time.gmtime(0)
  Time: <%= time.strftime('%H:%M:%S', now) %>
</div>
'''))

# Or import the module, as in Google Python Style Guide

import Pyjo.Template

# Use Python modules
mt = Pyjo.Template.new()
print(mt.render('''
% import time
<div>
  % now = time.gmtime(0)
  Time: <%= time.strftime('%H:%M:%S', now) %>
</div>
'''))

# Render with arguments
print(mt.render('''
% numbers, title = args
<div>
  <h1><%= title %></h1>
  % for i in numbers {
    Test <%= i %>
  % }
</div>
''', range(1, 14), 'Hello World!'))

# Render with named variables
print(Pyjo.Template.new(vars=True).render('''
<div>
  <h1><%= title %></h1>
  %= 5 + 5
</div>
''', {'title': 'Hello World!'}))

Pyjo.Template is a minimalistic, fast, and very Python-ish template engine, designed specifically for all those small tasks that come up during big projects. Like preprocessing a configuration file, generating text from heredocs and stuff like that.

Syntax

<% Python code %>
<%= Python expression, replaced with result %>
<%== Python expression, replaced with XML escaped result %>
<%# Comment, useful for debugging %>
<%% Replaced with "<%", useful for generating templates %>
% Python code line, treated as "<% line =%>" (explained later)
%= Python expression line, treated as "<%= line %>"
%== Python expression line, treated as "<%== line %>"
%# Comment line, useful for debugging
%% Replaced with "%", useful for generating templates

Escaping behavior can be reversed with the auto_escape attribute.

<%= Python expression, replaced with XML escaped result %>
<%== Python expression, replaced with result %>

Markup, an object with the method __html__, as Pyjo.String.HTML, is always excluded from automatic escaping, and a value is rendered as an interpolation of Pyjo.String.Template: None is nothing, a template is markup, and a list is rendered item by item.

% from Pyjo.String.HTML import html
<%= html('<div>excluded!</div>') %>

The indentation of the template does not matter: a code line that ends with : opens a block, which end closes, and a line that starts with elif, else, except, finally or case closes the block before it and opens the next one. end closes the whole match after its last case. Braces can open and close a block too, which makes one-liners easy: { at the end of a line that starts with a keyword of a block opens it, and } at the start of a line closes it, as in } else {. Any other brace is the one of Python, so a dict or a set that starts at the end of such a line is put in parentheses.

% for name in ['Baerbel', 'Wolfgang']:
  % if name.startswith('B'):
<b><%= name %></b>
  % else:
<i><%= name %></i>
  % end
% end
<% for i in range(3) { %><%= i %><% } %>

Whitespace characters around tags can be trimmed by adding an additional equal sign to the end of a tag.

<% for i in range(1, 4) { %>
  <%= 'Trim all whitespace characters around this expression' =%>
<% } %>

Newline characters can be escaped with a backslash.

This is <%= 1 + 1 %> a\
single line

And a backslash in front of a newline character can be escaped with another backslash.

This will <%= 1 + 1 %> result\\
in multiple\\
lines

A newline character gets appended automatically to every template, unless the last character is a backslash. And empty lines at the end of a template are ignored.

There is <%= 1 + 1 %> no newline at the end here\

You can capture whole template blocks for reuse later with the begin and end keywords. Just be aware that both keywords are part of the surrounding tag and not actual Python code, so there can only be whitespace after begin and before end.

<% block = begin %>
  <% name = args[0] =%>
  Hello <%= name %>.
<% end %>
<%= block('Baerbel') %>
<%= block('Wolfgang') %>

Python lines can also be indented freely.

% block = begin
  % name = args[0]
  Hello <%= name %>.
% end
%= block('Baerbel')
%= block('Wolfgang')

Pyjo.Template templates get compiled to a Python function, that means you can access arguments simply via args.

% foo, bar = args
% x = args[0]
test 123 <%= foo %>

The compilation of templates to Python code can make debugging a bit tricky, but Pyjo.Template will return Pyjo.Exception objects that stringify to error messages with context.

name 'xx' is not defined at template line 4.
Context:
  1: <html>
  2: </head>
  3: <body>
  4: % i = 2; xx
  5: %= i * 2
  6: </body>
Traceback (most recent call first):
  File "template", line 4, in "_template"
  File "path/to/Pyjo/Template.py", line 123, in "process"
  File "path/to/myapp.py", line 123, in "<module>"

Debugging

You can set the PYJO_TEMPLATE_DEBUG environment variable to get some advanced diagnostics information printed to STDERR.

PYJO_TEMPLATE_DEBUG=1

See also

Pyjoyment, https://mojolicious.org.

Ported from Mojo::Template of Mojolicious 9.49.

Classes

class Pyjo.Template.Pyjo_Template(*, append: str = '', auto_escape: bool | None = None, capture_end: str = 'end', capture_start: str = 'begin', code: str = '', comment_mark: str = '#', compiled: Callable[[...], Any] | None = None, encoding: str | None = 'UTF-8', escape: Callable[[Any], str] | None = None, escape_mark: str = '=', expression_mark: str = '=', line_start: str = '%', name: str | None = None, namespace: str = 'Pyjo.Template.Sandbox', prepend: str = '', replace_mark: str = '%', tag_end: str = '%>', tag_start: str = '<%', tree: list[list[str]] | None = None, trim_mark: str = '=', unparsed: str = '', vars: bool | None = None)

Pyjo.Template inherits all methods from Pyjo.Base and implements the following new ones.

append
code = mt.append
mt.append = 'print("Processed template")'

Append Python code to compiled template, as lines of their own.

auto_escape
boolean = mt.auto_escape
mt.auto_escape = boolean

Activate automatic escaping.

# "&lt;html&gt;"
Pyjo.Template.new(auto_escape=True).render("<%= '<html>' %>")
capture_end
end = mt.capture_end
mt.capture_end = 'end'

Keyword indicating the end of a capture block, defaults to end.

<% block = begin %>
  Some data!
<% end %>
capture_start
start = mt.capture_start
mt.capture_start = 'begin'

Keyword indicating the start of a capture block, defaults to begin.

<% block = begin %>
  Some data!
<% end %>
code
code = mt.code
mt.code = code

Python code for template if available.

comment_mark
mark = mt.comment_mark
mt.comment_mark = '#'

Character indicating the start of a comment, defaults to #.

<%# This is a comment %>
compiled
compiled = mt.compiled
mt.compiled = compiled

Compiled template code if available.

encoding
encoding = mt.encoding
mt.encoding = 'UTF-8'

Encoding used for template files, defaults to UTF-8.

escape: Callable[[Any], str]
cb = mt.escape
mt.escape = lambda value: ...

A callback used to escape the results of escaped expressions, defaults to Pyjo.String.Template.escape().

mt.escape = lambda value: str(value)[::-1]
escape_mark
mark = mt.escape_mark
mt.escape_mark = '='

Character indicating the start of an escaped expression, defaults to =.

<%== foo %>
expression_mark
mark = mt.expression_mark
mt.expression_mark = '='

Character indicating the start of an expression, defaults to =.

<%= foo %>
line_start
start = mt.line_start
mt.line_start = '%'

Character indicating the start of a code line, defaults to %.

% foo = 23
property name: str
name = mt.name
mt.name = 'foo.ep'

Name of template currently being processed, defaults to template.

namespace
namespace = mt.namespace
mt.namespace = 'main'

Namespace used to compile templates, defaults to Pyjo.Template.Sandbox: the module of the name, if it is loaded, or a module of its own. Note that namespaces should only be shared very carefully between templates, since the globals of the module will not be cleared automatically.

parse(template: str) → Self
mt = mt.parse('<%= 1 + 1 %>')

Parse template into tree.

prepend
code = mt.prepend
mt.prepend = 'this = args[0]'

Prepend Python code to compiled template, as lines of their own.

process(*args: Anything) → Any
output = mt.process()
output = mt.process(*args)
output = mt.process({'foo': 'bar'})

Process previously parsed template and return the result, or a Pyjo.Exception object if rendering failed.

# Parse and process
print(Pyjo.Template.new().parse('Hello <%= args[0] %>').process('Bender'))

# Reuse template (for much better performance)
mt = Pyjo.Template.new()
print(mt.render('Hello <%= args[0] %>!', 'Bender'))
print(mt.process('Fry'))
print(mt.process('Leela'))
render(template: str, *args: Anything) → Any
output = mt.render('<%= 1 + 1 %>')
output = mt.render('<%= args[0] + args[1] %>', *args)
output = mt.render('<%= foo %>', {'foo': 'bar'})

Render template and return the result, or a Pyjo.Exception object if rendering failed.

# Longer version
output = mt.parse('<%= 1 + 1 %>').process()

# Render with arguments
print(Pyjo.Template.new().render('<%= args[0] %>', 'bar'))

# Render with named variables
print(Pyjo.Template.new(vars=True).render('<%= foo %>', {'foo': 'bar'}))
render_file(path: Anything, *args: Anything) → Any
output = mt.render_file('/tmp/foo.ep')
output = mt.render_file('/tmp/foo.ep', *args)
output = mt.render_file('/tmp/bar.ep', {'foo': 'bar'})

Same as render(), but renders a template file.

replace_mark
mark = mt.replace_mark
mt.replace_mark = '%'

Character used for escaping the start of a tag or line, defaults to %.

<%% foo = 23 %>
tag_end
end = mt.tag_end
mt.tag_end = '%>'

Characters indicating the end of a tag, defaults to %>.

<%= foo %>
tag_start
start = mt.tag_start
mt.tag_start = '<%'

Characters indicating the start of a tag, defaults to <%.

<% foo = 23 %>
tree: list[list[str]]
tree = mt.tree
mt.tree = [['text', 'foo'], ['line']]

Template in parsed form if available. Note that this structure should only be used very carefully since it is very dynamic.

trim_mark
mark = mt.trim_mark
mt.trim_mark = '-'

Character activating automatic whitespace trimming, defaults to =.

<%= foo =%>
unparsed
unparsed = mt.unparsed
mt.unparsed = '<%= 1 + 1 %>'

Raw unparsed template if available.

vars
boolean = mt.vars
mt.vars = boolean

Instead of a list of values, use a dict with named variables to pass data to templates.

# "works!"
Pyjo.Template.new(vars=True).render('<%= test %>!', {'test': 'works'})