Contextual Template Analysis
New in version 1.3.0
Complementing static template analysis, added in Python Liquid version 1.2.0, contextual template analysis renders a template and captures information about template variable and filter usage as it goes.
Given some render context data, BoundTemplate.analyze_with_context()
will visit nodes in a template's syntax tree as if it were being rendered, excluding those nodes that are not reachable using the current render context.
Limitations
Due to some unfortunate design decisions, Python Liquid does not support template introspection from within a render context or Expression
object. Meaning line numbers and template names are not available when using contextual template analysis. Only variable names are reported along with the number of times they were referenced. This is not the case with static template analysis.
It's also not currently possible to detect names added to a block's scope. For example, forloop.index
will be included in the results object if referenced within a for loop block.
Usage
BoundTemplate.analyze_with_context()
and BoundTemplate.analyze_with_context_async()
accept the same arguments as BoundTemplate.render()
. The returned object is an instance of ContextualTemplateAnalysis
. Each of its properties is a dictionary mapping template variable name to the number of times that name was referenced.
ContextualTemplateAnalysis.all_variables
includes all variable names discovered while rendering a template given some render context data. It will not include variables from blocks that would not have been rendered.
from liquid import Template
template = Template("""\
{% assign fallback = 'anonymous' %}
{% if user %}
Hello, {{ user.name }}.
{% else %}
Hello, {{ fallback }}
{% endif %}
""")
# `user` is undefined
analysis = template.analyze_with_context()
print(list(analysis.all_variables))
# `user` is defined
analysis = template.analyze_with_context(user={"name": "Sally"})
print(list(analysis.all_variables))
['user', 'fallback']
['user', 'user.name']
Local Variables
ContextualTemplateAnalysis.local_variables
includes variable names that have been assigned with the assign
, capture
, increment
or decrement
tags, or any custom tag that uses Context.assign()
.
from liquid import Template
template = Template("""\
{% assign fallback = 'anonymous' %}
{% if user %}
Hello, {{ user.name }}.
{% else %}
Hello, {{ fallback }}
{% endif %}
""")
# `user` is undefined
analysis = template.analyze_with_context()
print(list(analysis.local_variables))
['fallback']
Undefined variables
ContextualTemplateAnalysis.undefined_variables
includes variable names that could not be resolved in the current render context. If a name is referenced before it is assigned, it will appear in undefined_variables
and local_variables
.
from liquid import Template
template = Template("""\
{% assign fallback = 'anonymous' %}
{{ nosuchthing }}
{% if user %}
Hello, {{ user.name }}.
{% else %}
Hello, {{ fallback }}
{% endif %}
""")
# `user` is undefined
analysis = template.analyze_with_context()
print(list(analysis.undefined_variables))
['nosuchthing', 'user']
Filters
New in version 1.7.0
ContextualTemplateAnalysis.filters
includes the names of filters used in a template, including those found in included or rendered templates.
from liquid import Template
template = Template(
"""\
{% assign fallback = 'anonymous' %}
{{ nosuchthing }}
{% if user %}
Hello, {{ user.name | upcase }}.
{% else %}
Hello, {{ fallback | downcase }}
{% endif %}
"""
)
analysis = template.analyze_with_context(user="Sue")
print(list(analysis.filters))
['upcase']