aosabook/500lines · error · TempliteSyntaxError
%s: %r
Error message
%s: %r
What it means
Raised by Code._syntax_error in the Templite template engine (template-engine chapter). During compilation, when the compiler detects malformed template syntax it calls _syntax_error(msg, thing), which raises TempliteSyntaxError formatted as '%s: %r' % (msg, thing) — a human message plus a repr of the offending token/node. It surfaces only at compile time, before any template is rendered.
Source
Thrown at template-engine/template-engine.markdown:1208
```python
else:
self._variable(expr, self.all_vars)
code = "c_%s" % expr
return code
```
<!-- [[[end]]] -->
#### Helper Functions
During compilation, we used a few helper functions. The `_syntax_error` method
simply puts together a nice error message and raises the exception:
<!-- [[[cog include("templite.py", first="def _syntax_error", numblanks=1, dedent=False) ]]] -->
```python
def _syntax_error(self, msg, thing):
"""Raise a syntax error using `msg`, and showing `thing`."""
raise TempliteSyntaxError("%s: %r" % (msg, thing))
```
<!-- [[[end]]] -->
The `_variable` method helps us with validating variable names and adding them
to the sets of names we collected during compilation. We use a
regex to check that the name is a valid Python identifier, then add the name to
the set:
<!-- [[[cog include("templite.py", first="def _variable", numblanks=4, dedent=False) ]]] -->
```python
def _variable(self, name, vars_set):
"""Track that `name` is used as a variable.
Adds the name to `vars_set`, a set of variable names.
Raises an syntax error if `name` is not a valid name.
"""View on GitHub (pinned to fba689d101)
Solutions
- Read the %s message and the %r token in the exception to locate the offending construct.
- Balance every {% %} block with its matching {% end %} and verify {{ }} braces close.
- Use only the tags the engine supports (if/else/endif, for/endfor via end); avoid foreign template dialects.
- Lint the template by compiling it in a test before deploying.
Example fix
# before: unclosed block, unknown tag
Templite('{% for x in xs %}{{ x }}') # -> TempliteSyntaxError
Templite('{% bob %}') # unknown keyword
# after: close blocks, use supported tags
Templite('{% for x in xs %}{{ x }}{% end %}')
Templite('{% if x %}yes{% else %}no{% end %}') Defensive patterns
Strategy: validation
Validate before calling
import re
TAG = re.compile(r'\{%.*?%\}|\{\{.*?\}\}')
def lint_template(text):
for m in TAG.finditer(text):
tok = m.group()
if tok.startswith('{%') and not tok.strip().endswith('%}'):
return (False, 'unclosed tag: %r' % tok)
return True, ''
ok, why = lint_template(tpl)
if not ok:
raise ValueError(why)
Templite(tpl) Type guard
def is_balanced(text):
return text.count('{%') == text.count('%}') and text.count('{{') == text.count('}}') Try / catch
try:
t = Templite(source, **context)
except TempliteSyntaxError as e:
log.error('template syntax error: %s', e)
raise Prevention
- Compile templates in unit tests, not lazily at first request.
- Pair every {% if %}/{% for %} with the engine's {% end %} (check dialect).
- Restrict variable names to the Python-identifier regex the engine enforces.
- Echo the offending %r token from the exception into your editor to jump to it.
When it happens
Trigger: Compiling a template string with malformed syntax: an unclosed {% %} or {{ }} block, a {% %} tag in an expression context, a malformed {% if %}/{% for %} construct, an unrecognised tag keyword, or an invalid variable name rejected by _variable.
Common situations: Hand-editing a template and leaving a dangling delimiter; copy-pasting a fragment that opens a block but never closes it; using Jinja2 syntax ({% endif %} vs this engine's {% end %}) by mistake; a variable name containing characters the identifier regex rejects.
AI-assisted analysis of aosabook/500lines@fba689d101 (2026-08-13).
Data as JSON: /api/errors/eca3abb517526680.
Report an issue: GitHub.