我有一个大型YAML文件,大量使用YAML锚点和引用,例如:
warehouse:
obj1: &obj1
key1: 1
key2: 2
specific:
spec1:
<<: *obj1
spec2:
<<: *obj1
key1: 10
文件太大,所以我找了一个解决方案,允许我分成2个文件:warehouse.yaml
和specific.yaml
,并在{{1}中包含warehouse.yaml
}}。我读了this simple article,它描述了我如何使用PyYAML来实现这一点,但它也说不支持合并键(&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;
我真的遇到了错误:
yaml.composer.ComposerError:找到未定义的别名'obj1
当我试图这样做时。
所以,我开始寻找替代方法,我感到很困惑,因为我对PyYAML并不是很了解。
我可以获得所需的合并密钥支持吗?我的问题还有其他解决办法吗?
答案 0 :(得分:3)
在PyYAML中处理锚点和别名的关键是作为anchors
的一部分的词典Composer
。它将锚点映射到节点,以便可以查找别名。它的存在受到Composer
的存在的限制,Loader
是你使用的Loader
的复合元素。
yaml.load()
类仅在调用Loader()
期间存在,因此之后没有简单的方法来提取它:首先你必须创建compose_document()
的实例持久化,然后确保不调用正常的self.anchors = {}
方法(其中包括warehouse.yaml
,以便为下一个文档(在单个流中)清理)。
如果你有warehouse:
obj1: &obj1
key1: 1
key2: 2
:
specific.yaml
和warehouse: !include warehouse.yaml
specific:
spec1:
<<: *obj1
spec2:
<<: *obj1
key1: 10
:
specific.yaml
即使您可以保留,提取并传递锚信息,也永远无法使用您的代码段,因为处理!include
的作曲家会更早地遇到未定义的别名而不是标记{{ 1}}用于构造(并填充anchors
)。
您可以采取哪些措施来解决此问题,包括specific.yaml
specific:
spec1:
<<: *obj1
spec2:
<<: *obj1
key1: 10
来自warehouse.yaml
:
warehouse:
obj1: &obj1
key1: 1
key2: 2
specific: !include specific.yaml
,或包含在第三个文件中。 请注意,密钥specific
位于两个文件中。
运行这两个文件:
import sys
from ruamel import yaml
def my_compose_document(self):
self.get_event()
node = self.compose_node(None, None)
self.get_event()
# self.anchors = {} # <<<< commented out
return node
yaml.SafeLoader.compose_document = my_compose_document
# adapted from http://code.activestate.com/recipes/577613-yaml-include-support/
def yaml_include(loader, node):
with open(node.value) as inputfile:
return list(my_safe_load(inputfile, master=loader).values())[0]
# leave out the [0] if your include file drops the key ^^^
yaml.add_constructor("!include", yaml_include, Loader=yaml.SafeLoader)
def my_safe_load(stream, Loader=yaml.SafeLoader, master=None):
loader = Loader(stream)
if master is not None:
loader.anchors = master.anchors
try:
return loader.get_single_data()
finally:
loader.dispose()
with open('warehouse.yaml') as fp:
data = my_safe_load(fp)
yaml.safe_dump(data, sys.stdout, default_flow_style=False)
给出:
specific:
spec1:
key1: 1
key2: 2
spec2:
key1: 10
key2: 2
warehouse:
obj1:
key1: 1
key2: 2
如果您的specific.yaml
没有顶级密钥specific
:
spec1:
<<: *obj1
spec2:
<<: *obj1
key1: 10
然后将yaml_include()
的最后一行替换为:
return my_safe_load(inputfile, master=loader)
以上是用ruamel.yaml
完成的(免责声明:我是该软件包的作者)并在Python 2.7和3.6上进行了测试。通过更改导入,它也适用于PyYAML。
使用新的ruamel.yaml
API可以大大简化上述内容,因为传递给loader
构造函数的yaml_include()
知道YAML
实例,但当然您仍然需要一个不会破坏锚点的改编compose_document
。假设specific.yaml
没有顶级键specific
,以下内容会提供与之前相同的输出。
import sys
from ruamel.std.pathlib import Path
from ruamel.yaml import YAML, version_info
yaml = YAML(typ='safe', pure=True)
yaml.default_flow_style = False
def my_compose_document(self):
self.parser.get_event()
node = self.compose_node(None, None)
self.parser.get_event()
# self.anchors = {} # <<<< commented out
return node
yaml.Composer.compose_document = my_compose_document
# adapted from http://code.activestate.com/recipes/577613-yaml-include-support/
def yaml_include(loader, node):
y = loader.loader
yaml = YAML(typ=y.typ, pure=y.pure) # same values as including YAML
yaml.composer.anchors = loader.composer.anchors
return yaml.load(Path(node.value))
yaml.Constructor.add_constructor("!include", yaml_include)
data = yaml.load(Path('warehouse.yaml'))
yaml.dump(data, sys.stdout)
答案 1 :(得分:0)
似乎有人已经解决了这个问题,作为ruamel.yaml的扩展。
pip install ruamel.yaml.include
(source on GitHub)
要在上面获得所需的输出:
warehouse.yml
obj1: &obj1
key1: 1
key2: 2
specific.yml
specific:
spec1:
<<: *obj1
spec2:
<<: *obj1
key1: 10
您的代码应为:
from ccorp.ruamel.yaml.include import YAML
yaml = YAML(typ='safe', pure=True)
yaml.allow_duplicate_keys = True
with open('specific.yml', 'r') as ymlfile:
return yaml.load(ymlfile)
如果您想不在输出中包含仓库密钥,它还包含一个方便的!exclude函数。如果您只想要特定的密钥,则您的specific.yml
可以以:
!exclude includes:
- !include warehouse.yml
在这种情况下,您的Warehouse.yml也可能包含顶级warehouse:
键。