Search code examples
pythonyamlhieraruamel.yaml

How to insert a key value pair in an empty yaml file in ruamel.yaml in Python?


My objective is to insert a key value pair in a YAML file which might be empty.

For example, my hiera.yaml (used in puppet) file contains only three hyphens.

Here is my code:

#!/usr/bin/python
import ruamel.yaml
import sys

def read_file(f):
  with open(f, 'r') as yaml:
    return ruamel.yaml.round_trip_load(yaml)

dict = {}

dict['first_name'] = sys.argv[1]
dict['last_name'] = sys.argv[2]
dict['role'] = sys.argv[3]

data = read_file('hiera.yaml')

pos = len(data)
data.insert(pos, sys.argv[1], dict, None)


ruamel.yaml.round_trip_dump(data, open('hiera.yaml', 'w'), block_seq_indent=1)

I am running it like:

./alice.py Alice Doe Developer

I get an output like:

Traceback (most recent call last):
  File "./alice.py", line 16, in <module>
    pos = len(data)
TypeError: object of type 'NoneType' has no len()

But when my hiera.yaml file is not empty, for example:

$ cat hiera.yaml
john:
$./alice.py Alice Doe Developer
$ cat hiera.yaml
john:
alice:
  first_name: Alice
  last_name: Doe
  role: Developer

Then it works properly.

Please tell me how to insert a key value pair(in my case a dict) to an empty YAML file. The examples of ruamel.yaml official page use doc string as a sample YAML content and then insert key-value pairs.


Solution

  • An empty scalar in a YAML document gives you the null YAML object that loads into Python as None:

    a: 1
    b: 
    

    The value for key b in data loaded from this will be None.

    An empty file, or an empty string, from which your data is loaded using ruamel.yaml is considered the same as a file/string that contains the scalar null:

    null
    

    If you load that, you get None back, and you cannot add new keys to that.

    To be sure check if the data you loaded is a dict or a subclass of a dict (in case you load with round_trip_load, you will get a ruamel.yaml.comment.CommentedMap), or check if it is None:

     data = ruamel.yaml.round_trip_load(open('myfile.yaml'))
     if data is None:
         data = ruamel.yaml.comments.CommentedMap()
     data.insert(len(data), key, value)
    

    You have to use a CommentedMap() as a normal Python dict doesn't have a method .insert()

    Beware thought that a toplevel item in YAML file might also be a scalar (string, integer, datetime, etc) or a sequence (which gets loaded as a list). The former probably cannot be .inserted() upon, and the later (list) takes only a single parameter for .insert()