PyYaml "include file" 和 yaml 别名 (anchors/references)
PyYaml "include file" and yaml aliases (anchors/references)
我有一个大型 YAML 文件,其中大量使用 YAML 锚点和引用,例如:
warehouse:
obj1: &obj1
key1: 1
key2: 2
specific:
spec1:
<<: *obj1
spec2:
<<: *obj1
key1: 10
文件太大,所以我寻找了一个解决方案,允许我拆分为 2 个文件:warehouse.yaml
和 specific.yaml
,并将 warehouse.yaml
包含在 specific.yaml
。我读了 this simple article,它描述了我如何使用 PyYAML 来实现它,但它也说不支持合并键 (<<)。
我真的报错了:
yaml.composer.ComposerError: found undefined alias 'obj1
当我尝试那样做时。
所以,我开始寻找替代方法,但我很困惑,因为我对 PyYAML 了解不多。
我可以获得所需的合并密钥支持吗?我的问题还有其他解决方案吗?
在 PyYAML 中处理锚点和别名的关键是字典 anchors
,它是 Composer
的一部分。它将锚点映射到节点,以便可以查找别名。它的存在受 Composer
的存在限制,它是您使用的 Loader
的复合元素。
Loader
class 仅在调用 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
用于构造(并填充 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 可以大大简化上述内容,因为传递给 yaml_include()
构造函数的 loader
知道 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)
现在好像有人解决了这个问题作为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:
密钥。
我有一个大型 YAML 文件,其中大量使用 YAML 锚点和引用,例如:
warehouse:
obj1: &obj1
key1: 1
key2: 2
specific:
spec1:
<<: *obj1
spec2:
<<: *obj1
key1: 10
文件太大,所以我寻找了一个解决方案,允许我拆分为 2 个文件:warehouse.yaml
和 specific.yaml
,并将 warehouse.yaml
包含在 specific.yaml
。我读了 this simple article,它描述了我如何使用 PyYAML 来实现它,但它也说不支持合并键 (<<)。
我真的报错了:
yaml.composer.ComposerError: found undefined alias 'obj1
当我尝试那样做时。
所以,我开始寻找替代方法,但我很困惑,因为我对 PyYAML 了解不多。
我可以获得所需的合并密钥支持吗?我的问题还有其他解决方案吗?
在 PyYAML 中处理锚点和别名的关键是字典 anchors
,它是 Composer
的一部分。它将锚点映射到节点,以便可以查找别名。它的存在受 Composer
的存在限制,它是您使用的 Loader
的复合元素。
Loader
class 仅在调用 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
用于构造(并填充 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 可以大大简化上述内容,因为传递给 yaml_include()
构造函数的 loader
知道 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)
现在好像有人解决了这个问题作为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:
密钥。