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.