PyYaml 组合两个 yaml 文件

问题描述 投票:0回答:2

我正在尝试建立一个系统,其中有几个(可能更多)用于配置的 yaml 文件。我希望能够在另一个文件中引用一个文件中的内容。

我知道 YAML 不允许这样做。

我认为我的计划是合并两个 YAML 文件,然后将其视为单个文件。我非常确定我可以将这两个文件放在一起,创建一个临时文件,并将其读取为 YAML,或者将文件读取为文本,连接它们,然后解析字符串。

但是,我觉得应该有更好的方法来做到这一点。有没有?

python yaml pyyaml
2个回答
0
投票

在 YAML 中引用的唯一方法是使用

&
(锚点)和
*
(别名)。为了使它们发挥作用,它们必须位于同一个 YAML 文档中。以下内容将不起作用(这是基于 merge key 功能,但普通对象引用具有相同的限制):

import ruamel.yaml

yaml_str = """\
a: &BASE { x: 1, y: 2}
---
b:
  << : *BASE
  z: 3
"""

for data in ruamel.yaml.load_all(yaml_str):
    print(data)

抛出一个编译器错误,提示“BASE”未找到。删除

---
文档分隔符,一切正常。

因此原则上连接两个文档是可行的。如果不将其与包含该别名的锚点连接起来,则无法单独加载带有别名的文档。

此外,需要注意的是,所有文档都必须在顶层具有映射或序列。如果要组合一个序列:

- &BASE a
- b

带有映射:

c: 1
d: *BASE

结果将无法加载。


如上所述,如果所有文件的顶级类型都相同,则无法加载 YAML 文件并将它们合并在内存中。 IE。给定合并密钥文档中的示例分为

1.yaml
:

- &CENTER { x: 1, y: 2 }
- &LEFT { x: 0, y: 2 }
- &BIG { r: 10 }
- &SMALL { r: 1 }

2.yaml

# Explicit keys
-
  x: 1
  y: 2
  r: 10
  label: center/big

3.yaml

# Merge one map
-
  << : *CENTER
  r: 10
  label: center/big

4.yaml

# Merge multiple maps
-
  << : [ *CENTER, *BIG ]
  label: center/big    

5.yaml

# Override
-
  << : [ *BIG, *LEFT, *SMALL ]
  x: 1
  label: center/big

不能在各个 YAML 文件上使用

load()
并将它们组合起来:

import ruamel.yaml
import glob

data = []
for file_name in sorted(glob.glob('*.yaml')):
    data.append(ruamel.yaml.load(open(file_name)))
print(ruamel.yaml.dump(data, allow_unicode=True))

(如果

2.yaml
等没有别名,上面的方法就可以工作)

如果您不想在程序之外连接文件,您可以 使用这个类:

class CombinedOpenForReading(object):
    def __init__(self, file_names):
        self._to_do = file_names[:]
        self._fp = None

    def __enter__(self):
        return self

    def __exit__(self, exception_type, exception_value, exception_traceback):
        if self._fp:
            self._fp.close()

    def read(self, size=None):
        res = ''
        while True:
            if self._fp is None:
                if not self._to_do:
                    return res
                else:
                    self._fp = open(self._to_do.pop(0))
            if size is None:
                data = self._fp.read()
            else:
                data = self._fp.read(size)
            if size is None or not data:
                self._fp.close()
                self._fp = None
            res += data
            if size is None:
                continue
            size -= len(data)
            if size == 0:
                break
        return res

要做的事:

import ruamel.yaml
import glob

with CombinedOpenForReading(sorted(glob.glob('*.yaml'))) as fp:
    data = ruamel.yaml.round_trip_load(fp)
assert data[6]['r'] == 10
print(ruamel.yaml.dump(data, Dumper=ruamel.yaml.RoundTripDumper))

获得:

- &CENTER {x: 1, y: 2}
- &LEFT {x: 0, y: 2}
- &BIG {r: 10}
- &SMALL {r: 1}
# Explicit keys
- x: 1
  y: 2
  r: 10
  label: center/big
# Merge one map
- <<: *CENTER
  r: 10
  label: center/big
# Merge multiple maps
- <<: [*CENTER, *BIG]
  label: center/big
# Override
- <<: [*BIG, *LEFT, *SMALL]
  x: 1
  label: center/big

(您必须按照正确的顺序提交文件,从而进行排序。并确保文件末尾有换行符,否则可能会出现意外错误。)


0
投票

我认为这比@Anthon 的更简单。它可能不完整,但我认为这就是我所需要的......

def merge(fList):
    ''' 
    Takes a list of yaml files and loads them as a single yaml document.
    Restrictions:
    1) None of the files may have a yaml document marker (---)
    2) All of the files must have the same top-level type (dictionary or list)
    3) If any pointers cross between files, then the file in which they are defined (&) must be 
    earlier in the list than any uses (*).
    '''

    if not fList:
        #if flist is the empty list, return an empty list. This is arbitrary, if it turns out that
        #an empty dictionary is better, we can do something about that.
        return []

    sList = []
    for f in fList:
        with open(f, 'r') as stream:
            sList.append(stream.read())
    fString = ''
    for s in sList:
        fString = fString + '\n'+ s

    y = yaml.load(fString)

    return y

欢迎评论。

© www.soinside.com 2019 - 2024. All rights reserved.