Inspecting a template before rendering¶
"What does this template need?" is worth being able to answer before the render, not after: it is what lets an application build a form, validate a payload, or fail with a message naming what is missing instead of emitting a document full of blanks.
from docxtpl import Template
tpl = Template("invoice.docx")
tpl.undeclared_variables()
# {'customer', 'order', 'lines', 'total'}
The answer covers every part — a name used only in a footer or only in the document title is reported — and asking does not change the document: the inspection compiles a copy.
missing = tpl.undeclared_variables() - set(context)
if missing:
raise ValueError(f"the template needs {sorted(missing)}, which the context lacks")
tpl.render(context)
What counts as needed¶
The answer comes from Jinja2's parsed syntax tree rather than from a search for
{{, which is what makes it right in the cases that matter:
- a name introduced by the template itself is not reported.
linein{%tr for line in lines %}is the loop's own variable;linesis what the caller has to supply; - a name used only inside a branch is reported, because the branch might be taken;
- a name used only as
{{ customer.name }}is reported ascustomer, since that is what the context has to hold.
Is the template even valid?¶
The other question, and it needs no context at all:
report = tpl.validate()
report.ok # -- False --
report.variables # -- ['customer', 'lines', 'order', 'total'] --
report.problems # -- [Problem(part='header 1', message='{% endfor %} is …')] --
validate() compiles and parses every part, which is where the two halves of
being valid are found: compiling catches a tag whose scope prefix names an
element that does not contain it, and parsing catches a {% for %} with no
{% endfor %}. Nothing is rendered and nothing is written.
A part that did not compile contributes no variables, because there is no honest way to say what a template one cannot parse requires.
Requiring the context to be complete¶
A tag whose variable the context does not supply renders to nothing:
Which is often what a template means — an optional field. When it is not,
strict=True makes it an error naming the variable and the part:
This is a better answer than a "was it fully rendered" flag checked afterwards, because nothing after the render can tell a missing value from one that was legitimately empty.
Errors that name where they came from¶
When a render does fail, the part is in the message — which a Jinja2 traceback has no way of knowing:
from docxtpl import RenderError
try:
tpl.render(context)
except RenderError as error:
print(error) # -- 'order' is undefined (in footer 1) --
print(error.part_name) # -- 'footer 1' --
raise error.__cause__ from None # -- the original, if you want it --
Every error this package raises derives from DocxTemplateError, so one clause
catches the lot:
| Exception | Raised when |
|---|---|
TemplateSyntaxError |
a tag is unterminated, or scoped to an element that does not contain it |
UnsupportedTagError |
a table directive is used outside a table, or given an argument it cannot use |
StyleError |
a rendered value named a style a run cannot carry, or one that does not exist |
ImageError |
an image source is not in a format that can be read |
RenderError |
rendering failed — by Jinja2's doing or a context object's |
StyleError, ImageError and MediaNotFoundError are ValueErrors as well,
because that is what python-docx-ng and docxtpl raise for the equivalent
mistakes, so an existing except ValueError keeps working.
A TemplateSyntaxError quotes the surrounding document text, so the tag can be
found in Word:
{%tr endfor %} is scoped to w:tr, which does not contain it.
Found in: 'Totals per line {%tr endfor %}'
From the command line¶
The quickest way to find out what a template does — and the quickest way to reproduce a bug report, since it needs a template and a JSON file rather than a program:
| Option | Effect |
|---|---|
--validate |
check the template and list what it needs; write nothing |
--report FILE |
write the check as JSON to FILE, for CI. Implies --validate |
--strict |
a variable the context does not supply is an error, not a blank |
-o, --overwrite |
replace the output file; without it, an existing file is refused |
-q, --quiet |
say nothing on success |
--no-escape |
do not XML-escape values, as the compatibility API does not |
--no-sandbox |
render without Jinja2's sandbox, which is on by default here |
It exits non-zero and prints one line to stderr on failure, without a traceback: the errors this package raises are written for a person to act on.
The sandbox default is the opposite of the library's, and Security says why.
Checking a template in CI¶
$ docxtpl-ng --validate invoice.docx
invoice.docx is valid
requires: customer, lines, order, total
$ docxtpl-ng --validate broken.docx
docxtpl-ng: broken.docx: document body: {% endfor %} is scoped to w:tr, which does
not contain it. Found in: 'Totals per line {%tr endfor %}'
$ echo $?
1
--validate takes a template and nothing else. --report writes the same
answer as JSON, in a shape that changes only by gaining keys: