Jinja2
External
- https://jinja.palletsprojects.com/en/latest/
- https://medium.com/knoldus/jinja2-template-the-modern-design-friendly-templating-engine-a9218fec96e5
- https://pypi.org/project/Jinja2/
Internal
Overview
Jinja2 is a templating engine. Placeholders in the template allow writing code that has a syntax similar to Python. The engine ingests the template and data and renders the final document. Jinja2 provides template inheritance and inclusion. Jinja2 allows definition and import of macros within the template. Templated are compiled to Python code and cached, or they can be compiled ahead of time. Jinja2 provides extensible filters, tests, functions and syntax.
Jinja’s philosophy is that while application logic belongs in Python if possible, it shouldn’t make the template designer’s job difficult by restricting functionality too much.
Programming Model
Find out what the latest version is from https://pypi.org/project/Jinja2/
Then add this to your requirements.txt
jinja2 == 3.1.2
Read the Template from File
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
tdir = Path('/Users/ovidiu/tmp/jinja')
template = Environment(loader=FileSystemLoader(tdir)).get_template('test-template.yaml.j2')
text = template.render(
variable_1='blue',
variable_2=5
)
For a template similar to:
color: {{ variable_1 }}
size: {{ variable_2 }}
the rendering looks similar to:
color: blue
size: 5
Template as String in Memory
template_as_text = """
color: {{ variable_1 }}
size: {{ variable_2 }}
"""
template = Environment().from_string(template_as_text)
text = template.render(
variable_1='blue',
variable_2=5
)
Variables
Variables can be provided individually as arguments of the render()
method, as shown above.
Alternatively, a recursive data structure can be provided as argument:
data = {
'variable_1': 'blue',
'variable_2': 5,
}
text = template.render(data)
Template Variable
raw content followed by {{ color }}
If the color
variable in the example above is not defined when the template is rendered, it will be rendered as an empty string.
Also see Variables above.
Conditionals
The expressions described below can be used in conditionals:
{% if <expression> %}
something will be rendered here
{% elif <expression> %}
something else will be rendered here
{% else %}
and something else will be rendered here
{% endif %}
Example:
{%- if color == 'blue' -%}
BLUE
{%- elif color == 'red' -%}
RED
{%- else -%}
UNDEFINED
{%- endif -%}
Loops
my_list:
{%- for i in seq %}
- '{{ i }}'
{%- endfor %}
where seq
is a Python list:
text = Environment(...).get_template(...).render(seq=['a', 'b'])
Macros
Macros are equivalent with functions in a regular programming language:
{%- macro some_macro(color, size) -%}
color: {{ color }}, size: {{ size }}
{%- endmacro -%}
{{- some_macro('blue', 1) }}
Rendering Booleans
If a boolean value from Python will be rendered as having its first character capitalized. This is a problem when rendering YAML or JSON. This is something that works but I am not sure is optimal (need to adjust whitespace):
{% if value is boolean %}{{ value | lower }}
{% else %}{{ value }}
{% endif %}
Rendering Dictionaries
something:
{%- for key, value in a_dict.items() %}
{{key}}: {{value-}}
{% endfor %}
somethingelse
text = Environment().from_string(...).render(a_dict={'a': 'A', 'b': 'B', 'c': 'C'})
This will render:
something:
a: A
b: B
c: C
somethingelse
Rendering a List of Dictionaries
{% if a_list_of_maps %}
some_tag_that_introduces_the_list_of_maps:
{%- for m in a_list_of_maps %}
- {%- for k, v in m.items() %}
{{ k }}: {{ v }}
{%- endfor %}
{%- endfor %}
{%- endif %}
Recursive Rendering of a Deep Data Structure to YAML
Expressions
Jinja allows expressions with a syntax similar to Python's. The expressions can be used in conditionals.
Literals
Jinja allows for the the following literals:
- Double quoted strings:
"Blue is a color"
. - Integers:
34
. - Floating points:
34.1
,43.1e3
. - Lists:
['this', 'is', 'a', 'list']
. Lists are useful for storing sequential data to be iterated over. - Tuples:
('this', 'is', 'a', 'tuple')
. - Dictionaries:
{'color': 'blue, 'size': 1}
- Booleans:
true
,false
. They are lowercase, butTrue
andFalse
is also supported, but the documentation recommends using lowercase. - None:
none
. It is lowercase.None
is also supported, but the documentation recommends using lowercase.
Math
Supported operators:
+
: adds objects together. If the objets are numbers, performs mathematical addition. If the objects are strings and list, the operator concatenates the operands.-
/
//
: divide numbers and returns the truncated result.%
*
: multiplies numbers, but can also be used to repeat a string a number of times:
{{ '-' * 80 }}
Comparisons
==
Use ==
compare two objects and assess equality.
{%- if color == 'blue' -%}
Painted blue
{%- endif -%}
==
can be used to check for a specific boolean value:
{%if color_enabled == false -%}
the color is enabled
{%- endif %}
!=
Use !=
compare two objects and assess inequality.
{%- if color != 'blue' -%}
not painted blue
{%- endif -%}
Use !=
with caution when attempting to detect empty strings. The following example:
{% if color != "" -%}
painted
{%- endif -%}
will render "painted" if color
has some value, or if color
is explicitly assigned the empty string. If the color
variable is not defined, or it is None
, the template will render "painted", which is probably not intended. For a comprehensive approach on handling undefined, None
and empty variables, see Handling Undefined, None and Empty Variables below.
Other Comparisons
>
>=
<
<=
is
is
is defined Conditional
The is defined
expression evaluates to true
or false
whether the variable it was applied to was defined (exists) or not. For more details on usage see Handling Undefined, None and Empty Variables below.
Type Conditional
A built-in test that checks the type may follow is
:
To check whether a value is a string:
{%if some_var is string -%}
...
To check whether a value is a dictionary:
{%if some_var is mapping -%}
...
To check whether a value is a iterable:
{%if some_var is iterable -%}
...
in
is
and, or, not
and
returntrue
if the left hand and the right hand operands aretrue
.or
returntrue
if the left hand or the right hand operands aretrue
.not
{%if some_var == "a" and some_other_var == "b" -%}
my vars are {{ some_var }}, {{ some_other_var }}
{%- endif %}
(expr)
(expr)
Groups an expression.
|
|
apply a filter.
~
~
convert all operands into strings and concatenate them.
()
()
call a callable. Inside of the parentheses you can use positional arguments and keyword arguments Python-style.
., []
.
get an attribute of an object.[]
index or key operator.
For a complex recursive data structure, such as recursively embedded maps, passed to render()
, an arbitrary level key can be accessed with the dot notation and also by key. The template:
color1: {{ m.m1.color }}
color2: {{ m['m2']['color'] }}
rendered with:
m = {
'm1': {
'color': 'red'
},
'm2': {
'color': 'blue'
}
}
will print:
color1: red
color2: blue
Python Methods
Built-in Tests
none()
See Handling Undefined, None and Empty Variables below.
string()
number()
boolean()
{% if some_var is boolean %} ...
mapping()
iterable()
Rendering an Object's __str__() Representation
The __str__()
is honored, but if it is a multi-line string, only the first line is correctly indented.
Whitespace Management
A leading dash removes all whitespace (including new lines) between the last non space template character and it. A trailing dash removes all whitespace (including new lines) between it and the next non-whitespace character from the template.
{{-
-}}
{%-
-%}
Indentation
some_tag:
{{ rendered_multiline_configuration | indent(4) }}
The first line must be explicitly indented with the same number of space. The spaces are added after each new line.
Filters
upper
"{{ ... | upper }}"
lower
"{{ ... | lower }}"
indent
some_tag:
{{ rendered_multiline_configuration | indent(4) }}
The first line must be explicitly indented with the same number of space. The spaces are added after each new line.
Code Examples
Handling Undefined, None and Empty Variables
The expression to check whether a variable has been defined or not is is defined
. If we want to strictly test whether the variable is defined, even if its value is None
or non-truthy, use is defined
:
{%if color is defined -%}
color: {{ color }} (may display None or a non-truthy value)
{%- else -%}
color variable is not defined
{%- endif %}
Simply using the variable as test expression, Python-style, will evaluate to false
for non-defined variables, but also for variables that have been defined and have a None
or a non-truthy value.
{%if color -%}
color: {{ color }}
{%- else -%}
color variable is not defined, or it is None, or it is non-truthy
{%- endif %}
It is a good idea to check whether a variable exists with if <var-name>
or if <var-name> is defined
because an attempt to use an undefined variable in an expression will raise an exception. It is fine to use undefined variable in a rendering expression, the variable will render as the empty string.
The documentation says that none(var)
expression identifies None
values, but the execution of the following code with Jinja2 3.1.2 raised TypeError: 'NoneType' object is not callable
regardless whether I defined or not color
variable when rendering:
{%if none(color) -%}
NONE
{%- endif %}