Gerardo Blanco
Gerardo Blanco

Reputation: 21

ruamel.yaml: how do I add a new element at the top level, and inside other elements

ruamel.yaml docs are sparse, and I haven't found the responses I needed here.

Contents of my_file.yaml:

desc: "blahblahblah"

Q1: How do I (programmatically) add "size" as an element at the same level as "desc" (i.e. at the TOP level of the tree)

Q2: How do I (programmatically) add subelements n-times deeper

The resulting file should look something like this:

desc: 
    - "blahblahblah"
    - "desc_trans":
        - "Chinese": 
          - "Mandarin": "blahblahblah"
          - "Uyghur": "blahblahblah"
        - "Spanish": "blahblahblah"
size: "40k"

Upvotes: 2

Views: 2702

Answers (1)

Anthon
Anthon

Reputation: 76578

ruamel.yaml docs are sparse, but what you are trying to do is mostly on the Python level. You don't change YAML, you load YAML from a file and parse it into a data structure, change that data structure (using Python, as ruamel.yaml is a Python package) and then dump the data structure back to YAML.

First of all: you cannot get exactly what you want, as your indentation of sequences is inconsistent. The value for desc has an indent of six, with the offset of the block sequence indicator (-) within that being four. The value for "desc_trans" has an indent of four with an offset of two, and that for "Chinese" has the minimal indent of two. ruamel.yaml has only one pair of sequence indent and offset values applied to all (block) sequences.

There are a few tricky things you are trying to achieve:

  • loading a scalar value for the key desc and magically translating it into a sequence (with the old scalar value being the first element). The code should of course make this change only when necessary.

  • having a mix of plain scalars combined with scalars with superfluous double quotes. If I see it correctly, any scalar string that is not a key of the root-level mapping should be double quoted. You can achieve that programmatically, but in the example I'll do that by hand.

Assuming Python3:

from pathlib import Path
import sys
import ruamel.yaml
from ruamel.yaml.scalarstring import DoubleQuotedScalarString as dq

yaml = ruamel.yaml.YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
yaml.preserve_quotes = True
data = yaml.load(Path('my_file.yaml'))

assert isinstance(data, dict)  # check that the root-level is a dictionary like object 
data['size'] = dq('40k')

if not isinstance(data['desc'], list):
    data['desc'] = [data['desc']]  # change the non-list into a list
l = data['desc']
# make the desc_trans dict first, cannot use dict(desc_trans=[dict(Chinese=[Mandarin=....
desc_trans = []
l.append({dq('desc_trans'): desc_trans})
Chinese = {dq('Chinese'): [{dq('Mandarin'): dq('blahblahblah')}]}
Spanish = {dq('Spanish'): dq('blahblahblah')}
desc_trans.append(Chinese)
desc_trans.append(Spanish)
# the next line also could be simplified using `desc_trans.append()`
data['desc'][1]['desc_trans'][0]['Chinese'].append({dq('Uygur'): dq('blahblahblah')})

# if you want to write to a file use: out = Path('output.yaml')
out = sys.stdout
yaml.dump(data, out)

which gives:

desc:
  - "blahblahblah"
  - "desc_trans":
      - "Chinese":
          - "Mandarin": "blahblahblah"
          - "Uygur": "blahblahblah"
      - "Spanish": "blahblahblah"
size: "40k"

In general I recommend assigning a deep element to a variable and then appending or assigning new keys to that.

As ruamel.yaml actually loads the mappings in an ordered dictionary subclas, it is not really necessary to have these sequences of single key-value pair mappings, that you have as value for "dest_trans" and for "Chinese". Leaving out the lists simplifies the code, and the subclass has an .insert() method to give the necessary control over the key ordering.

Upvotes: 2

Related Questions