Search code examples
pythonyamlcommentsruamel.yaml

how to add surrounding comments to an element in a list


fruits = CommentedSeq()
fruits.append('banana')
fruits.yaml_set_comment_before_after_key(0, 'comments above', after='comments below')
yaml.dump({'fruits': fruits}, sys.stdout)

I'd like the output to be the following:

fruits:
  # comments above
  - banana
  # comments below

But from my test, comments below is discarded.


Solution

  • The (undocumented) method you use is called yaml_set_comment_before_or_after_key. That last part key refers to the key of key-value pair in a mapping. You are using a sequence and although there is some similarity of handling both on round-trip, they are not handled in the same way.

    First thing to do in this case is see if your desired results round-trips at all:

    import sys
    import ruamel.yaml
    
    yaml_str = """\
    fruits:
      # comments above
      - banana
      # comments below
    """
    
    yaml = ruamel.yaml.YAML()
    yaml.indent(sequence=4, offset=2)
    data = yaml.load(yaml_str)
    yaml.dump(data, sys.stdout)
    

    This gives:

    fruits:
      # comments above
      - banana
      # comments below
    

    so it does round-trip.

    Then compare the comments attached to the loaded data with your constructed data:

    from ruamel.yaml.comments import CommentedSeq
    
    fruits = CommentedSeq()
    fruits.append('banana')
    fruits.yaml_set_comment_before_after_key(0, 'comments above', after='comments below')
    
    print('loaded:   ', data['fruits'].ca)
    print('generated:', fruits.ca)
    

    that is not the same:

    loaded:    Comment(comment=[None, [CommentToken('# comments above\n', line: 1, col: 2)]],
      items={0: [CommentToken('\n  # comments below\n', line: 3, col: 2), None, None, None]})
    generated: Comment(comment=None,
      items={0: [None, [CommentToken('# comments above\n', col: 0)], None, [CommentToken('# comments below\n', col: 2)]]})
    

    The loaded data doesn't have comments above associated with sequence element 0. It is a comment that exists before the sequence start. You can still get what you want:

    from ruamel.yaml.tokens import CommentToken
    from ruamel.yaml.error import CommentMark
    
    indent = 2
    fruits.ca.comment = [None, [CommentToken('# comments above\n', CommentMark(indent))]]
    fruits.ca.items[0] = [CommentToken(f'\n{" "*indent}# comments below\n', CommentMark(0)), None, None, None]
    yaml.dump({'fruits': fruits}, sys.stdout)
    

    which gives:

    fruits:
      # comments above
      - banana
      # comments below
    

    Make sure you pin the ruamel.yaml version for your application, when you are using these kind of internals that will change.