Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jinja2: render template inside template

Tags:

python

jinja2

Is it possible to render a Jinja2 template inside another template given by a string? For example, I want the string

{{ s1 }}

to be rendered to

Hello world

given the following dictionary as a param for Template.render:

{ 's1': 'Hello {{ s2 }}', 's2': 'world' }

I know the similar process can be done with include tag separating the content of s1 to the another file, but here I don't want to follow that way.

like image 326
akarin64 Avatar asked Oct 19 '16 13:10

akarin64


People also ask

How are templates reused in Jinja2?

To reuse a Jinja template you use the Jinja built-in {% extends %} tag. The {% extends %} tag uses the syntax {% extends <name> %} to reuse the layout of another template. This means that in order to reuse the layout in listing 4-5 defined in a file base.

What is the Jinja2 delimiters for expressions to print to the template output?

The default Jinja delimiters are configured as follows: {% ... %} for Statements. {{ ... }} for Expressions to print to the template output.

How do I load and render a Jinja template?

With Jinja imported, you can go on to load and render your first template: >>> import jinja2 >>> environment = jinja2.Environment() >>> template = environment.from_string("Hello, { { name }}!") >>> template.render(name="World") 'Hello, World!' The core component of Jinja is the Environment () class.

How to print a variable from a Jinja2 template?

We import the Template object from the jinja2 module. Template is the central template object. It represents a compiled template and is used to evaluate it. In our template, we have the { { }} syntax which is used to print the variable. The variable is passed in the render () method. With the render () method, we generate the final output.

What file types can I generate with Jinja?

Jinja can generate any text-based format (HTML, XML, CSV, LaTeX, etc.). A Jinja template doesn’t need to have a specific extension: .html, .xml, or any other extension is just fine. A template contains variables and/or expressions, which get replaced with values when a template is rendered; and tags, which control the logic of the template.

What is the difference between Jinja2 template and template engine?

The template engine is similar to the Python format () method; but template engines are more powerful and have many more features. We import the Template object from the jinja2 module. Template is the central template object. It represents a compiled template and is used to evaluate it.


Video Answer


1 Answers

I do not have an environment to easily test these ideas, but am exploring something similar within airflow's use of jinja templates.

From what I can find the best way to do this is do explicitly render the inner template string within the outer template. To do this you may need to pass or import the Template constructor in the param dictionary.

Here is some (untested) code:

from jinja2 import Template
template_string = '{{ Template(s1).render(s2=s2) }}'
outer_template = Template(template_string)
outer_template.render( 
    s1='Hello {{ s2 }}', 
    s2='world',
    Template=Template
)

This is not nearly as clean as you were hoping for, so we may be able to take things further by creating a custom filter so we can use it like this:

{{ s1|inner_render({"s2":s2}) }}

Here is a custom filter I think will do the job:

from jinja2 import Template
def inner_render(value, context):
    return Template(value).render(context)

Now let's assume we want the same context as the outer template, and - what the heck - lets render an arbitrary number of levels deep, N. Hopefully some example usages will look like:

{{ s1|recursive_render }}

{{ s3|recursive_render(2) }}

An easy way to get the context from our custom filter is to use the contextfilter decorator

from jinja2 import Template
from jinja2 import contextfilter
@contextfilter
def recursive_render(context, value, N=1):
    if N == 1:
        val_to_render = value
    else:
        val_to_render = recursive_render(context, value, N-1)
    return Template(value).render(context)

Now you can do something like s3 = '{{ s1 }}!!!' and {{ s3|recursive_render(2) }} should render to Hello world!!!. I suppose you could go even deeper and detect how many levels to render by counting brackets.


Having gone through all this I would like to explicitly point out that this is very confusing.

Although I do believe I have found a need for 2 levels of rendering within my very specific airflow usage, I cannot imagine a need for more levels than that.

If you are reading this thinking "this is just what I need": Whatever you are trying to do can probably be done more eloquently. Take a step back, consider that you may have an xy problem, and re-read jinja's docs to be sure there isn't a better way.

like image 67
7yl4r Avatar answered Sep 19 '22 20:09

7yl4r