8

I have a YAML file (all.yaml) that looks like:

...
var1: val1
var2: val2
var3: {{var1}}-{{var2}}.txt
...

If I load it in Python like this:

import yaml

f = open('all.yaml')
dataMap = yaml.safe_load(f)
f.close()
print(dataMap["var3"])

the output is {{var1}}-{{var2}}.txt and not val1-val2.txt.

Is it possible to replace the nested vars with the value?

I tried to load it with:

import jinja2
templateLoader = jinja2.FileSystemLoader( searchpath="/path/to/dir" )
templateEnv = jinja2.Environment( loader=templateLoader )
TEMPLATE_FILE = "all.yaml"
template = templateEnv.get_template( TEMPLATE_FILE )

The exception is no longer thrown, now I am stuck and have to research how to proceed.

dreftymac
  • 31,404
  • 26
  • 119
  • 182
Sandro Koch
  • 303
  • 1
  • 4
  • 11
  • You would obviously need to use Jinja2 at some point. What have you tried so far? – larsks Aug 12 '15 at 14:04
  • I tried to load it with import jinja2 templateLoader = jinja2.FileSystemLoader( searchpath="/" ) templateEnv = jinja2.Environment( loader=templateLoader ) TEMPLATE_FILE = "all.yaml" template = templateEnv.get_template( TEMPLATE_FILE ) but this will throw a TemplateNotFound-Exception – Sandro Koch Aug 12 '15 at 14:22
  • It's better to update your answer rather than trying to leave code samples in the comments. A `TemplateNotFound` exception should be relatively easy to solve; it's unlikely that your files `all.yml` is located in `/`, but that's where you told Jinja2 to look. – larsks Aug 12 '15 at 14:33
  • yes, that was the problem. i updated the question. thanks – Sandro Koch Aug 12 '15 at 14:43
  • Why did this question get downvoted? – ostrokach May 25 '16 at 21:59
  • @ostrokach The YAML input and program the OP specified cannot give the `{{var1}}-{{var2}}.txt` that the OP claims that it gives (see my answer as to why). – Anthon Apr 27 '17 at 14:42
  • @SandroKoch If you are willing to do the jinja2 rendering yourself (as I deduct from your updated answer), you should have a look at my answer which gets exactly what you want. – Anthon Apr 27 '17 at 14:44
  • **See also:** https://stackoverflow.com/questions/41620674/use-placeholders-in-yaml – dreftymac Sep 30 '19 at 04:39
  • Lack of a minimum effort to use jinja2 I'd say. – Leonardo Dec 14 '22 at 17:45

4 Answers4

6

First define an Undefined class and load yaml to get known values. Then load it again and render with known values.

#!/usr/bin/env python

import yaml
from jinja2 import Template, Undefined

str1 = '''var1: val1
var2: val2
var3: {{var1}}-{{var2}}.txt
'''

class NullUndefined(Undefined):
  def __getattr__(self, key):
    return ''

t = Template(str1, undefined=NullUndefined)
c = yaml.safe_load(t.render())

print t.render(c)

Run it:

$ ./test.py
var1: val1
var2: val2
var3: val1-val2.txt
Lei Feng
  • 71
  • 1
  • 2
1

Here is one possible solution:

  1. Parse your YAML document with the yaml module
  2. Iterate over the keys in your YAML document, treating each value as a Jinja2 template to which you pass in the keys of the YAML document as parameters.

For example:

import yaml
from jinja2 import Template

with open('sample.yml') as fd:
    data = yaml.load(fd)

for k, v in data.items():
    t = Template(v)
    data[k] = t.render(**data)

print yaml.safe_dump(data, default_flow_style=False)

This will work fine with your particular example, but wouldn't do anything useful for, say, nested data structures (in fact, it would probably just blow up).

