11
Июн
2021

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

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

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

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

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

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

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

view_obj.toml
        [[fruits]]
name = "apple"

[fruits.physical]  # подтаблица
color = "red"
shape = "round"

[[fruits.varieties]]  # вложенный массив таблиц
name = "red delicious"

[[fruits.varieties]]
name = "granny smith"

[[fruits]]
name = "banana"

[[fruits.varieties]]
name = "plantain"
    

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

view_obj.json
        {
  "fruits": [
    {
      "name": "apple",
      "physical": {
        "color": "red",
        "shape": "round"
      },
      "varieties": [
        { "name": "red delicious" },
        { "name": "granny smith" }
      ]
    },
    {
      "name": "banana",
      "varieties": [
        { "name": "plantain" }
      ]
    }
  ]
}
    

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

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

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

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

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

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

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

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

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

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

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

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

start_end.yaml
        --- # Начало файла
# Список фруктов
- Apple
- Orange
- Strawberry
- Mango
... # Конец файла
    

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

dict.yaml
        # Запись о сотруднике
martin:
  name: Martin Johnson
  job: Developer
  skill: Elite
    

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

types.yaml
        integer: 25
float: 25.0
exponent: 12.3015e+05
boolean: Yes
string: "25"
infinity: .inf # бесконечность
neginf: -.inf  # минус бесконечность
    

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

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

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

supported_bools.yaml
        create_key: yes
needs_agent: no
knows_oop: True
likes_emacs: TRUE
uses_cvs: false
    

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

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

multilines_and_one_long_line.yaml
        include_newlines: |
            это действительно
            три отдельные
            строки
fold_newlines: >
            а это на самом деле одна
            длинная строка, которая
            выглядит как три отдельные
    

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

anchor_alias.yaml
        hello: &hi 'hello' # вводится якорь hi
greeting:
  audience: 'world'
  hello: *hi # применение якоря; hello получит строку 'hello'
    

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

anchor_for_block.yaml
        foo:
  bar: &bar # псевдоним для блока
    qux: 'quxqux'
    baz: 'bazbaz'
greeting:
  audience: 'world'
  bar: *bar # *bar развернется в словарь с ключами qux и baz
    

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

merge_key.yaml
        bar: &bar # вводится псевдоним блока
  qux: 'quxqux'
  baz: 'bazbaz'
greeting:
  audience: 'world'
  bar:
    <<: *bar # *bar развернется в словарь qux и baz
    baz: 'notbaz' # НО baz перезапишется новым значением notbaz
    

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

Порог входа высок, но за счет огромного количества плагинов и продуманной концепции редактора эффективность разработки увеличивается многократно. Детали работы с редактором можно узнать, например, из книгиНейла, Дрю «Практическое использование Vim».

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

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

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

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

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

simple_example_marshmallow.py
        from dataclasses import dataclass, field
from typing import List, Optional

import marshmallow_dataclass
import marshmallow.validate


@dataclass
class Building:
    height: float = field(
       metadata = {
           "validate": marshmallow.validate.Range(min=0)
        }
    )
    name: str = field(default="anonymous")


@dataclass
class City:
    name: Optional[str]
    buildings: List[Building] = field(default_factory=list)



# создаем экземпляр схемы
city_schema = marshmallow_dataclass.class_schema(City)()
# city_schema => <City(many=False)>
# загружаем словарь
city = city_schema.load(
    {
        "name": "Paris",
        "buildings": [{
            "name": "Eiffel Tower",
            "height": 324
        }]
    }
)
# city => City(
        name='Paris',
        buildings=[
            Building(
                height=324.0,
                name='Eiffel Tower'
            )
        ]
     )

city.buildings
# => [Building(height=324.0, name='Eiffel Tower')]
city.buildings[0].height # 324.0
    

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

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

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

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

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

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

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

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

  • изменяющийся блок управляющих параметров, или другими словами на динамическую часть инфраструктуры кода, с которой разработчик будет взаимодействовать в будущем,
  • неизменяющийся блок базовой логики – статичную часть кода, которая не требует внесения изменений в программный код напрямую.
<i>Высокоуровневая схема взаимодействия конфигурационного файла с Python-приложением</i>
Высокоуровневая схема взаимодействия конфигурационного файла с Python-приложением

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

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

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

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

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

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

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

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

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

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

struct_repo.md
        .
configs/
  `- gauss_processes_acf.yaml
figure_exapmples/
  `- gauss_exp_acf.pdf
  `- ...
figures/
  `- *.pdf
python_scripts
  `- main.py
  `- helper_funcs_and_class_schema.py
README.md

    

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

gauss_proc_acf.yaml
        ---
# *** MENU ***
# Types of ACF (for `kind_acf`):
# 1: exp type,
# 2: exp-cos type,
# 3: exp-cos-sin type (plus),
# 4: exp-cos-sin type (minus)

# *** MAIN CONTROL PARAMETERS ***
sigma: !!float 2            # standard deviation
w_star: !!float 1.25        # model parameter
w0: !!float 2.5             # model parameter
alpha: !!float 0.05         # smoothing factor for EWMA
window_width: !!int 14     # width of window in MA
delta_t: !!float 0.05       # time step
N: !!int 1000               # number of counts
kind_acf: !!int 1           # ACF type


