我有两个YAML文件,“A”和“B”,我希望将A的内容插入到B中,要么拼接到现有的数据结构中,就像数组一样,要么作为元素的子元素,就像某个散列键的值一样。
这可能吗?怎么做?如果不是,是否有指向规范引用的指针?
我有两个YAML文件,“A”和“B”,我希望将A的内容插入到B中,要么拼接到现有的数据结构中,就像数组一样,要么作为元素的子元素,就像某个散列键的值一样。
这可能吗?怎么做?如果不是,是否有指向规范引用的指针?
当前回答
加上上面@Joshbode的初始回答,我对代码片段进行了一些修改,以支持UNIX风格的通配符模式。
不过我还没有在windows中进行测试。为了便于维护,我面临着将大型yaml中的数组拆分到多个文件中的问题,并正在寻找一种解决方案,以便在基本yaml的同一个数组中引用多个文件。因此,下面的解决方案。解决方案不支持递归引用。它只支持在基本yaml中引用的给定目录级别中的通配符。
import yaml
import os
import glob
# Base code taken from below link :-
# Ref:https://stackoverflow.com/a/9577670
class Loader(yaml.SafeLoader):
def __init__(self, stream):
self._root = os.path.split(stream.name)[0]
super(Loader, self).__init__(stream)
def include(self, node):
consolidated_result = None
filename = os.path.join(self._root, self.construct_scalar(node))
# Below section is modified for supporting UNIX wildcard patterns
filenames = glob.glob(filename)
# Just to ensure the order of files considered are predictable
# and easy to debug in case of errors.
filenames.sort()
for file in filenames:
with open(file, 'r') as f:
result = yaml.load(f, Loader)
if isinstance(result, list):
if not isinstance(consolidated_result, list):
consolidated_result = []
consolidated_result += result
elif isinstance(result, dict):
if not isinstance(consolidated_result, dict):
consolidated_result = {}
consolidated_result.update(result)
else:
consolidated_result = result
return consolidated_result
Loader.add_constructor('!include', Loader.include)
使用
a:
!include a.yaml
b:
# All yamls included within b folder level will be consolidated
!include b/*.yaml
其他回答
扩展@Josh_Bode的回答,这里是我自己的PyYAML解决方案,它的优点是yaml.Loader的一个自包含子类。它不依赖于任何模块级的全局变量,也不依赖于修改yaml模块的全局状态。
import yaml, os
class IncludeLoader(yaml.Loader):
"""
yaml.Loader subclass handles "!include path/to/foo.yml" directives in config
files. When constructed with a file object, the root path for includes
defaults to the directory containing the file, otherwise to the current
working directory. In either case, the root path can be overridden by the
`root` keyword argument.
When an included file F contain its own !include directive, the path is
relative to F's location.
Example:
YAML file /home/frodo/one-ring.yml:
---
Name: The One Ring
Specials:
- resize-to-wearer
Effects:
- !include path/to/invisibility.yml
YAML file /home/frodo/path/to/invisibility.yml:
---
Name: invisibility
Message: Suddenly you disappear!
Loading:
data = IncludeLoader(open('/home/frodo/one-ring.yml', 'r')).get_data()
Result:
{'Effects': [{'Message': 'Suddenly you disappear!', 'Name':
'invisibility'}], 'Name': 'The One Ring', 'Specials':
['resize-to-wearer']}
"""
def __init__(self, *args, **kwargs):
super(IncludeLoader, self).__init__(*args, **kwargs)
self.add_constructor('!include', self._include)
if 'root' in kwargs:
self.root = kwargs['root']
elif isinstance(self.stream, file):
self.root = os.path.dirname(self.stream.name)
else:
self.root = os.path.curdir
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'))
self.root = oldRoot
return data
我认为@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,因为输入流可能不是一个文件,但它非常有用!
您的问题没有要求使用Python解决方案,但这里有一个使用PyYAML的解决方案。
PyYAML允许您将自定义构造函数(例如!include)附加到YAML加载器。我已经包含了一个可以设置的根目录,以便这个解决方案支持相对和绝对文件引用。
基于类的解决方案
这是一个基于类的解决方案,避免了我原始响应的全局根变量。
请参阅以下要点,了解一个类似的、更健壮的Python 3解决方案,该解决方案使用元类注册自定义构造函数。
import yaml
import os
class Loader(yaml.SafeLoader):
def __init__(self, stream):
self._root = os.path.split(stream.name)[0]
super(Loader, self).__init__(stream)
def include(self, node):
filename = os.path.join(self._root, self.construct_scalar(node))
with open(filename, 'r') as f:
return yaml.load(f, Loader)
Loader.add_constructor('!include', Loader.include)
一个例子:
foo.yaml
a: 1
b:
- 1.43
- 543.55
c: !include bar.yaml
bar.yaml
- 3.6
- [1, 2, 3]
现在可以使用以下方法加载文件:
>>> with open('foo.yaml', 'r') as f:
>>> data = yaml.load(f, Loader)
>>> data
{'a': 1, 'b': [1.43, 543.55], 'c': [3.6, [1, 2, 3]]}
不幸的是,YAML在其标准中没有提供这个功能。
但是如果你正在使用Ruby,有一个gem通过扩展Ruby YAML库来提供你所要求的功能: https://github.com/entwanderer/yaml_extend
也许这可以激励你,试着与jbb惯例保持一致:
https://docs.openstack.org/infra/jenkins-job-builder/definition.html#inclusion-tags
-工作: 名称:test-job-include-raw-1 建造者: -壳: !包括生:include-raw001-hello-world.sh