larsks
  • 277,717
  • 41
  • 399
  • 399
  • When the YAML file contains something like *val: 1.5* it will throw a AttributeError: 'float' object has no attribute 'iter_fields' – Sandro Koch Aug 12 '15 at 14:59
  • Oh, absolutely. This is an example, not a robust solution! :) – larsks Aug 12 '15 at 15:05
  • Hey, -1 person, any comments on how this could be improved? I know it's an older one but I'm happy to fix it up. Cheers! – larsks Oct 12 '18 at 18:17
0

There is no replacement/substitution of scalar parts within the YAML specification.

Anything you want to do on that level has to be done in your application. For me, and for YAML, {{var1}} is just a nested mapping. {{var1}} is short for {{var1: null}: null}. After that the - is not allowed.

There are however multiple problems with your post:

  1. You are using PyYAML which only supports the old (2005) YAML 1.1. Therefore you cannot you cannot have multiple documents (i.e. ended with ...) without using an explicit document start (---) like you can in YAML 1.2

  2. Even if you correct the first line to read --- instead of ... your file will not load as a dict {{var1}} cannot be followed by a scalar - (from -{{var2}}.txt)

  3. And if you would just use {{var1}} in your file, PyYAML cannot load this as it loads YAML mappings as Python dict and Python doesn't allow mutable keys for a dict. Just like you get an TypeError in Python when you try to do: {dict(var1=None): None}

So you should at least change your input file all.yaml to:

---
var1: val1
var2: val2
var3: '{{var1}}-{{var2}}.txt'
...

to get this to load in YAML.

You'll have to load this file two times:

  • once by PyYAML to get the values that you can use to render template
  • once as template by jinja2

After you render the template you load that (string) once more in PyYAML and you have the value that you want.

Given the corrected all.yaml as specified above in the current directory and this program:

import yaml
import jinja2

YAML_FILE = 'all.yaml'
with open(YAML_FILE) as fp:
    dataMap = yaml.safe_load(fp)

env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath='.'))
template = env.get_template(YAML_FILE)

data = yaml.safe_load(template.render(**dataMap))
print(data["var3"])

will print what you wanted:

val1-val2.txt
Anthon
  • 69,918
  • 32
  • 186
  • 246
  • 1
    You can absolutely combine the YAML module and the Jinja2 module to do something like what the OP is asking. [Ansible](http://ansible.com) is one example of a tool that performs Jinja2 template processing on YAML values. So sure, you can't do this with the `yaml` module by itself, but I don't think that's what the OP is asking. – larsks Aug 12 '15 at 14:34
  • @larsks You are correct. Another example tool is [cookiecutter](https://cookiecutter.readthedocs.io/en/latest/advanced/templates_in_context.html) – dreftymac Sep 30 '19 at 04:34
  • 1
    Of course you can always replace something that is not valid YAML with something that **is** valid YAML and then load it. However the OP states that his first example (`all.yaml`) is YAML, and it is not. You first have to expand that jinja2 template and then parse the expanded result hoping it by then is replaced by something the YAML parser understands. – Anthon Sep 30 '19 at 05:15
  • That is an entirely accurate statement. An unprocessed Jinja template that just happens to look similar to YAML syntax is not the same thing as well-formed YAML. A processed Jinja template is by no means guaranteed to produce well-formed YAML. One challenge is communicating this circumstance to potential YAML users who have yet to *use* YAML (let alone develop modules for it), while minimizing the chance they will be confused or intimidated away. – dreftymac Sep 30 '19 at 05:39
0

I do not believe you can use:

yaml.load 

or

yaml.safe_load 

on a file containing jinja2 variables as values. The {{variable}} will attempt to be interpreted as a dict by yaml.

hcoat
  • 2,633
  • 1
  • 22
  • 29
wko
  • 1
  • 1
  • **Note:** This can be avoided by quoting the placeholder so that YAML interprets it as a plain scalar value (string). Change `{{variable}}` to `"{{variable}}"`. – dreftymac Sep 30 '19 at 17:14