5

If I have a yaml file containing a string with the bracket notation {} used in partnership with python f-strings, how might a leverage the f-string interpolation here? Take for example this simple yaml file:

# tmp.yaml
k1: val1
k2: val2 as well as {x}

If x = 'val3', I would like the value of the k2 to reflect val2 as well as val3

# app.py
x = 'val3'
with open('tmp.yaml', 'rt') as f:
    conf = yaml.safe_load(f)

print(conf)
{'k1': 'val1', 'k2': 'val2 as well as {x}'}

This could be accomplished pretty easily with format strings...

print(conf['k2'].format(x=x))
val2 as well as val3

But how to do the same with f-strings?

dreftymac
  • 31,404
  • 26
  • 119
  • 182
user9074332
  • 2,336
  • 2
  • 23
  • 39

2 Answers2

3

You can define a custom constructor:

import yaml

values = { 'x': 'val3' }

def format_constructor(loader, node):
  return loader.construct_scalar(node).format(**values)

yaml.SafeLoader.add_constructor(u'!format', format_constructor)

conf = yaml.safe_load("""
k1: val1
k2: !format val2 as well as {x}
""")

print(conf)

If you don't want to use the tag !format, you can also use add_constructor with u'tag:yaml.org,2002:str' as tag. This will override the default string constructor with yours.

flyx
  • 35,506
  • 7
  • 89
  • 126
  • Could you provide some background on this please? – user9074332 Nov 01 '18 at 01:51
  • 1
    @user9074332 What happens is that you put the local tag `!format` on the YAML scalar `val2 as well as {x}` and then tell PyYAML *„when you encounter a node tagged with `!format`, use this function to construct the target value from it“*. In the function, you first construct a string using PyYAML's usual constructor, and then call `format` on it, giving the `value` dict that contains the replacements you want to do. You could also just `...format(x='val3')` but the method I show can be generalized to larger YAML files with multiple replacement templates. – flyx Nov 02 '18 at 10:11
3

I found that jinja2 provides the easiest solution to this problem.

Source yaml file:

# tmp.yaml
k1: val1
k2: val2 as well as {{ x }}

Read the file and render using jinja templating:

with open('tmp.yaml', 'rt') as f:
    conf = f.read().rstrip()

print(conf)
# 'k1: val1\nk2: val2 as well as {{ x }}'

import jinja2
template = Template(conf)
conf = template.render(x='val3')
config = yaml.safe_load(conf)

print(config)
# {'k1': 'val1', 'k2': 'val2 as well as val3'}
user9074332
  • 2,336
  • 2
  • 23
  • 39