Using ruamel, I need to add new values to a yaml file where the index (and depth) is not known until runtime. Is there a way to insert a value if I'm given the entire path for the index? (I can decide the format for the index)
i.e. Given the following yaml:
app:
datasource:
url:example.org
username:myuser
password:mypw
I need to add a new value to this yaml file. If I'm given the index app:datasource:port
and value myport
, then I want the output to be:
app:
datasource:
url:example.org
username:myuser
password:mypw
port:myport
What I tried
I tried just putting the entire index inside square brackets []
or use .insert()
, but neither of these work:
import sys
from ruamel.yaml import YAML
yaml = YAML()
with open('myfile.yaml', 'r') as stream:
code = yaml.load(stream)
# assume this index isn't known until runtime
index = 'app:datasource:port'
other_index = 'app:datasource:other_port'
code[index] = 'myport'
code.insert(1, other_index, 'otherport')
yaml.dump(code, sys.stdout)
it produces the following output which isn't what I want. It just treats them as a top level index rather than a nested one:
app:
datasource:
url:example.org
username:myuser
password:mypw
app:datasource:port: myport
app:datasource:other_port: otherport
What can I do to insert a value at a dynamically provided index?
I think your input file doesn't load to what you expect it loads to. The value for datasource
is not
a mapping, but unquoted multi-line string. That is because there has to be
a space after the value indicator (:
), unless both the key and value are quoted.
Since your index
and other_index
are just strings (that happen to have a colon in them),
these are used as keys for assigning to the root level mapping (your code
).
When you load a YAML document, such as yours, that has a mapping at the root, you'll get
an instance of ruamel.yaml.comments.CommentedMap
as a result. That instance has a method, mlget
,
that takes a list of "index-segments", that are used to recursively traverse the
data structure. You can use that to easily find the parent of your "index" and set
the value.
Assuming you put a space after the colon of the last three lines of your input:
import sys
from pathlib import Path
import ruamel.yaml
def _mlput(self, path, value, index=None, sep=':'):
spath = path.split(sep)
parent = self.mlget(spath[:-1])
if index is None:
parent[spath[-1]] = value
else:
parent.insert(index, spath[-1], value)
ruamel.yaml.comments.CommentedMap.mlput = _mlput
path = Path('myfile.yaml')
yaml = ruamel.yaml.YAML()
data = yaml.load(path)
data.mlput('app:datasource:port', 'myport')
data.mlput('app:datasource:other_port', 'otherport', 1)
yaml.dump(data, sys.stdout)
which gives:
app:
datasource:
url: example.org
other_port: otherport
username: myuser
password: mypw
port: myport