使用 deepmerge python 模块并使用 ruamel.yaml 合并 2 个 yaml 时保留注释的问题

Issue in preserving comments when merging 2 yamls using deepmerge python module and using ruamel.yaml

代码:

from deepmerge import always_merger
import ruamel.yaml

fileA = "source.yaml"
fileB = "dest.yaml"

yaml = ruamel.yaml.YAML()

with open(fileA,'r+') as f:
   fileAdictionary= yaml.load(f)

with open(fileB,'r+') as f:
   fileBdictionary = yaml.load(f)

result = always_merger.merge(fileAdictionary, fileBdictionary)
with open('output.yaml','w+') as f:
   yaml.dump(result,f)

source.yaml

element:
    connection:
        test: true

dest.yaml

element:
    connection:
        test: true
    networkPolicy:
        enabled: true
        # network policy has been enabled
    test_str_param: "abc"
    # comment for string parameter
    test_int_param: 10
    # comment for the integer parameter
    test_bool_param: true
    # comment for the boolean parameter

实际输出

output.yaml

element:
  connection:
    test: true
  networkPolicy:
    enabled: true
        # network policy has been enabled
  test_str_param: abc
  test_int_param: 10
  test_bool_param: true

问题描述

如您在 output.yaml 中所见,元素 test_str_paramtest_int_paramtest_bool_param 的注释未保留或继承自 dest.yaml

预期

需要做什么才能在最终 output.yaml

中保留与所有参数有关的所有注释

预期输出

element:
    connection:
        test: true
    networkPolicy:
        enabled: true
        # network policy has been enabled
    test_str_param: "abc"
    # comment for string parameter
    test_int_param: 10
    # comment for the integer parameter
    test_bool_param: true
    # comment for the boolean parameter

您从输入中加载的是 CommentedMap 个实例,这些是子 类 dict 个。 deepmerge 将它们作为指令处理,但因为它没有做任何特别的事情 对于注释,如果键合并到一个已经存在的文件中,您将丢失它们 CommentedMap(如 fileAdictionary['element']),但不是当一个值被合并到 是一个 CommentedMap 并且在 fileAdictionary 中还不存在(即没有 fileAdictionary['element']['networkPolicy'])

deepmerge允许你添加自己的策略,但我不确定是什么 best/recommended 添加新类型的过程:

import sys
from pathlib import Path
# from deepmerge import always_merger
import deepmerge
import ruamel.yaml
RYCM = ruamel.yaml.comments.CommentedMap

class CommentedMapStrategies(deepmerge.strategy.core.StrategyList):
    NAME = 'CommentedMap'

    @staticmethod
    def strategy_merge(config, path, base, nxt):
        for k, v in nxt.items():
            if k not in base:
                base[k] = v
            else:
                base[k] = config.value_strategy(path + [k], base[k], v)
        try:
            for k, v in nxt.ca.items.items():
                base.ca.items[k] = v
        except AttributeError:
            pass
    
        return base

    @staticmethod
    def strategy_override(config, path, base, nxt):
        """
        move all keys in nxt into base, overriding
        conflicts.
        """
        return nxt

# insert as it needs to be before 'dict'
deepmerge.DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES.insert(0, (RYCM, 'merge'))
Merger = deepmerge.merger.Merger
Merger.PROVIDED_TYPE_STRATEGIES[RYCM] = CommentedMapStrategies

always_merger = Merger(deepmerge.DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES, ['override'], ['override'])

fileA = Path('source.yaml')
fileB = Path('dest.yaml')

yaml = ruamel.yaml.YAML()
yaml.indent(mapping=4)
result = always_merger.merge(yaml.load(fileA), yaml.load(fileB))
yaml.dump(result, sys.stdout)

给出:

element:
    connection:
        test: true
    networkPolicy:
        enabled: true
        # network policy has been enabled
    test_str_param: abc
    # comment for string parameter
    test_int_param: 10
    # comment for the integer parameter
    test_bool_param: true
    # comment for the boolean parameter

根据您在 YAML 文档中的注释位置,您可能需要 amend/complete 在 strategy_merge.

中复制评论

请注意,以上内容依赖于 CommentedMap 可能会发生变化的内部结构, 所以在升级之前固定 ruamel.yaml 版本并进行测试。