Keep YAML file order with ruamel

Viewed 2066

I'm trying to add new elements to a YAML file, but I would like that the new elements to keep the visual order, to make the file easier to read for humans. I was bale to keep the order with orderdict, but with this we get the !!omap directive "polluting" the YAML file. I'd like new elements to have the same order has the "yaml_doc" example in the code below.

Is there a way to add new elements to YAML, and keep the order when dumping the file and not showing the omap directive?

Below is the code I have and the output I get:

import sys
from ruamel.yaml import YAML
from ruamel.yaml.comments import CommentedSeq as cs
from ruamel.yaml.comments import TaggedScalar as ts
from ruamel.yaml.scalarstring import SingleQuotedScalarString as sq
from ruamel.ordereddict import ordereddict


yaml_doc = """\
version: 0
projects:
  - name: A1
    dir: B1
    aplan:
      when: ["X", "Y", "Z"]
    wflow: l
"""
yaml = YAML()
yaml.preserve_quotes = True
yaml.width = 4096
data = yaml.load(yaml_doc)

ref = data['projects']

record_to_add = dict(name='A2', dir='B2',
                     aplan=dict(when=["X", "Y", "Z"]), wflow='l')
ref.append(record_to_add)

record_to_add_2 = ordereddict([('name', 'A3'), ('dir', 'B3'), ('aplan', ordereddict(
    [('when', ['X', 'Y', 'Z'])])), ('wflow', 'l')])
ref.append(record_to_add_2)

yaml.dump(data, sys.stdout)

Output of the dump

version: 0
projects:
- name: A1
  dir: B1
  aplan:
    when: ["X", "Y", "Z"]
  wflow: l
- aplan:
    when:
    - X
    - Y
    - Z
  wflow: l
  name: A2
  dir: B2
- !!omap
  - name: A3
  - dir: B3
  - aplan: !!omap
    - when:
      - X
      - Y
      - Z
  - wflow: l
1 Answers

To preserve the order of the keys, ruamel.yaml uses a subclass of ordereddict, which you can check by using print(type(data)) right after loading. That is a CommentedMap and you should create those like you did your ordereddict:

import sys
from ruamel.yaml import YAML
from ruamel.yaml.comments import CommentedSeq as cs
from ruamel.yaml.comments import TaggedScalar as ts
from ruamel.yaml.scalarstring import SingleQuotedScalarString as sq
from ruamel.yaml.comments import CommentedMap as ordereddict


yaml_doc = """\
version: 0
projects:
  - name: A1
    dir: B1
    aplan:
      when: ["X", "Y", "Z"]
    wflow: l
"""
yaml = YAML()
yaml.preserve_quotes = True
yaml.width = 4096
data = yaml.load(yaml_doc)
# print(type(data['projects'][0])) # would give: <class 'ruamel.yaml.comments.CommentedMap'>

ref = data['projects']

record_to_add = dict(name='A2', dir='B2',
                     aplan=dict(when=["X", "Y", "Z"]), wflow='l')
ref.append(record_to_add)

record_to_add_2 = ordereddict([('name', 'A3'), ('dir', 'B3'), ('aplan', ordereddict(
    [('when', ['X', 'Y', 'Z'])])), ('wflow', 'l')])
ref.append(record_to_add_2)

yaml.dump(data, sys.stdout)

which gives:

version: 0
projects:
- name: A1
  dir: B1
  aplan:
    when: ["X", "Y", "Z"]
  wflow: l
- name: A2
  dir: B2
  aplan:
    when:
    - X
    - Y
    - Z
  wflow: l
- name: A3
  dir: B3
  aplan:
    when:
    - X
    - Y
    - Z
  wflow: l

Ordered mappings are explicitly mentioned in the YAML spec and that is what a Python ordereddict maps to when dumping (and the reverse when loading). CommentedMap can also handle preserving comments and node attributes.

Related