在Ruamel.yaml中的键之前插入评论

Ale*_*vey 2 ruamel.yaml

我正在使用Ruamel Python库以编程方式编辑人工编辑的YAML文件。

我正在努力了解如何将注释插入结构化数据中。

我有一些数据:

a:
  b: banana
  c: apple
  d: orange
Run Code Online (Sandbox Code Playgroud)

我想添加评论和新密钥:

a:
  b: banana
  c: apple
  d: orange
  # This is my comment
  e: pear
Run Code Online (Sandbox Code Playgroud)

是否可以使用进行此操作ruamel.yaml,如果可以,如何进行?

Ant*_*hon 6

是的,这是可能的,因为您可以通过往返检查:

import sys
import ruamel.yaml

with open('your_input.yaml') as fp:
    data = ruamel.yaml.round_trip_load(yaml_str)
ruamel.yaml.round_trip_dump(data, sys.stdout)
Run Code Online (Sandbox Code Playgroud)

打印的输出将与您的输入匹配,因此注释data在转储时会以某种方式插入到结构的层次结构中,并保留并写出。

ruamel.yaml注释中附加了listsdict的包装器类,您可以使用进行检查print(type(data['a']):它是CommentedMap(来自ruamel.yaml.comment.py)。可以通过属性访问a的属性的悬挂值的注释信息:_yaml_commentca

cm = data['a']
print(cm.ca)
Run Code Online (Sandbox Code Playgroud)

给出:

items={'e': [None, [CommentToken(value='# This is my comment\n')], None, None]})
Run Code Online (Sandbox Code Playgroud)

这表明注释与键关联,该键e位于注释之后。不幸的是,CommentToken不能仅仅通过像表示它那样调用它来创建,即CommentToken(value='# This is my comment\n')它需要更多的工作,因为它至少需要一个开始Mark

没有创建此类注释的“帮助程序”例程,但是通过查看CommentedMap其基类,CommentedBase您可以得出以下结果¹:

import sys
import ruamel.yaml

if not hasattr(ruamel.yaml.comments.CommentedMap, "yaml_set_comment_before_key"):
    def my_yaml_set_comment_before_key(self, key, comment, column=None,
                                       clear=False):
        """
        append comment to list of comment lines before key, '# ' is inserted
            before the comment
        column: determines indentation, if not specified take indentation from
                previous comment, otherwise defaults to 0
        clear: if True removes any existing comments instead of appending
        """
        key_comment = self.ca.items.setdefault(key, [None, [], None, None])
        if clear:
            key_comment[1] = []
        comment_list = key_comment[1]
        if comment:
            comment_start = '# '
            if comment[-1] == '\n':
                comment = comment[:-1]  # strip final newline if there
        else:
            comment_start = '#'
        if column is None:
            if comment_list:
                 # if there already are other comments get the column from them
                column = comment_list[-1].start_mark.column
            else:
                column = 0
        start_mark = ruamel.yaml.error.Mark(None, None, None, column, None, None)
        comment_list.append(ruamel.yaml.tokens.CommentToken(
            comment_start + comment + '\n', start_mark, None))
        return self

    ruamel.yaml.comments.CommentedMap.yaml_set_comment_before_key = \
        my_yaml_set_comment_before_key
Run Code Online (Sandbox Code Playgroud)

使用CommentedMap此方法扩展后,您可以执行以下操作:

yaml_str = """\
a:
  b: banana
  c: apple
  d: orange
  e: pear
"""

data = ruamel.yaml.round_trip_load(yaml_str)
cm = data['a']

cm.yaml_set_comment_before_key('e', "This is Alex' comment", column=2)
cm.yaml_set_comment_before_key('e', 'and this mine')
ruamel.yaml.round_trip_dump(data, sys.stdout)
Run Code Online (Sandbox Code Playgroud)

要得到:

a:
  b: banana
  c: apple
  d: orange
  # This is Alex' comment
  # and this mine one
  e: pear
Run Code Online (Sandbox Code Playgroud)

除非您读注释,否则无法查询cm注释应位于哪一列,以使其与键对齐e(该列是在写出数据结构时确定的)。您可能会想存储一个特殊值(-1?)并尝试在输出过程中确定该值,但是在流输出时上下文很少。您当然可以确定/将列设置为嵌套级别(1),然后将其乘以缩进(您提供的缩进round_trip_dump,默认为2)。

注释功能是为了保留往返功能而设计的,而不是最初用于修改或插入新功能的,因此不能保证接口的稳定性。考虑到这一点,请确保您在周围创建一个例程或一组例程yaml_set_comment_before_key()以进行更改,因此,如果界面发生更改,则只有一个模块可以更新(能够附加注释的功能不会消失) ,但是这样做的方法可能会改变)


¹ 也许不是您,但是由于我是ruamel.yaml的作者,所以我应该能够在文档不足的地方找到自己的出路。