I'm trying to change a value nested deeply in a YAML file using Python and pyyaml. I need to preserve the formatting, which is a combination of block and flow.
I want to change a single value nested in a dictionary several layers deep in the structure. For example, I might change the x value to 2:
a:
b:
c: {x:1, y:1}
d: {r1: 2, r2: 4}
e:
f: 1
g: 1
I've been able to import the data, change the value, and dump the file with block formatting or with default formatting, but neither exactly matches the format that I need. Is there a way to either mix the formatting or to only change the target value without re-writing the entire file?
You should probably look beyond PyYAML, one of the problems is that it constructs your {x:1, y:1}
to the Python dict {"x": 1, "y": 1}
where it should be construct to {"x:1": None, "y:1": None}
, as there is no space after the colon and the scalars are not double quoted.
Assuming you want the first Python representation, even if you rather not change your YAML to be correct and rely on the faulty interpretation thereof by PyYAML, PyYAML is not going to dump that without the extra space after the colon, thus changing your files.
I suggest you look at ruamel.yaml (disclaimer: I am the author of that package) where you only extra thing apart from correcting your input YAML, is to to set the indent for mappings to 4 (as the default is 2).
ruamel.yaml also supports having nested flow style nodes under block style where PyYAML only supports all-flow (default-flow-style=True
), all-block (default-flow-style=False
)or all-block-with-collection-leaf-nodes-flow (the default) out-of-the-box
import sys
import ruamel.yaml
yaml_str = """\
a:
b:
c: {x: 1, y: 1} # these need spaces after the colon
d: {r1: 2, r2: {r3: 3, r4: 4}}
e:
f: 1
g: 1
"""
yaml = ruamel.yaml.YAML()
yaml.indent(mapping=4)
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)
which gives:
a:
b:
c: {x: 1, y: 1} # these need spaces after the colon
d: {r1: 2, r2: {r3: 3, r4: 4}}
e:
f: 1
g: 1
And yes, the comment is preserved as well.