Конфигурационные файлы как инструмент управления приложениями на Python

Конфигурационные файлы как инструмент управления приложениями на Python

Вводные замечания о форматах конфигурационных файлов

Обычно, когда речь заходит о конфигурационных файлах, то на память приходят статичные текстовые файлы, содержащие описание различных настроек системы и выступающие главным образом в роли малозначительного элемента инфраструктуры кода. Т акого рода файлы несложно превратить в динамический элемент инфраструктуры, который позволит управлять логикой работы приложения без необходимости вносить изменения в программный код.

  • INI – самый простой формат из рассмотренных. С одной стороны чем проще читать конфигурационный файл, тем лучше, но с другой – файлы *.INI могут оперировать только одномерными структурами, т.е. структурами с простой одноуровневой иерархией. В большинстве прикладных задач, когда приходится иметь дело с моделями объектов, допускающих представление в виде списков, ассоциативных массивов и т.п., возможностей INI оказывается недостаточно.
  • JSON-файл выглядит как обычный словарь Python и может включать сложные иерархические зависимости, однако с точки зрения читаемости проигрывает и YAML, и TOML. Кроме того, JSON не поддерживает комментарии, а они часто могут значительно упростить сопровождение кода.
  • В отличие от предыдущих, формат TOML обладает несоизмеримой гибкостью и широтой арсенала поддерживаемых типов данных. TOML поддерживает простые пары «ключ-значение», массивы, классические и встроенные таблицы, массивы таблиц, булевы значения, а также локальные временные метки и временные метки со смещением.

Для сравнения рассмотрим одну и ту же модель объекта, описанного с помощью TOML и JSON.

Вот TOML-представление модели объекта:

А вот JSON-представление:

Синтаксические особенности JSON – избыточные фигурные и квадратные скобки – делают сложноструктурные JSON-файлы «размазанными».

Формат YAML обладает схожими с форматом TOML возможностями (в смысле гибкости представления моделей объектов и разнообразия поддерживаемых типов данных), но на сложных структурах выглядит компактнее.

Python-библиотеки для работы с конфигурационными файлами

Из всего многообразия предназначенных для работы с конфигурационными файлами библиотек – ориентированных на какой-то конкретный формат или «всеядных» – можно выделить следующие:

    : это элемент стандартной библиотеки Python, предназначенный для работы с INI-файлами Microsoft Windows. В распоряжении пользователя есть класс ConfigParser , который реализует базовые возможности библиотеки. : элемент не входит в стандартную библиотеку, поэтому его нужно установить с помощью менеджера пакетов pip pip install pyyaml . В Python-сценарии обращение к библиотеке выглядит как import yaml .
    : этот элемент тоже представляет собой стороннее решение для работы с форматом TOML и требует установки с помощью pip install toml . : это очень гибкая библиотека, которая 1) позволяет работать со всеми популярными форматами конфигурационных файлов (*.toml, *.yaml, *.json, *.ini, *.py), 2) поддерживает мультипрофили, т.е. конфигурационный файл может содержать несколько заголовков, относящихся к различным стадиям готовности программного продукта (например, default, development, production и т.д.), а нужный набор настроек затем вызывается с указанием соответствующего заголовка, 3) умеет работать с переменными окружения («работает из коробки» с библиотекой dotenv), 4) предлагает утилиту командной строки для выполнения операций общего назначения (init, list, write, validate и т.д.). Устанавливается библиотека с помощью менеджера пакетов pip pip install dynaconf .
    : это, строго говоря, не просто библиотека, а полноценная платформа, предназначенная для решения широкого круга задач, связанных с конфигурацией сложных приложений. Установить hydra можно либо с помощью менеджера пакетов pip pip install hydra-core –upgrade , либо с помощью менеджера пакетов conda conda install -c conda-forge hydra-core .

В основном, выбор библиотеки определяется следующими аспектами:

  • сложностью задачи и ее особенностями. К примеру, конфигурация маршрута подготовки моделей машинного обучения для развертывания на облачной платформе может быть выполнена и с помощью PyYAML/toml, а вот управление сложным web-проектом скорее всего потребует продвинутых возможностей dynaconf или hydra;
  • требованиями к показателю переиспользования кода. С этой точки зрения преимущества на стороне библиотеки hydra;
  • гибкостью решения и одновременно простотой сопровождения кода. Здесь чаще используются библиотеки PyYAML и toml.

