Схема конфигурации с использованием Pydantic

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

Допустим, у нас есть интерфейс с именем MLModel, в котором есть простой метод predict. MLModel — это абстракция, имеющая две реализации: MockedModel и RestModel. Фабрика позаботится о создании экземпляра с помощью конфигурации. Он сообщает о конкретной реализации, которую мы хотим использовать, MockedModel или RestModel, а также содержит их параметры. Таким образом, нашей системе все равно, какой MLModel мы используем, она просто знает, как получить объект и вызвать метод predict. Мы можем изменить поведение приложения, изменив конфигурацию, не изменяя код. Пример кода ниже.

Теперь мы можем спросить себя, как мы можем хранить конфигурацию модели внутри Python? Достаточно ли словарей или можно использовать что-то более глубокое?

Словари Python

Первое, что приходит нам в голову, — это использование словарей в качестве средства передачи конфигурации нашей фабрике. А почему бы не? Он гибкий, и вы можете положить туда все, что захотите. Это такой простой способ определить что-либо. У нас может быть ключ, например. тип, который указывает класс стратегии (конкретный), который мы хотим использовать, и, кроме того, у нас может быть внутренний словарь, содержащий конфигурацию для конкретного класса.

'ml_model': {
    'model_type': 'rest',
    'http_endpoint': 'http://localhost:9050/model',
    'timeout': 10
}

Аккуратный. Для такого простого примера это работает как шарм. Однако это имеет свою цену. Нет ни схемы, ни подсказок типа, ни валидации. Легко сделать опечатку, которая может преследовать вас часами. Давайте посмотрим на пример.

'ml_model': {
    'model_type': 'rest',
    'timeout': '20'
}

Здесь мы забыли параметр http, верно? Вы заметили, что на этот раз параметр timeoutis является строкой, но должен ли он быть целым числом? Что приемлемо для типа? Какой диапазон подходит для значения, допустим ли -100? Без схемы и валидации в одном месте нам либо нужно проверять документацию, не обновил ли ее кто-то, либо нам нужно погрузиться в код, чтобы проверить его самостоятельно. В одном из предыдущих блогов я писал о важности правильной документации. Конечно, это слишком простой пример, чтобы показать, почему наличие схемы имеет значение, но я надеюсь, что вы поняли суть.

Посмотрите на наивный пример. dict_factory — это простейший if-else, который проверяет model_type и, в зависимости от типа, создает правильный экземпляр MLModel.. Нам просто нужно отправить правильный словарь, и мы получим модель, скрытую за интерфейсом MLModel. Увы, если мы допустим ошибку в конфигурации и у нас не будет валидации, это уменьшит существование где-то во время выполнения.

Мы можем реализовать проверку самостоятельно там, где она нам нужна, но слишком легко сделать ошибку, и вы загромождаете свой код. К счастью, есть много способов решить эту проблему. В этом блоге мы рассмотрим Pydantic.

Пидантик

Pydantic — это средство проверки данных и управления настройками, использующее аннотации-подсказки. Поскольку он использует подсказки типов, вам не нужно учиться определять схему, поскольку вы уже это знаете. Он имеет хорошую интеграцию классов данных, и вы можете определить свои собственные типы данных и правила де/сериализации. Есть некоторые тесты, говорящие о том, что Pydantic быстр, покупайте честно, я сам не сравнивал скорость с другими альтернативами. Однако одно из самых мощных применений Pydantic — управление настройками и проверка в реальном времени. Вкратце, он поддерживает комбинированное использование нескольких источников, таких как переменные среды, секретные файлы и другие. Вы можете найти дополнительную информацию и функции https://pydantic-docs.helpmanual.io/usage/settings/. А пока давайте сосредоточимся на нашем примере использования полиморфной структуры настроек и на том, как мы можем использовать здесь Pydantic.

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

Хитрость заключается в подсказке типа Union. Здесь мы можем сказать, что настройки для ml_model могут быть экземпляром MockedModelSettings или RestModelSettings. Кроме того, мы можем указать Pydantic искать определенное поле, которое может служить дискриминатором между конкретными настройками. Вот это model_type. Тип Literal представляет значение, которое он ожидает от словаря (наша конфигурация). Дискриминатор также улучшит производительность, поскольку Pydantic будет проверять и проверять только совпавшую модель, а не использовать все члены объединения. Также есть поддержка вложенного дискриминатора, но это будет слишком глубоко для этого блога :)

Теперь мы лучше подготовлены для нашей наивной фабрики. Как только мы нажмем parse_obj, Pydantic переведет словарь в допустимый объект AppSettings, и он автоматически подберет для нас тип ml_model на основе model_type..

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

Сноска

В этом сообщении блога мы решили конкретный вариант использования схемы параметров моделирования с помощью библиотеки сериализации Pydantic. Мы использовали подсказку типа Union, чтобы упростить себе жизнь при моделировании полиморфных настроек. Можем ли мы просто использовать словари Python? Конечно, могли бы, а в самых простых случаях, наверное, и должны были бы. Тем не менее, нам нужно знать, что это подвержено ошибкам, и мы потеряем некоторые хорошие функции подсказки типа, которые помогут нам в дальнейшем.