I have a .yaml file I want to update with a 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.
The problem is that yaml.dump
doesn't actually write to a file. Instead, it returns the modified YAML as a string unless you write it 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)
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.
You can try the following (it creates a new file to store the result):
with open("output_file", 'a') as out:
with open("input_file", 'r') as f:
for line in f:
line = re.sub(r"state: present", "state: installed", line)
out.write(line)
My guess is that you didn't indent properly, since your function in its current state seems not to do anything. Python does care about indentation.
Try this:
def set_state(state):
with open("file_to_edit.yaml", 'rw') as f:
doc = yaml.load(f)
doc['state'] = state
yaml.dump(f)