在保留注释的同时向 yaml 添加/添加新条目

prepending / adding new entries to yaml while preserving comment

我想将条目添加到带有注释的现有 yaml 文件的开头(或结尾,这无关紧要),我希望保留这些注释。有没有一种优雅的方法可以使用 ruamel.yaml?

import sys
import ruamel.yaml

root_str = """\
# block comment
# block comment
# block comment
root:
    - class:
        - subclass:
            var1: a
    - class:
        var2: b    
"""

prepend_str = """\
pre_root:
    - pre_class:
        var1: c
"""

yaml = ruamel.yaml.YAML()
yaml.indent(mapping=4, sequence=4, offset=4)
root = yaml.load(root_str)
prepend = yaml.load(prepend_str)

这显然行不通:

new_str = {**prepend, **root}

这适用于我的情况,但看起来很老套:

import io
from contextlib import redirect_stdout

with io.StringIO() as buf, redirect_stdout(buf):
    ruamel.yaml.dump(prepend, sys.stdout, Dumper=ruamel.yaml.RoundTripDumper)
    ruamel.yaml.dump(root, sys.stdout, Dumper=ruamel.yaml.RoundTripDumper)
    output = buf.getvalue()

with open("out.yaml", "w") as yaml_file:
    yaml_file.write(output)

有几种方法可以使这更简单。首先你不应该混合 新 API ( yaml = ruamel.yaml.YAML() ) 与已弃用的旧 API ( ruamel.yaml.dump(....). 其次,您可以省略 contextlib 内容并直接流式传输到 BytesIO(更好 比 StringIO 作为 YAML.dump() 写一个 UTF-8 流):

with io.BytesIO() as buf:
    yaml.dump(prepend, buf)
    yaml.dump(root, buf)
    output = buf.getvalue()

with open("out.yaml", "wb") as yaml_file:
    yaml_file.write(output)

sys.stdout.write(open("out.yaml").read())

给出:

pre_root:
    - pre_class:
        var1: c
# block comment
# block comment
# block comment
root:
    - class:
        - subclass:
            var1: a
    - class:
        var2: b

您可以通过直接写入打开的文件来进一步简化此操作(请注意,在这两种情况下 已打开 "wb",因为 UTF-8):

with open("out.yaml", "wb") as yaml_file:
    yaml.dump(prepend, yaml_file)
    yaml.dump(root, yaml_file)

这会为您提供与之前相同的文件。

如果要保留root_str开头的块注释,应该 在数据级别合并:

# This also works in case prepend has multiple keys. When prepend
# and root have keys in common, you need to do something smarter
for key in reversed(prepend):  
    root.insert(0, key, prepend[key])

yaml.dump(root, sys.stdout)

给出:

# block comment
# block comment
# block comment
pre_root:
    - pre_class:
        var1: c
root:
    - class:
        - subclass:
            var1: a
    - class:
        var2: b

如果您只想添加或附加 YAML 文件,为什么不使用像 new_str = '\n'.join([prepend_str, root_str]).

这样的字符串呢?

但是,在 YAML 之前添加或附加可能会违反唯一键。 使用对象表示时,您还可以将其合并

我用 ruamel.yaml 解决它的唯一方法是:

import sys
from ruamel.yaml import YAML

root_str = """\
# block comment
# block comment
# block comment
root:
    - class:
        - subclass:
            var1: a
    - class:
        var2: b    
"""

prepend_str = """\
pre_root:
    - pre_class:
        var1: c
"""
yaml = YAML()
root = yaml.load(root_str)
print("--- root: ----")
yaml.dump(root, sys.stdout)

# print(type(root))
# see methods of object:
# help(root)

prepend = yaml.load(prepend_str)
print("--- prepend: ----")
yaml.dump(prepend, sys.stdout)

def merge_into(base, mixin):
    # compare the key sets for shared keys
    bks = base.keys()
    mks = mixin.keys()
    if set(bks).intersection(mks):
       print("Warning: Keys overlap! Aborted merge.")
       print("\tbase keys:", bks)
       print("\tmixin keys:", mks)
       return
    # append (without control of order)
    for k in mks:
        base[k] = mixin[k]

print("--- prepend merged into root: ----")
merge_into(root, prepend)
# does not work as expected
# root.insert(0, '', prepend, 'attached comment')
yaml.dump(root, sys.stdout)

然而,这只是简单地合并(实际上不是前置而是附加)并忽略混合中的任何注释。

打印输出为:

--- root: ----
# block comment
# block comment
# block comment
root:
- class:
  - subclass:
      var1: a
- class:
    var2: b
--- prepend: ----
pre_root:
- pre_class:
    var1: c
--- prepend merged into root: ----
# block comment
# block comment
# block comment
root:
- class:
  - subclass:
      var1: a
- class:
    var2: b
pre_root:
- pre_class:
    var1: c