Struggling to add and remove blank lines with ruamel roundtrip

Viewed 304

I have a large YAML file where I need to add 2 key-values to the end of one item.
Here is an example of the format of the original file:

repo1:
  description: sample description
  team: sample team

repo2:
  description: sample description
  team: sample team

I want the output to look like:

repo1:
  description: sample description
  team: sample team
  archived: true
  archived_date: sample date

repo2:
  description: sample description
  team: sample team

I'm using ruamel.yaml as roundtrip keeps the formatting and comments at the top of this large file. My code to do this is as follows, it takes an input of repo_name which is the item I want to add onto:

from ruamel.yaml import YAML

yaml = YAML()
with open("repos.yml", "r") as file:
  repos = yaml.load(file)
for repo in repos.items():
  if repo_name == repo[0]:
    repo[1].update({"archived": bool("true"), "archived_date": datetime.today().strftime('%d-%m-%Y')})
    break
  else:
    continue

with open("repos.yml", "w") as processed_file:
  yaml.default_flow_style = False
  yaml.width = float("inf")
  yaml.dump(repos, processed_file)

This nearly does what I want but the output looks like this:

repo1:
  description: sample description
  team: sample team

  archived: true
  archived_date: sample date
repo2:
  description: sample description
  team: sample team

How can I remove the blank line before what I've included and add one after it? Printing repo[1] in the loop shows the dictionary doesn't have a blank line but I want to use roundtrip to keep the rest of the formatting.

1 Answers

If, after loading, you would inspect the comment attribute, .ca, for repos['repo1'] you would see:

Comment(comment=None,
  items={'team': [None, None, CommentToken('\n\n', line: 3, col: 8), None]})

So the "comment" that consist of the empty EOL comment followed by an empty line ('\n\n') is attached to the team key, and that is the one you want to move to the new latest key:

import sys
from datetime import date
from ruamel.yaml import YAML

yaml_str = """
repo1:
  description: sample description
  team: sample team

repo2:
  description: sample description
  team: sample team
"""

repo_name = 'repo1'

yaml = YAML()
# yaml.default_flow_style = False
yaml.width = 2048

repos = yaml.load(yaml_str)
for repo in repos.items():
  if repo_name == repo[0]:
    repo[1].update(dict(archived=True, archived_date=date.today().strftime('%d-%m-%Y')))
    break
  else:
    continue

ca_items = repos['repo1'].ca.items
ca_items['archived_date'] = ca_items.pop('team')
yaml.dump(repos, sys.stdout)

which gives:

repo1:
  description: sample description
  team: sample team
  archived: true
  archived_date: 17-12-2021

repo2:
  description: sample description
  team: sample team
  • Make sure to make your code install/check the revision number of ruamel.yaml with which you found this to work. Internals might change at some point.
  • You can make the code more robust, by walking over the keys and detecting the key name of the last element and make sure it has a comment before reassigning it.
  • The line and column information doesn't need updating.
  • The recommended extension for YAML files (as per the yaml.org FAQ) has been .yaml for over 15 years.
  • bool("true") and bool("false") both dump to true in YAML, whereas bool("") dumps to false. It is more clear to use True and False in your dictionary update.
Related