用键中的点分隔符解析 YAML

问题描述 投票:0回答:2

我们使用 YAML 配置来进行服务扩展。通常是这样的:

service:
  scalingPolicy:
    capacity:
      min: 1
      max: 1 

因此很容易使用基本的 PyYAML 打开并解析为字典以获得

config['service']['scalingPolicy']['capacity']['min']
结果为
1
。问题是一些配置是用点分隔符构建的,例如:

service.scalingPolicy.capacity:
  min: 1
  max: 1

此配置的基本使用者是 Java 的 Spring,并且以某种方式将其与上面的示例同等对待。但由于还需要使用 Python 解析这些配置 - 我将整个点分隔线作为

config['service.scalingPolicy.capacity']
键。

问题是 - 我如何让 python 解析任何类型的按键组合(

separated by dots
并用
tabulation and :
分隔)。我没有找到 Python YAML 库的相关参数(我已经检查了标准 PyYAML 和
ruamel.yaml
),并且手动处理任何可能的组合似乎是一个疯狂的想法。我唯一可能的想法是编写自己的解析器,但也许我缺少一些东西,所以我不必重新发明自行车。

python pyyaml ruamel.yaml
2个回答
3
投票

这并不是微不足道的,使用键来分割查找要容易得多 点递归到嵌套数据结构中。这里你有一个嵌套的 数据结构和不同的

[key]
查找意味着不同的事情 不同级别。

如果你在默认往返模式下使用

ruamel.yaml
,你可以添加一个类变量 到表示映射的类型,该映射定义了键的拆分内容和实例变量 跟踪已经匹配的前缀:

import sys
import ruamel.yaml
from ruamel.yaml.compat import ordereddict
from ruamel.yaml.comments import merge_attrib

yaml_str = """\
service.scalingPolicy.capacity:
  min: 1
  max: 1
"""


def mapgetitem(self, key):
    sep = getattr(ruamel.yaml.comments.CommentedMap, 'sep')
    if sep is not None: 
        if not hasattr(self, 'splitprefix'):
           self.splitprefix = ''
        if self.splitprefix:
            self.splitprefix += sep + key
        else:
            self.splitprefix = key
        if self.splitprefix not in self:
            for k in self.keys():
                if k.startswith(self.splitprefix):
                    break
                else:
                    raise KeyError(self.splitprefix)
            return self
        key = self.splitprefix
        delattr(self, 'splitprefix') # to make the next lookup work from start
    try:
        return ordereddict.__getitem__(self, key)
    except KeyError:
        for merged in getattr(self, merge_attrib, []):
            if key in merged[1]:
                return merged[1][key]
        raise

old_mapgetitem = ruamel.yaml.comments.CommentedMap.__getitem__ # save the original __getitem__
ruamel.yaml.comments.CommentedMap.__getitem__ = mapgetitem
ruamel.yaml.comments.CommentedMap.sep = '.'

yaml = ruamel.yaml.YAML()
# yaml.indent(mapping=4, sequence=4, offset=2)
# yaml.preserve_quotes = True
config = yaml.load(yaml_str)
print('min:', config['service']['scalingPolicy']['capacity']['min'])
print('max:', config['service']['scalingPolicy']['capacity']['max'])
print('---------')
config['service']['scalingPolicy']['capacity']['max'] = 42
# and dump with the original routine, as it uses __getitem__
ruamel.yaml.comments.CommentedMap.__getitem__ = old_mapgetitem
yaml.dump(config, sys.stdout)

给出:

min: 1
max: 1
---------
service.scalingPolicy.capacity:
  min: 1
  max: 42

0
投票

我找到了另一种解决方案,使用 pyyaml 用点替换递归字典键。我希望能有所帮助。

import yaml

my_yaml = """
service.scalingPolicy.capacity:
    min: 1
    max: 50
"""


def convert_to_dict(source_string, split_symbol='.', value=None):
    return_value = value
    elements = source_string.split(split_symbol)
    for element in reversed(elements):
        if element:
            return_value = {element: return_value}
    return return_value


def split_dots(source_dict):
    return_value = {}
    for key in source_dict:
        new_value = source_dict[key]
        if isinstance(source_dict[key], dict):
            new_value = split_dots(source_dict[key])
        new_key = key
        if '.' in key:
            new_dict = convert_to_dict(key, '.', new_value)
            new_key = list(new_dict.keys())[0]
            new_value = new_dict[new_key]
        if new_key in return_value:
            return_value[new_key].update(new_value)
        else:
            return_value[new_key] = new_value
    return return_value


def main():
    try:
        yaml_dict = yaml.safe_load(my_yaml)
        processed_yaml_dict = split_dots(yaml_dict)
        print("Min:", processed_yaml_dict['service']['scalingPolicy']['capacity']['min'])
        print("Max:", processed_yaml_dict['service']['scalingPolicy']['capacity']['max'])
        data = yaml.dump(processed_yaml_dict, indent=True)
        print("New yaml:", data)
    except yaml.YAMLError as exc:
        print(exc)


if __name__ == "__main__":
    main()
© www.soinside.com 2019 - 2024. All rights reserved.