7

Here is a config file, I use PyYAML to change some value from it and then I write some config, but it will change my format, it confuses me.

 $ results.yaml 
 nas:
     mount_dir: '/nvr'
     mount_dirs: ['/mount/data0', '/mount/data1', '/mount/data2']

# yaml.py

import yaml.py

conf = open("results.conf", "r")
results = yaml.load(conf)
conf.close()

result['nas']['mount_dirs'][0]= "haha"

with open('/home/zonion/speedio/speedio.conf', 'w') as conf:
    yaml.dump(speedio, conf, default_flow_style=False)

conf.close()

but it change my format,what should I do?

# cat results.conf
nas:
  mount_dir: /nvr
  mount_dirs:
  - haha
  - /mount/data1
  - /mount/data2
Anthon
  • 69,918
  • 32
  • 186
  • 246
haroldT
  • 153
  • 2
  • 11
  • Can we take it your extra space before `nas` (and the following lines) is because of inappropriate formatting here on [so], and that your actual indent is four spaces? BTW, if you `import yaml.py` you will get an `ImportError`. – Anthon Oct 27 '16 at 06:25

4 Answers4

6

If you use ruamel.yaml ¹, you can relatively easily achieve this, by combining this and this answer here on StackOverlow.

By default ruamel.yaml normalizes to an indent of 2, and drops superfluous quotes. As you don't seem to want that, you have to either explicitly set the indent, or have ruamel.yaml analyse the input, and tell it to preserve quotes:

import sys
import ruamel.yaml
import ruamel.yaml.util

yaml_str = """\
nas:
    mount_dir: '/nvr'
    mount_dirs: ['/mount/data0', '/mount/data1', '/mount/data2']
"""

result, indent, block_seq_indent = ruamel.yaml.util.load_yaml_guess_indent(
    yaml_str, preserve_quotes=True)
result['nas']['mount_dirs'][0] = "haha"
ruamel.yaml.round_trip_dump(result, sys.stdout, indent=indent,
                            block_seq_indent=block_seq_indent)

instead of the load_yaml_guess_indent() invocation you can do:

result = ruamel.yaml.round_trip_load(yaml_str, preserve_quotes=True)
indent = 4
block_sequence_indent = None 

If you want haha to be (single) quoted in the output make it a SingleQuotedScalarString:

result['nas']['mount_dirs'][0] = \
       ruamel.yaml.scalarstring.SingleQuotedScalarString("haha")

with that the output will be:

nas:
    mount_dir: '/nvr'
    mount_dirs: ['haha', '/mount/data1', '/mount/data2']

(given that your short example input has no block style sequences, the block_sequence_indent cannot be determined and will be None)


When using the newer API you have control over the indent of the mapping and sequences seperately:

yaml = ruamel.yaml.YAML()
yaml.indent(mapping=4, sequence=6, offset=3)  # not that that looks nice
data = yaml.load(some_stream)
yaml.dump(data, some_stream)

This will make your YAML formatted consistently if it wasn't so to begin with, and make no further changes after the first round-trip.


¹ Disclaimer: I am the author of that package.

Anthon
  • 69,918
  • 32
  • 186
  • 246
  • what version of ruamel is this ? becuase its not working on my machine in office which is `0.10.12` – Chang Zhao Mar 11 '19 at 14:08
  • @ChangZhao "is this" is a bit unspecific, but if you mean the new API, that was introduced with 0.15.1 in June 2017. I am not sure why you are using 0.10.12, that was from 2015 IIRC and there have been way over 100 releases of `ruamel.yaml` since then. – Anthon Mar 11 '19 at 14:32
  • well after importing ruamel when I type ruamel in python shell, I get `` so ruamel comes as a builtin ? and it seems to be ruamel never got installed or updated since then ? – Chang Zhao Mar 11 '19 at 15:33
  • You should just do a `pip install -U ruamel.yaml` – Anthon Mar 11 '19 at 20:04
6

ruamel.yaml unfortunately does not completely preserve original format, quoting its docs:

Although individual indentation of lines is not preserved, you can specify separate indentation levels for mappings and sequences (counting for sequences does not include the dash for a sequence element) and specific offset of block sequence dashes within that indentation.

I do not know any Python library that does that.

When I need to change a YAML file without touching its format I reluctantly use regexp (reluctantly as it's almost as bad as parsing XHTML with it).

Please feel free to suggest a better solution if you know any, I would gladly learn about it!

Greg Dubicki
  • 5,983
  • 3
  • 55
  • 68
1

ruamel implements a round-trip loader and dumper, try:

import ruamel.yaml
conf = open("results.conf", "r")
results = ruamel.yaml.load(conf, ruamel.yaml.RoundTripLoader)
conf.close()
results['nas']['mount_dirs'][0] = "haha"
with open('/home/zonion/speedio/speedio.conf', 'w') as conf:
  ruamel.yaml.dump(results, conf, ruamel.yaml.RoundTripDumper)
flyx
  • 35,506
  • 7
  • 89
  • 126
  • thank you for you help, but it make my single quotes disappear!!! Like : [haha, /mount/data1, /mount/data2], By the way, the Indentation is not right too. – haroldT Oct 26 '16 at 03:21
  • 2
    Well then ruamel is not as round-tripping as it advertizes to be. You can customize its output by adding parameters to dump; in your case, `indent=4, default_style="'"` might help (but might also result in *all* scalars to be quoted). Note that round-tripping is nothing YAML is designed to do, and trying to do it violates the YAML spec. The real answer is probably „if you want round-tripping, YAML is not the right tool to use“. – flyx Oct 26 '16 at 07:33
0

Try to load it first and the dump like that:

import ruamel.yaml
yaml_str = f"""\
    nas:
        mount_dir: '/nvr'
        mount_dirs: ['/mount/data0', '/mount/data1', '/mount/data2']"""
yaml = ruamel.yaml.YAML()
data = yaml.load(yaml_str)

with open("test.yaml", 'w') as outfile:
    yaml.dump(data, outfile)
outfile.close()