Whitespace control within the Jinja2 templating language can be a fickle beast if you don’t follow a few simple rules. Please keep in mind this an opinionated view about what “looks better” and “is easier” to implement, and not an industry standard. With that disclaimer out of the way, let’s jump into it.
There are two primary issues that I have observed in Jinja whitespace.
Empty lines, often seen within for loops or conditionals not met.
>>> from jinja2 import Template
>>>
>>> vlan_j2 = '''{% for vlan in vlans %}
... vlan {{ vlan['id'] }}
... {% if vlan.get('name') %}
... name {{ vlan['name'] }}
... {% endif %}
... {% endfor %}'''
>>>
>>> vlans = [{"id": "10"}, {"id": "20", "name": "printer"}]
>>>
>>> t = Template(vlan_j2)
>>>
>>> print(t.render(vlans=vlans))
vlan 10
vlan 20
name printer
>>>
Well, that is obviously not the output one would want. You can use the Jinja strip method -
to indicate to strip the line, as seen here.
>>> from jinja2 import Template
>>>
>>> vlan_j2 = '''{% for vlan in vlans -%}
... vlan {{ vlan['id'] }}
... {%- if vlan.get('name') %}
... name {{ vlan['name'] }}
... {%- endif %}
... {% endfor %}'''
>>>
>>> vlans = [{"id": "10"}, {"id": "20", "name": "printer"}]
>>>
>>> t = Template(vlan_j2)
>>>
>>> print(t.render(vlans=vlans))
vlan 10
vlan 20
name printer
>>>
Certainly looks better, but now you have to account for where to the put the dash (-
). In building this, it actually took me a few tries to get it right. It will only get more difficult to manage with more complexity.
Whitespace at that start of the line is added in order to have nested conditionals.
Reusing that same example, within nested conditionals, best practice is to introduce nesting to provide the visual cues for how nested the object is.
{% for vlan in vlans %}
vlan {{ vlan['id'] }}
{% if vlan.get('name') %}
name {{ vlan['name'] }}
{% endif %}
{% endfor %}
Now let’s see what happens when we render this.
>>> from jinja2 import Template
>>>
>>> vlan_j2 = '''{% for vlan in vlans %}
... vlan {{ vlan['id'] }}
... {% if vlan.get('name') %}
... name {{ vlan['name'] }}
... {% endif %}
... {% endfor %}'''
>>>
>>> vlans = [{"id": "10"}, {"id": "20", "name": "printer"}]
>>>
>>> t = Template(vlan_j2, trim_blocks=True)
>>>
>>> print(t.render(vlans=vlans))
vlan 10
vlan 20
name printer
>>>
A keen eye will note that we introduced
trim_blocks=True
more to come on that later.
As you can see, this does not follow the spacing expected. I have seen others take the approach of “padding” the conditionals, for example.
{% for vlan in vlans %}
vlan {{ vlan['id'] }}
{% if vlan.get('name') %}
name {{ vlan['name'] }}
{% endif %}
{% endfor %}
While certainly a step in the right direction, their is still no whitespace indicator at the start of the line.
As already hinted, there are options that can be used to get the desired effect. Specifically the trim_blocks
and lstrip_blocks
methods.
>>> from jinja2 import Template
>>>
>>> vlan_j2 = '''{% for vlan in vlans %}
... vlan {{ vlan['id'] }}
... {% if vlan.get('name') %}
... name {{ vlan['name'] }}
... {% endif %}
... {% endfor %}'''
>>>
>>> vlans = [{"id": "10"}, {"id": "20", "name": "printer"}]
>>>
>>> t = Template(vlan_j2, trim_blocks=True, lstrip_blocks=True)
>>>
>>> print(t.render(vlans=vlans))
vlan 10
vlan 20
name printer
>>>
trim_blocks
will remove first newline after a template tag is removed automatically, similar to adding a -
to the tag. lstrip_blocks
will strip tabs and spaces from the beginning of a line to the start of a block.
The result is that you can have a tag indented with only whitespace and have the next line automatically removed.
The preceding example should account for nearly all of the whitespace use cases in building network CLI configurations. I rarely, if ever, touch anything else.
In Ansible, trim_blocks
is already set to True
by default, and you can set lstrip_blocks
by adding a line to the top of your Jinja file. The below example shows how it should look.
#jinja2: lstrip_blocks: True
{% for vlan in vlans %}
vlan {{ vlan['id'] }}
{% if vlan.get('name') %}
name {{ vlan['name'] }}
{% endif %}
{% endfor %}
It is worth noting that the indicator must be on the first line and you have to add it to every file, which is may cause confusion if a file is referenced via the include
method.
-Ken
Share details about yourself & someone from our team will reach out to you ASAP!