Создать эволюционный анализатор конфигурации YAML - PullRequest
1 голос
/ 29 марта 2019

Для моего приложения 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.yaml и config_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 Ответ

1 голос
/ 29 марта 2019

Это кажется немного громоздким, чтобы поддерживать. Значения по умолчанию Аргумент распространяется на несколько значений, инкапсуляция отсутствует.

Вы должны просто сделать класс конфигурации, производный от 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}

Я не думаю, что использование FullConstructor PyYAML поможет вам сделать лучше конфигурационные файлы, безопасное конструирование и использование тегов explict для любых конструкции, которые вы хотите пометить, лучше и удобнее для пользователя.

Конечно, вы можете сделать это в PyYAML, если вы действительно только хочу поддержать YAML 1.1 (который был заменен YAML 1.2 в 2009 году, почти 10 лет назад), и нужно только подмножество 1.1, что PyYAML могу загрузить. (Отказ от ответственности: я автор ruamel.yaml)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...