Undefined handling and debugging

Jinja · Advanced · 8 min

Silently empty output from a typo is a classic template bug, especially in config generation.

When a template references something that doesn't exist, Jinja creates an undefined object. The class used is set on the Environment with undefined=:

  • Undefined (default): prints as empty, is falsy and iterates as empty, but raises UndefinedError on attribute access ({{ db.host }} with no db) or arithmetic
  • ChainableUndefined: also allows a.b.c chains on undefined values, returning undefined instead of raising, handy with deeply optional data
  • DebugUndefined: prints the expression back, such as {{ user.name }}, so missing values stand out in output
  • StrictUndefined: raises UndefinedError on almost any use, including printing, iterating and truth tests

StrictUndefined is the safest choice for generated configuration, emails and anything where a blank is a bug. Within it, you can still handle optional values explicitly with {% if x is defined %} or x|default("..."), which work because tests and default don't trigger the error.

To find out which variables a template expects, parse it and ask:

from jinja2 import meta
meta.find_undeclared_variables(env.parse(source))

That is useful for validating that a caller provides everything, or for documenting template inputs.

Error messages from Jinja include the template name and line number in the traceback, because compiled templates keep a mapping back to template lines.

Example

from jinja2 import ChainableUndefined, DebugUndefined, Environment, StrictUndefined, meta
from jinja2.exceptions import UndefinedError

src = "host={{ db_host }} port={{ port|default(5432) }} {% if debug is defined %}debug{% endif %}"

print(Environment().from_string(src).render())                 # host= port=5432
print(Environment(undefined=DebugUndefined).from_string(src).render())
# host={{ db_host }} port=5432
try:
    Environment(undefined=StrictUndefined).from_string(src).render()
except UndefinedError as e:
    print("error:", e)                                     # error: 'db_host' is undefined
print(Environment(undefined=StrictUndefined).from_string(src).render(db_host="h"))
# host=h port=5432
print(Environment(undefined=ChainableUndefined).from_string("[{{ a.b.c }}]").render())  # []

env = Environment()
print(sorted(meta.find_undeclared_variables(env.parse(src))))  # ['db_host', 'debug', 'port']

Remember

  • The undefined= option decides what missing values do
  • StrictUndefined turns typos into errors; default and is defined still work
  • meta.find_undeclared_variables lists a template's inputs

Watch out for

  • Generating config files with the default Undefined, so a typo produces an empty value.

Check yourself

  1. With StrictUndefined, what does {{ missing|default("x") }} render?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: jinja.palletsprojects.com/en/stable/api/#undefined-types