Accessing nested YAML mappings with jinja2

Jeroen Valcke picture Jeroen Valcke · Dec 31, 2014 · Viewed 11k times · Source

I've recently started using YAML and jinja2. I'm having trouble understanding why I need to reference the entire structure of my YAML mapping in the jinja2 template.

I have the following YAML file

---
PROVIDERS:
    PROV1:
        int: ge-0/1/1
        ipv4: 10.0.1.1/30
    PROV2:
        int: ge-0/1/2
        ipv4: 10.0.1.2/30

and this is my jinja2 template

{%- for provider in PROVIDERS %}
    {{ provider }}
    {{ PROVIDERS[provider].int }}   <-- why not provider.int
    {{ PROVIDERS[provider].ipv4 }}  <-- why not provider.ipv4
{%- endfor %}

Parsing with pyyaml gives me the (expected) output

PROV2
ge-0/1/2
10.0.1.2/30
PROV1
ge-0/1/1
10.0.1.1/30

However why must I use PROVIDERS[provider].int? provider.int doesn't work.

Additionally, I was wondering if I could make this a list of mappings instead of a nested mapping:

---
PROVIDERS:
    - PROV1:
        int: ge-0/1/1
        ipv4: 10.0.1.1/30
    - PROV2:
        int: ge-0/1/2
        ipv4: 10.0.1.2/30

I've tried to do so, but the jinja2 template no longer produced the desired output.

Answer

Zero Piraeus picture Zero Piraeus · Jan 5, 2015

There are two things to consider here:

  1. What Python data structure is constructed from your YAML document?
  2. How can your template reference the elements of that data structure?

Answering point 1 is easy:

>>> import yaml
>>> from pprint import pprint
>>> p1 = yaml.load("""
... ---
... PROVIDERS:
...     PROV1:
...         int: ge-0/1/1
...         ipv4: 10.0.1.1/30
...     PROV2:
...         int: ge-0/1/2
...         ipv4: 10.0.1.2/30
... """)
>>> pprint(p1)
{'PROVIDERS': {'PROV1': {'int': 'ge-0/1/1', 'ipv4': '10.0.1.1/30'},
               'PROV2': {'int': 'ge-0/1/2', 'ipv4': '10.0.1.2/30'}}}

You have a dictionary with a single item whose key is 'PROVIDERS', and whose value is a dictionary with the keys 'PROV1' and 'PROV2', each of whose values is a further dictionary. That's a more deeply nested structure than you need (more on which later), but now that we can see your data structure, we can work out what's going on with your template.

This line:

{%- for provider in PROVIDERS %}

iterates over the keys of PROVIDERS (which, given your output, is obviously the second-level nested dictionary which is the value for the key 'PROVIDERS' in your top-level dictionary). Since what you're iterating over are the keys, you then need to use those keys to get at the associated values:

{{ PROVIDERS[provider].int }}
{{ PROVIDERS[provider].ipv4 }}

A more straightforward YAML document for your purposes would be this:

---
- id: PROV1
  int: ge-0/1/1
  ipv4: 10.0.1.1/30
- id: PROV2
  int: ge-0/1/2
  ipv4: 10.0.1.2/30

Note that we've ditched the redundant single-item mapping, and replaced the second-level mapping of mappings with a list of mappings. Again, we can check that:

>>> p2 = yaml.load("""
... ---
... - id: PROV1
...   int: ge-0/1/1
...   ipv4: 10.0.1.1/30
... - id: PROV2
...   int: ge-0/1/2
...   ipv4: 10.0.1.2/30
... """)
>>> pprint(p2)
[{'int': 'ge-0/1/1', 'ipv4': '10.0.1.1/30', 'id': 'PROV1'},
 {'int': 'ge-0/1/2', 'ipv4': '10.0.1.2/30', 'id': 'PROV2'}]

Here's how your template could use this data structure:

{%- for provider in PROVIDERS %}
    {{ provider.id }}
    {{ provider.int }}
    {{ provider.ipv4 }}
{%- endfor %}

Obviously you'll need to modify the code which supplies PROVIDERS to the template, since it's now the top-level list represented by the entire YAML document, rather than a dictionary nested inside it.