# *** OTHERS APP SETTINGS ***
visibility:
  ma_show: !!bool true

colors:
  white: !!str &white "#FDFDFD"
  grey: !!str &grey "#52616B"
  blue_purple: !!str &blue_purple "#8785A2"
  terakotta: !!str &terakotta "#E84A5F"
  pearl_night: !!str &pearl_night "#112D4E"
  krayola_green: !!str &krayola_green "#1CAC78"

figure_settings:
  height_main_fig: !!int 7
  width_main_fig: !!int 18
  left_xlim_acf: !!float 0.
  right_xlim_acf: !!float 4.
  left_xlim_pdf: !!float -9
  right_xlim_pdf: !!float 9
...
    

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

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

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

start_app.sh
        $ python python_scripts/main.py \
    --config-path configs/gauss_processes_acf.yaml \
    --output-fig-path figures/your_output_image_name.pdf

    

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

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

Повысить эффективность работы с конфигурационными YAML-файлами можно с помощью различных утилит (например, с помощью легковесной yq), но потоковый редактор sed, как правило, «из коробки» доступен на большинстве операционных систем.

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

sed_example.sh
        $ sed 's/w0: !!float 3.0/w0: !!float 3.15' \
    configs/gauss_processes_acf.yaml \
        > configs/gauss_expacf_w0=3.15.yaml
    

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

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

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

Заключение

Из руководства вы узнали:

  • какие из существующих форматов конфигурационных файлов могут пригодится в роли текстового интерфейса к Python-приложениям;
  • какие существуют библиотеки для работы с конфигурационными файлами и где проходит граница области применимости каждой из них;
  • как использовать выразительный синтаксис YAML-файлов – якоря, псевдонимы, ключи слияния – для упрощения описания модели объектов;
  • в каких сценариях имеет смысл использовать связку «конфигурационный файл + Python-приложение» и в чем сильная сторона такого подхода;
  • как может выглядеть пример использования текстовых интерфейсов в задаче обнаружения выбросов;
  • а также, как «винтажные» инструменты – Vim и sed – могут повысить эффективность редактирования текстовых файлов.

Полезные источники:

Связные материалы с платформы Proglib:

Источник: https://proglib.io/p/konfiguracionnye-fayly-kak-instrument-upravleniya-prilozheniyami-na-python-2021-06-11

11
Июн
2021

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

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

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

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

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

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

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

view_obj.toml
        [[fruits]]
name = "apple"

[fruits.physical]  # подтаблица
color = "red"
shape = "round"

[[fruits.varieties]]  # вложенный массив таблиц
name = "red delicious"

[[fruits.varieties]]
name = "granny smith"

[[fruits]]
name = "banana"

[[fruits.varieties]]
name = "plantain"
    

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

view_obj.json
        {
  "fruits": [
    {
      "name": "apple",
      "physical": {
        "color": "red",
        "shape": "round"
      },
      "varieties": [
        { "name": "red delicious" },
        { "name": "granny smith" }
      ]
    },
    {
      "name": "banana",
      "varieties": [
        { "name": "plantain" }
      ]
    }
  ]
}
    

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

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

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

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

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

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

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

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

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

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

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

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

start_end.yaml
        --- # Начало файла
# Список фруктов
- Apple
- Orange
- Strawberry
- Mango
... # Конец файла
    

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

dict.yaml
        # Запись о сотруднике
martin:
  name: Martin Johnson
  job: Developer
  skill: Elite
    

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

types.yaml
        integer: 25
float: 25.0
exponent: 12.3015e+05
boolean: Yes
string: "25"
infinity: .inf # бесконечность
neginf: -.inf  # минус бесконечность
    

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

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

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

supported_bools.yaml
        create_key: yes
needs_agent: no
knows_oop: True
likes_emacs: TRUE
uses_cvs: false
    

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

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

multilines_and_one_long_line.yaml
        include_newlines: |
            это действительно
            три отдельные
            строки
fold_newlines: >
            а это на самом деле одна
            длинная строка, которая
            выглядит как три отдельные
    

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

anchor_alias.yaml
        hello: &hi 'hello' # вводится якорь hi
greeting:
  audience: 'world'
  hello: *hi # применение якоря; hello получит строку 'hello'
    

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

anchor_for_block.yaml
        foo:
  bar: &bar # псевдоним для блока
    qux: 'quxqux'
    baz: 'bazbaz'
greeting:
  audience: 'world'
  bar: *bar # *bar развернется в словарь с ключами qux и baz
    

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

merge_key.yaml
        bar: &bar # вводится псевдоним блока
  qux: 'quxqux'
  baz: 'bazbaz'
greeting:
  audience: 'world'
  bar:
    <<: *bar # *bar развернется в словарь qux и baz
    baz: 'notbaz' # НО baz перезапишется новым значением notbaz
    

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

Порог входа высок, но за счет огромного количества плагинов и продуманной концепции редактора эффективность разработки увеличивается многократно. Детали работы с редактором можно узнать, например, из книгиНейла, Дрю «Практическое использование Vim».

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

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

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

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

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

simple_example_marshmallow.py
        from dataclasses import dataclass, field
from typing import List, Optional

import marshmallow_dataclass
import marshmallow.validate


@dataclass
class Building:
    height: float = field(
       metadata = {
           "validate": marshmallow.validate.Range(min=0)
        }
    )
    name: str = field(default="anonymous")


@dataclass
class City:
    name: Optional[str]
    buildings: List[Building] = field(default_factory=list)



# создаем экземпляр схемы
city_schema = marshmallow_dataclass.class_schema(City)()
# city_schema => <City(many=False)>
# загружаем словарь
city = city_schema.load(
    {
        "name": "Paris",
        "buildings": [{
            "name": "Eiffel Tower",
            "height": 324
        }]
    }
)
# city => City(
        name='Paris',
        buildings=[
            Building(
                height=324.0,
                name='Eiffel Tower'
            )
        ]
     )

city.buildings
# => [Building(height=324.0, name='Eiffel Tower')]
city.buildings[0].height # 324.0
    

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

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

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

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

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

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

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

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

  • изменяющийся блок управляющих параметров, или другими словами на динамическую часть инфраструктуры кода, с которой разработчик будет взаимодействовать в будущем,
  • неизменяющийся блок базовой логики – статичную часть кода, которая не требует внесения изменений в программный код напрямую.
<i>Высокоуровневая схема взаимодействия конфигурационного файла с Python-приложением</i>
Высокоуровневая схема взаимодействия конфигурационного файла с Python-приложением

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

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

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

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

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

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

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

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

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

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

struct_repo.md
        .
configs/
  `- gauss_processes_acf.yaml
figure_exapmples/
  `- gauss_exp_acf.pdf
  `- ...
figures/
  `- *.pdf
python_scripts
  `- main.py
  `- helper_funcs_and_class_schema.py
README.md

    

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

gauss_proc_acf.yaml
        ---
# *** MENU ***
# Types of ACF (for `kind_acf`):
# 1: exp type,
# 2: exp-cos type,
# 3: exp-cos-sin type (plus),
# 4: exp-cos-sin type (minus)

# *** MAIN CONTROL PARAMETERS ***
sigma: !!float 2            # standard deviation
w_star: !!float 1.25        # model parameter
w0: !!float 2.5             # model parameter
alpha: !!float 0.05         # smoothing factor for EWMA
window_width: !!int 14     # width of window in MA
delta_t: !!float 0.05       # time step
N: !!int 1000               # number of counts
kind_acf: !!int 1           # ACF type


# *** OTHERS APP SETTINGS ***
visibility:
  ma_show: !!bool true

colors:
  white: !!str &white "#FDFDFD"
  grey: !!str &grey "#52616B"
  blue_purple: !!str &blue_purple "#8785A2"
  terakotta: !!str &terakotta "#E84A5F"
  pearl_night: !!str &pearl_night "#112D4E"
  krayola_green: !!str &krayola_green "#1CAC78"

figure_settings:
  height_main_fig: !!int 7
  width_main_fig: !!int 18
  left_xlim_acf: !!float 0.
  right_xlim_acf: !!float 4.
  left_xlim_pdf: !!float -9
  right_xlim_pdf: !!float 9
...
    

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

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

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

start_app.sh
        $ python python_scripts/main.py \
    --config-path configs/gauss_processes_acf.yaml \
    --output-fig-path figures/your_output_image_name.pdf

    

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

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

Повысить эффективность работы с конфигурационными YAML-файлами можно с помощью различных утилит (например, с помощью легковесной yq), но потоковый редактор sed, как правило, «из коробки» доступен на большинстве операционных систем.

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

sed_example.sh
        $ sed 's/w0: !!float 3.0/w0: !!float 3.15' \
    configs/gauss_processes_acf.yaml \
        > configs/gauss_expacf_w0=3.15.yaml
    

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

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

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

Заключение

Из руководства вы узнали:

  • какие из существующих форматов конфигурационных файлов могут пригодится в роли текстового интерфейса к Python-приложениям;
  • какие существуют библиотеки для работы с конфигурационными файлами и где проходит граница области применимости каждой из них;
  • как использовать выразительный синтаксис YAML-файлов – якоря, псевдонимы, ключи слияния – для упрощения описания модели объектов;
  • в каких сценариях имеет смысл использовать связку «конфигурационный файл + Python-приложение» и в чем сильная сторона такого подхода;
  • как может выглядеть пример использования текстовых интерфейсов в задаче обнаружения выбросов;
  • а также, как «винтажные» инструменты – Vim и sed – могут повысить эффективность редактирования текстовых файлов.

Полезные источники:

Связные материалы с платформы Proglib:

Источник: https://proglib.io/p/konfiguracionnye-fayly-kak-instrument-upravleniya-prilozheniyami-na-python-2021-06-11

Тебе может это понравится...

Добавить комментарий