我有两个YAML文件,“A”和“B”,我希望将A的内容插入到B中,要么拼接到现有的数据结构中,就像数组一样,要么作为元素的子元素,就像某个散列键的值一样。
这可能吗?怎么做?如果不是,是否有指向规范引用的指针?
我有两个YAML文件,“A”和“B”,我希望将A的内容插入到B中,要么拼接到现有的数据结构中,就像数组一样,要么作为元素的子元素,就像某个散列键的值一样。
这可能吗?怎么做?如果不是,是否有指向规范引用的指针?
当前回答
据我所知,YAML中不直接支持include,你必须自己提供一种机制,然而,这通常很容易做到。
我在我的python应用程序中使用YAML作为配置语言,在这种情况下经常定义这样的约定:
>>> main.yml <<<
includes: [ wibble.yml, wobble.yml]
然后在我的(python)代码中:
import yaml
cfg = yaml.load(open("main.yml"))
for inc in cfg.get("includes", []):
cfg.update(yaml.load(open(inc)))
唯一的缺点是include中的变量将总是覆盖main中的变量,并且没有办法通过改变“includes:”语句在main中出现的位置来改变优先级。yml文件。
稍微不同的一点是,YAML不支持include,因为它并不是像基于文件的标记那样专门设计的。如果你在AJAX请求的响应中得到一个包含,它意味着什么?
其他回答
使用Symfony,它对yaml的处理将间接地允许您嵌套yaml文件。诀窍在于使用参数选项。例如:
common.yml
parameters:
yaml_to_repeat:
option: "value"
foo:
- "bar"
- "baz"
config.yml
imports:
- { resource: common.yml }
whatever:
thing: "%yaml_to_repeat%"
other_thing: "%yaml_to_repeat%"
其结果将与:
whatever:
thing:
option: "value"
foo:
- "bar"
- "baz"
other_thing:
option: "value"
foo:
- "bar"
- "baz"
YML标准没有指定这样做的方法。而且这个问题并不局限于YML。JSON也有同样的限制。
许多使用基于YML或JSON配置的应用程序最终都会遇到这个问题。当这种情况发生时,他们就会制定自己的惯例。
例如,对于swagger API定义:
$ref: 'file.yml'
例如,对于docker组合配置:
services:
app:
extends:
file: docker-compose.base.yml
或者,如果您希望将一个yml文件的内容拆分到多个文件中,就像内容树一样,您可以定义自己的文件夹结构约定并使用(现有的)合并脚本。
对于Python用户,可以尝试pyyaml-include。
安装
pip install pyyaml-include
使用
import yaml
from yamlinclude import YamlIncludeConstructor
YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir='/your/conf/dir')
with open('0.yaml') as f:
data = yaml.load(f, Loader=yaml.FullLoader)
print(data)
假设我们有这样的YAML文件:
├── 0.yaml
└── include.d
├── 1.yaml
└── 2.yaml
1.Yaml的内容:
name: "1"
2.Yaml的内容:
name: "2"
按名称包含文件
顶层: 如果0。yaml是:
!include include.d/1.yaml
我们会得到:
{"name": "1"}
在映射: 如果0。yaml是:
file1: !include include.d/1.yaml
file2: !include include.d/2.yaml
我们会得到:
file1:
name: "1"
file2:
name: "2"
在序列: 如果0。yaml是:
files:
- !include include.d/1.yaml
- !include include.d/2.yaml
我们会得到:
files:
- name: "1"
- name: "2"
ℹ注意: 文件名可以是绝对的(如/usr/conf/1.5/ make .yml)或相对的(如../../cfg/img.yml)。
通过通配符包含文件
文件名可以包含shell样式的通配符。从通配符找到的文件中加载的数据将按顺序设置。
如果0。yaml是:
files: !include include.d/*.yaml
我们会得到:
files:
- name: "1"
- name: "2"
ℹ注意: 对于Python>=3.5,如果递归参数!include YAML标记为真,模式" ** "将匹配任何文件和零个或多个目录和子目录。 在大型目录树中使用“**”模式可能会因为递归搜索而消耗过多的时间。
为了启用递归参数,我们应该在映射或序列模式下编写!include标记:
序列模式参数:
!include [tests/data/include.d/**/*.yaml, true]
映射模式参数说明
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
我认为@max - b使用的解决方案看起来很棒。但是,对于嵌套的包含,它没有成功。例如,如果config_1。Yaml包含config_2。Yaml,其中包括config_3。Yaml,装弹机有问题。但是,如果您在加载时简单地将新的加载器类指向它自己,它就可以工作!具体来说,如果我们将旧的_include函数替换为稍微修改过的版本:
def _include(self, loader, node):
oldRoot = self.root
filename = os.path.join(self.root, loader.construct_scalar(node))
self.root = os.path.dirname(filename)
data = yaml.load(open(filename, 'r'), loader = IncludeLoader)
self.root = oldRoot
return data
经过反思,我同意其他评论,嵌套加载一般不适合yaml,因为输入流可能不是一个文件,但它非常有用!
结合其他答案,这里是一个简短的解决方案,没有重载Loader类,它可以与任何加载器操作文件:
import json
from pathlib import Path
from typing import Any
import yaml
def yaml_include_constructor(loader: yaml.BaseLoader, node: yaml.Node) -> Any:
"""Include file referenced with !include node"""
# noinspection PyTypeChecker
fp = Path(loader.name).parent.joinpath(loader.construct_scalar(node)).resolve()
fe = fp.suffix.lstrip(".")
with open(fp, 'r') as f:
if fe in ("yaml", "yml"):
return yaml.load(f, type(loader))
elif fe in ("json", "jsn"):
return json.load(f)
else:
return f.read()
def main():
loader = yaml.SafeLoader # Works with any loader
loader.add_constructor("!include", yaml_include_constructor)
with open(...) as f:
yml = yaml.load(f, loader)
PyTypeChecker的存在是为了防止pep检查警告预期类型'ScalarNode',得到'节点'而不是通过节点:yaml。节点到loader.construct_scalar()。
如果yaml。加载输入流不是文件流,因为loader.name在这种情况下不包含路径:
class Reader(object):
...
def __init__(self, stream):
...
if isinstance(stream, str):
self.name = "<unicode string>"
...
elif isinstance(stream, bytes):
self.name = "<byte string>"
...
else:
self.name = getattr(stream, 'name', "<file>")
...
在我的用例中,我知道只包含YAML文件,所以解决方案可以进一步简化:
def yaml_include_constructor(loader: yaml.Loader, node: yaml.Node) -> Any:
"""Include YAML file referenced with !include node"""
with open(Path(loader.name).parent.joinpath(loader.construct_yaml_str(node)).resolve(), 'r') as f:
return yaml.load(f, type(loader))
Loader = yaml.SafeLoader # Works with any loader
Loader.add_constructor("!include", yaml_include_constructor)
def main():
with open(...) as f:
yml = yaml.load(f, Loader=Loader)
甚至是使用lambda的一行代码:
Loader = yaml.SafeLoader # Works with any loader
Loader.add_constructor("!include",
lambda l, n: yaml.load(Path(l.name).parent.joinpath(l.construct_scalar(n)).read_text(), type(l)))