Несколько слов о синтаксисе YAML

Синтаксис YAML прост и лаконичен, но есть несколько особенностей. Практически каждый YAML-файл строится на базе списка. Каждый элемент списка это список пар «ключ-значение», который обычно называют словарем. То есть представление модели объекта с помощью YAML сводится к тому, чтобы описать эту модель в терминах списков и словарей.

Иногда YAML-файлы начинаются с тройного тире — и заканчиваются троеточием . , а иногда эти последовательности символов в начале и в конце файла опускают. PyYAML корректно работает в обоих случаях.

Все элементы списка располагаются на одном и том же уровне и начинаются с – (тире и пробел):

Словари YAML представляют собой, как в и Python, наборы пар «ключ-значение» (за двоеточием должен следовать пробел):

YAML поддерживает стандартные типы данных: целочисленный (int), вещественный (float), булев (boolean), строковый (string) и null:

При необходимости можно явно указывать тип данных значения с помощью конструкции !![тип данных] , например:

  • pi: !!float 3.14159 ,
  • flag: !!bool false и т.д.

Булевы значения, могут иметь разные варианты записи, но рекомендуется использовать true и false , чтобы YAML-файл был совместим с настройками по умолчанию большинства YAML-линтеров:

В YAML необязательно заключать строковые константы в кавычки, но в ситуациях, когда требуется явно подчеркнуть строковую природу значения, кавычки не помешают. Допускается использовать как одинарные, так и двойные кавычки. Единственное отличие заключается в том, что двойные кавычки разрешают использовать управляющие коды строковых констант – их еще называют экранированными последовательностями – \t (символ горизонтальной табуляции), \r (символ возврата каретки), \n (символ перехода на новую строку) и т.д.

Для работы с длинными строками используют символы | и >

Бывает, что отдельные фрагменты YAML-файла требуется повторить несколько раз. Для решения такого рода задач используются якоря & и псевдонимы * . Пример:

Псевдонимы можно задавать и для блоков:

С помощью ключа слияния <<: можно «наследовать» и переопределять разделы:

Для редактирования YAML-файлов подойдет любой текстовый редактор, но важен следующий момент. Не всегда есть возможность использовать современный редактор с красивым графическим интерфейсом пользователя, а вот текстовый редактор Vim предустановлен на большинстве UNIX-подобных операционных систем.

Проверка типов управляющих параметров конфигурационного файла

В простых сценариях использования, когда можно ограничиться контролем типа данных на уровне конфигурационного файла, будет достаточно использовать явное указание типа с помощью !![тип данных] .

В случае сложной структуры модели объекта и в случае сценария, когда приложение должно уметь работать не только с конфигурационными файлами, подготовленными самим разработчиком, но и со сторонними конфигурационными файлами, с точки зрения устойчивости имеет смысл перенести проверку типов на уровень логики приложения.

Проверку типов можно организовать с помощью стандартной библиотеки Python dataclasses и сторонней библиотеки marshmallow_dataclass .

Рассмотрим простой пример использования marshmallow_dataclass, заимствованный со страницы проекта:

Здесь класс Building содержит всего два атрибута: вещественный height и строковый name . С помощью метода marshmallow.validate выполняется проверка на минимальное значение атрибута height , а с помощью field атрибут name получает значение по умолчанию.

Класс City содержит опциональный атрибут name , который ожидает получить либо объект строкового типа, либо None . Атрибут buildings ожидает принять список объектов типа Building .

Далее создаем экземпляр схемы city_schema на базе объекта класса City , а затем загружаем словарь. Схема ожидает получить строку для атрибута name и список объектов Building , то есть список объектов, которые описываются строковым атрибутом name и вещественными атрибутом height .

Используя точечную нотацию и нотацию списков можно добраться до значений атрибутов.

Шаблон применения конфигурационных файлов как инструмента управления Python-приложениями

Разобравшись с основными понятиями и концепциями, перейдем к рассмотрению связки «конфигурационный файл + Python-приложение».

В самом простом случае связка состоит из одного конфигурационного файла и одного модуля Python. В общем случае для управления Python-приложением может использоваться несколько файлов конфигурации (различных форматов), а базовая логика приложения опирается на наборов модулей и пакетов.

