简体   繁体   中英

How to read a component in YAML file so that I can edit it's key value using ruamel.yaml?

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?

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 elem ents (items) of a list, but over the key s 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()

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM