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

  1. Read the %s message and the %r token in the exception to locate the offending construct.
  2. Balance every {% %} block with its matching {% end %} and verify {{ }} braces close.
  3. Use only the tags the engine supports (if/else/endif, for/endfor via end); avoid foreign template dialects.
  4. 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

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.