0

This is my YAML file (input.yaml):

team_member:
  name: Max
  hobbies:
    - Reading

team_leader:
  name: Stuart
  hobbies:
    - dancing

I want to edit this YAML file to add more values in key 'hobbies', example:

team_member:
  name: Max
  hobbies:
    - Reading
    - Painting

team_leader:
  name: Stuart
  hobbies:
    - Dancing
    - Fishing

I tried to implement the code Anthon to fit my situation but it didn't helped at all, because the indention level of that YAML file is different from mine.
Example:

import sys
import ruamel.yaml

yaml = ruamel.yaml.YAML()
# yaml.preserve_quotes = True
with open('input.yaml') as fp:
    data = yaml.load(fp)
for elem in data:
    if elem['name'] == 'Stuart':
         elem['hobbies'] = ['Fishing']
         break  # no need to iterate further
yaml.dump(data, sys.stdout)

I get error "TypeError('string indices must be integers',)", I know this code might be completely wrong, but I am new to ruamel.yaml.

How to code this?

Anthon
  • 69,918
  • 32
  • 186
  • 246
MEHUL SOLANKI
  • 47
  • 3
  • 10

2 Answers2

1

The thing missing form the error message displayed is the line number (I assume that it is 9). That points to the line

    if elem['name'] == 'Stuart':

And if that doesn't give you a clue, the approach that I recommend in such cases is starting to add some print functions, so that you know what you are working on. The for loop looks like:

for elem in data:
    print('elem', elem)
    if elem['name'] == 'Stuart':
         print('elem->hobbies', elem['hobbies'])
         elem['hobbies'] = ['Fishing']

this prints

 elem team_member

before the exception is thrown, and I hope that will make you realize your are not iterating over the elements (items) of a list, but over the keys of a dict (constructed from the root level mapping in your YAML). And the value associated with the key is the object having a key name and a key hobbies.

So change the variable elem to key to make clear what you're handling and then proceed to work with value, the value associated with that key instead of elem within that loop¹:

for key in data:
    value = data[key]
    if value['name'] == 'Stuart':
         print('value->hobbies', value['hobbies'])
         value['hobbies'] = ['Fishing']

This gives:

value->hobbies ['dancing']
team_member:
  name: Max
  hobbies:
  - Reading

team_leader:
  name: Stuart
  hobbies:
  - Fishing

So we got rid of the exception, but the result is not exactly what you want. The element dancing for the key 'hobbies' is gone, because you assign a new (list) value to that key, whereas what you should do is append a single item to the list. We can also get rid of the print function by now:

for key in data:
    value = data[key]
    if value['name'] == 'Stuart':
         value['hobbies'].append('Fishing')

This will get you two items in the final sequence in the file. There is a few more things to address:

  • the capitalization of dancing incorrect. To correct that, add a line handling the list if there is only one element
  • the code for the name Max, needs to be added (and that is why you need to get rid of the break in your code)
  • the empty line, is considered a comment on the last element of the first sequence, that comment needs to be moved
  • your indentation of sequences is non-default

The final code would be like:

from pathlib import Path
import ruamel.yaml

path = Path('input.yaml')
yaml = ruamel.yaml.YAML()
yaml.indent(sequence=4, offset=2)  # for the non-default indentation of sequences

data = yaml.load(path)
for key in data:
    value = data[key]
    if value['name'] == 'Stuart':
         if len(value['hobbies']) == 1:
             value['hobbies'][0] = value['hobbies'][0].capitalize()
         value['hobbies'].append('Fishing')
    elif value['name'] == 'Max':
         last_item_index = len(value['hobbies']) - 1
         value['hobbies'].append('Painting')
         comments = value['hobbies'].ca
         if not comments.items[last_item_index][0].value.strip():
             # move empty comment lines from previous last item to new last item
             comments.items[last_item_index + 1] = comments.items.pop(last_item_index)

yaml.dump(data, path)

Which gives something quite close to what you wanted to get

team_member:
  name: Max
  hobbies:
    - Reading
    - Painting

team_leader:
  name: Stuart
  hobbies:
    - Dancing
    - Fishing

¹Alternative for the first two lines: for key, value in data.items()

Anthon
  • 69,918
  • 32
  • 186
  • 246
  • Thanks Anthon, I tried your code and it gets executed completely but my yaml file is not getting edited. I have also tried adding these line in code: out = Path('output.yaml') out = sys.stdout yaml.dump(data, out) – MEHUL SOLANKI Dec 23 '18 at 11:24
  • Actually, I am using Visual Studio to run this code, the python console window shows the output as you showed above but the actual yaml file does not gets edited. I want to edit the same input.yaml file but just for testing I added out = Path('output.yaml') | out = sys.stdout | yaml.dump(data, out) , but didn't helped. – MEHUL SOLANKI Dec 23 '18 at 11:43
  • There is no indication in your question that you want to write the original file., what you added doesn't work because the first assignment to out gets nullified by doing `out = sys.stdout`. If you leave that part out things of course work. – Anthon Dec 23 '18 at 14:38
0

Thanks Anthon your code worked I have to edit this code as follows:

import sys
import ruamel.yaml
from pathlib import Path

yaml = ruamel.yaml.YAML()
path = Path('input.yaml')
yaml.indent(sequence=4, offset=2)  # for the non-default indentation of sequences
with open(path) as fp:
    data = yaml.load(fp)
for key in data:
    value = data[key]
    if value['name'] == 'Stuart':
         if len(value['hobbies']) == 1:
             value['hobbies'][0] = value['hobbies'][0].capitalize()
         value['hobbies'].append('Fishing')
    elif value['name'] == 'Max':
         last_item_index = len(value['hobbies']) - 1
         value['hobbies'].append('Painting')
         comments = value['hobbies'].ca
         if not comments.items[last_item_index][0].value.strip():
             # move empty comment lines from previous last item to new last item
             comments.items[last_item_index + 1] = comments.items.pop(last_item_index)
yaml.dump(data, path)
MEHUL SOLANKI
  • 47
  • 3
  • 10
  • I updated my answer to reflect that you want to dump the output back to `input.yaml`, and updated to use `Path`. Using `open(path)` is not good Python, use `path.open()` instead (or pass `path` in directly to load as I do). You are importing `sys` in your code, but are not using it. – Anthon Dec 23 '18 at 14:46