创建一个渐进的YAML配置解析器

时间:2019-03-29 16:11:40

标签: python yaml

对于我的Python应用程序,我需要创建一个配置文件解析器,该解析器将能够解析该配置文件的多个版本。

目标是创建一个配置解析器,无论配置文件的版本如何,该解析器都将是安全的。

以以下示例为例:今天,我将软件和特定的配置文件发送给客户端。明天,我将发布一个新的软件版本。如何确保它与今天发送的配置文件兼容?还有下一个软件版本?

这里是一个示例:说我有config_1.yaml配置文件:

version: 1
digits:
  - one
  - two
  - three

我希望我的Python阅读:

{'digits': ['one', 'two', 'three'], 'version': 1}

然后一段时间,我将配置文件格式更新为config_2.yaml

version: 2
digits:
  - one
  - two
  - three

colors:
  red: #FF0000
  green: #00FF00
  blue: #0000FF

我希望我的软件将此配置读取为:

{'colors': {'blue': '#0000FF', 'green': '#00FF00', 'red': '#FF0000'},
 'digits': ['one', 'two', 'three'],
 'version': 2}

但是,我还需要此软件版本才能将config_1.yaml读为:

{'colors': [], 'digits': ['one', 'two', 'three'], 'version': 1}

以此类推:发布第三个软件版本时,我希望它能够读取config_3.yaml

version: 3
digits:
  - one
  - two
  - three

colors:
  red: '#FF0000'
  green: '#00FF00'
  blue: '#0000FF'

constants:
  pi: 3.1415
  e: 2.71828

为:

{'colors': {'blue': '#0000FF', 'green': '#00FF00', 'red': '#FF0000'},
 'constants': {'e': 2.71828, 'pi': 3.1415},
 'digits': ['one', 'two', 'three'],
 'version': 3}

分别为config_1.yamlconfig_2.yaml

{'colors': [], 'constants': {}, 'digits': ['one', 'two', 'three'], 'version': 1}

{'colors': {'blue': '#0000FF', 'green': '#00FF00', 'red': '#FF0000'},
 'constants': {},
 'digits': ['one', 'two', 'three'],
 'version': 2}

我编写了以下代码以获得这些结果:

import yaml
from pprint import pprint


def read_yaml(f_path):
    with open(f_path, 'r', encoding='utf-8') as fid:
        config = yaml.load(fid, Loader=yaml.FullLoader)
    return config


def read_config_1(config):
    pass


def read_config_2(config):
    if config['version'] < 2:
        read_config_1(config)
        # the "colors" part of the config was added between versions 1 and 2
        config['colors'] = []


def read_config_3(config):
    if config['version'] < 3:
        read_config_2(config)
        # the "constants" part of the config was added between versions 2 and 3
        config['constants'] = {}


def read_config_file(f_path):
    config = read_yaml(f_path)
    read_config_3(config)
    return config


if __name__ == '__main__':
    for f_name in [f'config_{i}.yaml' for i in range(1, 4)]:
        print('-'*20 + ' ' + f_name + ' ' + '-'*20)
        config = read_config_file(f_name)
        pprint(config)
        print()

有人对此代码有任何评论吗,或是否有效率的建议?

1 个答案:

答案 0 :(得分:1)

这似乎有点麻烦。默认值 分布在多个值上,没有封装。

您应该制作一个 从dict派生的配置类(假设您要 它可以与dict一起使用(订阅)。其__init__应该 将最新版本的所有值初始化为合理的值(空 列表,字典等)。之后,您读取YAML文件并覆盖值 你在那里找到。

from pprint import pprint
from pathlib import Path
import ruamel.yaml

CONFIG_VERSION = 3  # latest version number used in config files

yaml = ruamel.yaml.YAML(typ='safe')

class Config(dict):
   def __init__(self, file_name):
       self['constants'] = {}  # added version 3
       self['colors'] = []     # added version 2
       if not hasattr(file_name, 'open'):
           file_name = Path(file_name)
       d = yaml.load(file_name)
       if d['version'] > CONFIG_VERSION:
           print("don't know how to handle newer config version", d['version'])
       # optionally do something special for some versions
       # if d['version'] < NR:
       #      self.update_from_NR(d)
       # else: 
       self.update(d)
       d['version'] = CONFIG_VERSION  # in case you dump the config


if __name__ == '__main__':
    for f_name in [f'config_{i}.yaml' for i in range(1, 4)]:
        print('-'*20 + ' ' + f_name + ' ' + '-'*20)
        config = Config(f_name)
        pprint(config)
        print()

给出:

-------------------- config_1.yaml --------------------
{'colors': [], 'constants': {}, 'digits': ['one', 'two', 'three'], 'version': 1}

-------------------- config_2.yaml --------------------
{'colors': {'blue': None, 'green': None, 'red': None},
 'constants': {},
 'digits': ['one', 'two', 'three'],
 'version': 2}

-------------------- config_3.yaml --------------------
{'colors': {'blue': '#0000FF', 'green': '#00FF00', 'red': '#FF0000'},
 'constants': {'e': 2.71828, 'pi': 3.1415},
 'digits': ['one', 'two', 'three'],
 'version': 3}

我认为使用PyYAML的FullConstructor不会帮助您做得更好 配置文件,安全构建和对任何对象使用显式标签 您想要标记的结构更好,更用户友好。

您当然可以在PyYAML中执行上述操作,前提是您真的只 想要支持YAML 1.1(在2009年被YAML 1.2取代,将近10 年前),并且只需要PyYAML的1.1子集 可以加载。 (免责声明:我是ruamel.yaml的作者)