Jinja: Support nested variables

Created on 7 Dec 2016  ·  7Comments  ·  Source: pallets/jinja

I have been searching through documentation and various online articles and could not seem to find any mention of having nested variables so I am opening up a feature request. This could help benefit many projects by allowing more complex and automatic variable calls.

A normal variable can be defined like this:

{{ variable }}

It would be useful to allow dynamic information from another variable to be used to populate another variables name.

{{ variable{{ generated_var }} }}

My exact use case is with Ansible. I am grabbing a list of network interface devices from one variable (ansible_interfaces). Then I can use this to reference the information from another variable (ansible_eth0, for example). Here is a basic framework of what is trying to accomplish.

{% for interface in ansible_interfaces %}
IPADDR{{ loop.index }}={{ ansible_{{ interface }}.ipv4.address }}
NETMASK{{ loop.index }}={{ ansible_{{ interface }}.ipv4.netmask }}
{% endfor %}

Jinja2 does not like that when it renders as it complains about the extra brackets existing. For options of how to implement this I was thinking along the lines of one of these two different ideas.

(1) Allow variable expansion. This is exactly what I was showing earlier; allowing variables to be parsed from the inside-out.

(2) Add a filter to convert a string into a variable name.

{% for interface in ansible_interfaces %}
{% set interface_string="ansible_%s.ipv4.address"|format(interface) %}
IPADDR{{ loop.index }}={{ interface_string|variable }}
NETMASK{{ loop.index }}={{ interface_string|variable }}
{% endfor %}

Here is some rough (non-working) code showing the second idea.

# vim jinja2/jinja2/filters.py
@environmentfilter
def do_variable(environment, s):
    string_to_variable = "{{ %s }}" % s
    return environment.from_string(string_to_variable).render()

Ideally option 1 would be less complex in terms of end-user use. Let me know your thoughts on the matter. This is my first time ever looking into the code for Jinja2 but I would love to contribute back if any help is needed with the code.

Most helpful comment

http://serverfault.com/questions/762079/how-to-loop-through-interface-facts

Sounds like you can do this:

{{ hostvars[inventory_hostname]['ansible_%s' | format(interface)].ipv4.address }}

I'm very strong :-1: on variable variable names. It's usually a sign of bad architecture if an application doesn't provide a proper dict/list of data if you need to access it by dynamic key or iterate over it. FWIW, I think this hostvars dict is somewhat ugly compared to a proper dict mapping interface names to interface data. I'd open an issue with Ansible, suggesting to change that list to a dict. Since iterating over a dict yields you its keys it might not even be backwards incompatible if they changed it to a dict...

All 7 comments

http://serverfault.com/questions/762079/how-to-loop-through-interface-facts

Sounds like you can do this:

{{ hostvars[inventory_hostname]['ansible_%s' | format(interface)].ipv4.address }}

I'm very strong :-1: on variable variable names. It's usually a sign of bad architecture if an application doesn't provide a proper dict/list of data if you need to access it by dynamic key or iterate over it. FWIW, I think this hostvars dict is somewhat ugly compared to a proper dict mapping interface names to interface data. I'd open an issue with Ansible, suggesting to change that list to a dict. Since iterating over a dict yields you its keys it might not even be backwards incompatible if they changed it to a dict...

I am 100% in agreement that inside-out rendering to get variable*N variable names is a BAD idea.

However, I got here looking for a solution for outside-in render nesting and ended up exploring the solution myself. Is there support for such a thing? Would addition of a nested_render filter be beneficial or harmful in that it is likely to confuse people?

Nested variables would solve a problem with WTForms, in which a construct like
{{ form.playername(value="{{ currentname }}") }}
would be a lot clearer than any other solution I've found.

{{ form.playername(value=currentname) }} does it. but fyi, you should pass the current data to the form constructor and not bother with it in the templates at all

Ah, it's that simple! I ended up using Javascript to set the value from the currentname variable. When I set the default value in the form object in the Python code, it doesn't update.

Pass by in #pocoo on IRC and ping me there (it's very much offtopic here) and I can tell you how to do it correctly.

Was this page helpful?
0 / 5 - 0 ratings