Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing a value in a yaml file using Python

I have a .yaml file I want to update with Python code. Let's say it looks something like that:

  state: 'present'

I'd like to have a code that changes the state and saves the file. I'm trying with something like this and fail:

def set_state(state):
    with open("file_to_edit.yaml", 'rw') as f:
        doc = yaml.load(f)
    doc['state'] = state
    yaml.dump(f)

I am using the 'yaml' package for Python.

like image 466
Pavel Zagalsky Avatar asked Nov 23 '16 10:11

Pavel Zagalsky


People also ask

What is PyYAML in Python?

PyYAML is a YAML parser and emitter for Python. PyYAML features a complete YAML 1.1 parser, Unicode support, pickle support, capable extension API, and sensible error messages. PyYAML supports standard YAML tags and provides Python-specific tags that allow to represent an arbitrary Python object.


2 Answers

The problem is that yaml.dump(doc) doesn't actually write to a file. Instead, it returns the modified YAML as a string unless you pass the file descriptor as argument as well, which allows you to write directly to the file.

The following should work:

def set_state(state):
    with open('file_to_edit.yaml') as f:
        doc = yaml.load(f)

    doc['state'] = state

    with open('file_to_edit.yaml', 'w') as f:
        yaml.dump(doc, f)
like image 155
Matheus Portela Avatar answered Oct 20 '22 06:10

Matheus Portela


Before anything else: never use yaml.load() if you don't have to, as it is in principle unsafe to do so. For this kind of simple structure (without tags) you should use yaml.safe_load() (and the corresponding safe_dump() that will complain if your data cannot be safe_loaded after dumping).

yaml.dump() has the following signature:

def dump(documents, stream=None, Dumper=Dumper,
         default_style=None, default_flow_style=None,
         canonical=None, indent=None, width=None,
         allow_unicode=None, line_break=None,
         encoding='utf-8', explicit_start=None, explicit_end=None,
         version=None, tags=None)

Of this only the first one needs to be given, that should be your doc variable. If you don't specify a stream, then dump() writes data structure to an in memory file-object (like StringIO) and after writing returns the value as string.

So although you could do:

with open("file_to_edit.yaml", 'w') as f:
    f.write(yaml.safe_dump(doc))

this is inefficient and shows little understanding of how yaml.safe_dump() works.

If you want to open the file for reading and writing you have to make sure you both reset the index in the file and truncate its content. This is usually not worth the effort so it is safer to re-open the file for writing:

def set_state(state):
    file_name = "file_to_edit.yaml"
    with open(file_name) as f:
        doc = yaml.safe_load(f)
    doc['state'] = state
    with open(file_name, 'w') as f:
        yaml.safe_dump(doc, f, default_flow_style=False)

(of course you make the filename a variable when you want to make sure you overwrite the original, so you cannot mistype it).

If you don't specify default_flow_style=False, your output will look like:

{state: deleted}

The output will not include the superfluous quotes around present in your input. You can specify default_style="'" as well, but this will also put quotes around state.
If losing the quotes is a problem and you really want the output to look like the input, you should be using ruamel.yaml (disclaimer I am the author of that package), which can preserve the quotes on individual strings, handles YAML 1.2 (instead of YAML 1.1) and also preserves comments in your file.

like image 34
Anthon Avatar answered Oct 20 '22 07:10

Anthon