Home > Software engineering >  How to replace string with a dictionary in YAML
How to replace string with a dictionary in YAML

Time:06-04

I have a YAML which looks like this:

key_001:
 __TARGETS__

and I have a dictionary which looks like:

{a:[12,23,34]}

I want to replace __TARGETS__ in YAML with the dictionary above so the resultant YAML should look like:

key_001:
 - a:
   - 12
   - 23
   - 34

My Code:

with fileinput.FileInput('./lib/manual-project-prom-template.yaml', inplace=True, backup=".bak") as file:
    try:
        for line in file:
            line = line.replace('__TARGETS__',my_dict['a'])
            print (line, end='')
    except Exception as e:
        print (str(e))

I get following error:

replace() argument 2 must be str, not dict

CodePudding user response:

I would try with an approach like this:

import yaml

yaml_str = r"""
key_001:
 __TARGETS__
"""

d = {'a': [12, 23, 34]}
replacements = {'__TARGETS__': d}

yaml_dict = yaml.safe_load(yaml_str)
for k, v in yaml_dict.items():
    if v in replacements:
        yaml_dict[k] = replacements[v]

dumped = yaml.safe_dump(yaml_dict)
print(dumped)

Out:

key_001:
  a:
  - 12
  - 23
  - 34

Note: this requires pyyaml

pip install PyYAML

CodePudding user response:

You are trying to do string replacement on a YAML file and that is almost never a good idea. Even if you would simply replace __TARGETS__ with a string value things could go wrong, e.g. when that string consists of numbers only as the resulting scalar in the YAML document would need to be quoted to be loaded again as a string. And you want a dictionary as replacement, so you should load the YAML document, find the node that is the string __TARGET__ in the loaded data, replace that and dump the YAML.

Your {a:[12,23,34]} is neither a valid Python dictionary (a would have to be quoted, nor YAML mapping (which requires a space after the value separator (:) if that separator is not followed by a quote. I assumed YAML and corrected the missing space.

In the result you want the value for key key_001 is actually a list, therefor you have to insert the mapping / dictionary in a list in Python to get the result:

import sys
import ruamel.yaml
from pathlib import Path

yaml_str = """
{a: [12,23,34]}
"""

file_in = Path('input.yaml')  # file containing the YAML with __TARGET__


yaml = ruamel.yaml.YAML(typ='safe')  # this doesn't preserve flow-style
dictionary = yaml.load(yaml_str)  # load from string
# the above is equivalent to doing:
# dictionary = dict(a=[12,23,34])

yaml = ruamel.yaml.YAML()
yaml.indent(mapping=2, sequence=3, offset=1)
data = yaml.load(file_in) # load from file

def replace(d, original, replacement):
    if isinstance(d, dict):
        for k, v in d.items():
            # if the original could be a key, replacement with a dict or list
            # can be done but is relatively complicated
            if v == original:
                d[k] = replacement
                return
            replace(v, original, replacement)
    elif isinstance(d, list):
        for idx, elem in enumerate(d):
            if elem == original:
                d[idx] = replacement
                return
            replace(elem, original, replacement)

replace(data, '__TARGETS__', [dictionary]) # here we add the extra sequence

yaml.dump(data, sys.stdout)

which gives:

key_001:
 - a:
    - 12
    - 23
    - 34

Your YAML output is inconsistently indented (three positions of the outer sequence, two for the inner sequence). ruamel.yaml normalises all of these to the same indent (but you can set one or the other globally, by commenting out the yaml.indent() line)

If there are comments in the file containing __TARGETS__ these will be preserved.

  • Related