Сильная сторона такого представления задачи заключается в возможности явным образом декомпозировать приложение на:

  • изменяющийся блок управляющих параметров, или другими словами на динамическую часть инфраструктуры кода, с которой разработчик будет взаимодействовать в будущем,
  • неизменяющийся блок базовой логики – статичную часть кода, которая не требует внесения изменений в программный код напрямую.

Упрощенно, шаблон использования конфигурационных файлов как текстового интерфейса к Python-приложению выглядит так: управляющие параметры выносятся в конфигурационный файл, а базовая логика описывается в Python-модуле.

Этот шаблон распространяется на приложения произвольной сложности, но у него есть естественные ограничения.

Если конфигурационный файл разрастается до размеров исключающих быстрый поиск нужных управляющих параметров или затрудняет реализацию сложной логики управления, то стоит задуматься о более эффективных приемах организации работы.

В этом вопросе могут помочь:

    : библиотека для разработки настольных приложений с графическим интерфейсом пользователя; : мощная библиотека для прототипирования браузерных решений с графическим интерфейсом.

Пример связки «конфигурационный YAML-файл + Python-приложение»

В качестве примера возьмем задачу обнаружения выбросов в дискретной реализации стационарного гауссовского случайного процесса с различными типами корреляционных функций и ограничимся рассмотрением следующих их типов: экспоненциального, экспоненциально-косинусного, экспоненциально-косинусно-синусного (с плюсом) и экспоненциально-косинусно-синусного (с минусом).

Для простоты гипотеза о выбросах проверяется с помощью классической стандартизованной Z-оценки и модифицированной робастной Z-оценки (англ.) на медиане. Для моделирования псевдослучайных процессов с гауссовским распределением ординат и корреляционными функциями заданного типа использовались алгоритмы, заимствованные из книги Быкова, В.В. «Цифровое моделирование в статистической радиотехнике» .

Полный код примера с пояснениями и деталями реализации доступен в github-репозитории .

Вот его структура:

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

В figure_examples хранятся демонстрационные примеры работы сценария python_scripts/main.py , а прямые результаты его работы в – каталоге figures .

Управлять типом корреляционной функции процесса можно с помощью параметра kind_acf . Смысл остальных управляющих параметров ( w_star , w0 , alpha и т.д.) должен быть понятен из комментариев.

Для запуска приложения следует перейти в корневой каталог проекта и выполнить:

Функция cmd_line_parser из модуля helper_funcs_and_class_schema.py прочитает значения флагов ( –config-path , –output-fig-path ), а затем вернет путь до конфигурационного файла и путь до файла с результатами анализа псевдослучайного процесса.

Затем, функция read_yaml_file прочитает конфигурационный файл и проверит типы управляющих параметров. Если типы управляющих параметров корректны и ошибок нет, то функция вернет объект типа Params , к атрибутам которого можно будет обратиться с помощью точечной нотации. Остается только построить сводку с использованием функции draw_graph .

К примеру, изменить значение управляющего параметра, даже не открывая конфигурационный файл, а затем создать на базе этого конфигурационного файла новый можно так

Здесь sed ищет строку « w0: !!float 3.0 », заменяет ее строкой « w0: !!float 3.15 » и записывает результат ( > ) в новый конфигурационный файл.

На рисунках ниже приведены результаты работы Python-приложения с различными комбинациями управляющих параметров.

<i>Сводка по анализу выбросов стационарного гауссовского псевдослучайного процесса с корреляционной функцией экспоненциального типа</i>Сводка по анализу выбросов стационарного гауссовского псевдослучайного процесса с корреляционной функцией экспоненциального типа <i>Сводка по анализу выбросов стационарного гауссовского псевдослучайного процесса с корреляционной функцией экспоненциально-косинусного типа</i>Сводка по анализу выбросов стационарного гауссовского псевдослучайного процесса с корреляционной функцией экспоненциально-косинусного типа <i>Сводка по анализу выбросов стационарного гауссовского псевдослучайного процесса с корреляционной функцией экспоненциально-косинусно-синусного типа (минус)</i>Сводка по анализу выбросов стационарного гауссовского псевдослучайного процесса с корреляционной функцией экспоненциально-косинусно-синусного типа (минус) <i>Сводка по анализу выбросов стационарного гауссовского псевдослучайного процесса с корреляционной функцией экспоненциально-косинусно-синусного типа (плюс)</i>Сводка по анализу выбросов стационарного гауссовского псевдослучайного процесса с корреляционной функцией экспоненциально-косинусно-синусного типа (плюс)