Gre*_*hon 5 python yaml config pyyaml ruamel.yaml
我想要一个基本配置文件,其他配置文件使用它来共享通用配置。
例如,如果我有一个base.yml文件
foo: 1
bar:
- 2
- 3
Run Code Online (Sandbox Code Playgroud)
然后是第二个some_file.yml文件
foo: 2
baz: "baz"
Run Code Online (Sandbox Code Playgroud)
我希望最终得到一个合并的配置文件
foo: 2
bar:
- 2
- 3
baz: "baz"
Run Code Online (Sandbox Code Playgroud)
编写处理标签的自定义加载器很容易!include。
class ConfigLoader(yaml.SafeLoader):
def __init__(self, stream):
super().__init__(stream)
self._base = Path(stream.name).parent
def include(self, node):
file_name = self.construct_scalar(node)
file_path = self._base.joinpath(file_name)
with file_path.open("rt") as fh:
return yaml.load(fh, IncludeLoader)
Run Code Online (Sandbox Code Playgroud)
然后我可以解析一个!include标签。所以如果我的文件是
inherit:
!include base.yml
foo: 2
baz: "baz"
Run Code Online (Sandbox Code Playgroud)
但现在基本配置是一个映射。即如果我加载文件我最终会得到
foo: 1
bar:
- 2
- 3
Run Code Online (Sandbox Code Playgroud)
但是如果我不将标签作为映射的一部分,例如
!include base.yml
foo: 2
baz: "baz"
Run Code Online (Sandbox Code Playgroud)
我收到一个错误。yaml.scanner.ScannerError: mapping values are not allowed here。
但我知道 yaml 解析器可以解析标签而不需要映射。因为我可以做类似的事情
!!python/object:foo.Bar
x: 1.0
y: 3.14
Run Code Online (Sandbox Code Playgroud)
那么,如何编写加载程序和/或构建 YAML 文件,以便可以在配置中包含另一个文件?
在 YAML 中,您不能混合标量、映射键和序列元素。这是无效的 YAML:
- abc
d: e
Run Code Online (Sandbox Code Playgroud)
这也是
some_file_name
a: b
Run Code Online (Sandbox Code Playgroud)
并且您引用了该标量并提供了标签当然不会改变它是无效 YAML 的事实。
正如您已经发现的,您可以欺骗加载器返回 adict而不是字符串(就像解析器已经内置了非基本类型的构造函数,例如datetime.date)。
那这个:
!!python/object:foo.Bar
x: 1.0
y: 3.14
Run Code Online (Sandbox Code Playgroud)
之所以有效,是因为整个映射都被标记了,您只需标记一个标量值。
什么也是无效语法:
!include base.yaml
foo: 2
baz: baz
Run Code Online (Sandbox Code Playgroud)
但你可以这样做:
!include
filename: base.yaml
foo: 2
baz: baz
Run Code Online (Sandbox Code Playgroud)
并以特殊方式处理 'filename' 键,或者使标记!include为空键:
!include : base.yaml # : is a valid tag character, so you need the space
foo: 2
baz: baz
Run Code Online (Sandbox Code Playgroud)
不过,我会考虑使用合并键,因为合并本质上就是您想要做的事情。以下 YAML 有效:
import sys
import ruamel.yaml
from pathlib import Path
yaml_str = """
<<: {x: 42, y: 196, foo: 3}
foo: 2
baz: baz
"""
yaml = ruamel.yaml.YAML(typ='safe')
yaml.default_flow_style = False
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)
Run Code Online (Sandbox Code Playgroud)
这使:
baz: baz
foo: 2
x: 42
y: 196
Run Code Online (Sandbox Code Playgroud)
所以你应该能够做到:
<<: !load base.yaml
foo: 2
baz: baz
Run Code Online (Sandbox Code Playgroud)
任何了解合并键的人都会知道如果包含带有 value 的base.yaml键会发生什么,并且也会理解:foo3
<<: [!load base.yaml, !load config.yaml]
foo: 2
baz: baz
Run Code Online (Sandbox Code Playgroud)
(因为我倾向于将“include”与 C 预处理器中的文本包含相关联,所以我认为 `!load' 可能是一个更合适的标签,但这可能是一个品味问题)。
为了让合并键起作用,最简单的方法可能是直接子类化Constructor,因为合并是在标签解析之前完成的:
import sys
import ruamel.yaml
from ruamel.yaml.nodes import MappingNode, SequenceNode, ScalarNode
from ruamel.yaml.constructor import ConstructorError
from ruamel.yaml.compat import _F
from pathlib import Path
class MyConstructor(ruamel.yaml.constructor.SafeConstructor):
def flatten_mapping(self, node):
# type: (Any) -> Any
"""
This implements the merge key feature http://yaml.org/type/merge.html
by inserting keys from the merge dict/list of dicts if not yet
available in this node
"""
merge = [] # type: List[Any]
index = 0
while index < len(node.value):
key_node, value_node = node.value[index]
if key_node.tag == 'tag:yaml.org,2002:merge':
if merge: # double << key
if self.allow_duplicate_keys:
del node.value[index]
index += 1
continue
args = [
'while constructing a mapping',
node.start_mark,
'found duplicate key "{}"'.format(key_node.value),
key_node.start_mark,
"""
To suppress this check see:
http://yaml.readthedocs.io/en/latest/api.html#duplicate-keys
""",
"""\
Duplicate keys will become an error in future releases, and are errors
by default when using the new API.
""",
]
if self.allow_duplicate_keys is None:
warnings.warn(DuplicateKeyFutureWarning(*args))
else:
raise DuplicateKeyError(*args)
del node.value[index]
if isinstance(value_node, ScalarNode) and value_node.tag == '!load':
file_path = None
try:
if self.loader.reader.stream is not None:
file_path = Path(self.loader.reader.stream.name).parent / value_node.value
except AttributeError:
pass
if file_path is None:
file_path = Path(value_node.value)
# there is a bug in ruamel.yaml<=0.17.20 that prevents
# the use of a Path as argument to compose()
with file_path.open('rb') as fp:
merge.extend(ruamel.yaml.YAML().compose(fp).value)
elif isinstance(value_node, MappingNode):
self.flatten_mapping(value_node)
print('vn0', type(value_node.value), value_node.value)
merge.extend(value_node.value)
elif isinstance(value_node, SequenceNode):
submerge = []
for subnode in value_node.value:
if not isinstance(subnode, MappingNode):
raise ConstructorError(
'while constructing a mapping',
node.start_mark,
_F(
'expected a mapping for merging, but found {subnode_id!s}',
subnode_id=subnode.id,
),
subnode.start_mark,
)
self.flatten_mapping(subnode)
submerge.append(subnode.value)
submerge.reverse()
for value in submerge:
merge.extend(value)
else:
raise ConstructorError(
'while constructing a mapping',
node.start_mark,
_F(
'expected a mapping or list of mappings for merging, '
'but found {value_node_id!s}',
value_node_id=value_node.id,
),
value_node.start_mark,
)
elif key_node.tag == 'tag:yaml.org,2002:value':
key_node.tag = 'tag:yaml.org,2002:str'
index += 1
else:
index += 1
if bool(merge):
node.merge = merge # separate merge keys to be able to update without duplicate
node.value = merge + node.value
yaml = ruamel.yaml.YAML(typ='safe', pure=True)
yaml.default_flow_style = False
yaml.Constructor = MyConstructor
yaml_str = """\
<<: !load base.yaml
foo: 2
baz: baz
"""
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)
print('---')
file_name = Path('test.yaml')
file_name.write_text("""\
<<: !load base.yaml
bar: 2
baz: baz
""")
data = yaml.load(file_name)
yaml.dump(data, sys.stdout)
Run Code Online (Sandbox Code Playgroud)
这打印:
bar:
- 2
- 3
baz: baz
foo: 2
---
bar: 2
baz: baz
foo: 1
Run Code Online (Sandbox Code Playgroud)
笔记:
open(filename, 'rb'))。IncludeLoader,则可以提供带有合并键的完整工作示例(或者为您找出它由于某种原因不起作用) )yaml.load()是实例方法调用 ( import ruamel.yaml; yaml = ruamel.yaml.YAML()) 还是调用函数 ( from ruamel import yaml)。您不应该使用后者,因为它已被弃用。| 归档时间: |
|
| 查看次数: |
10245 次 |
| 最近记录: |