bestconfig
Один объект Config поверх всех мест, где Python-проект прячет настройки
Библиотека конфигурации, которая убирает шаг настройки целиком. Создаёте Config() — и он поднимается от текущего файла к корню проекта, находит по пути файлы YAML, JSON, INI, .env и Python-конфиги, подмешивает переменные окружения и отдаёт результат через один типизированный доступ.
$ pip install bestconfig # app/main.py — config.yaml and .env live two levels upfrom bestconfig import Config config = Config("myconfig.json")# that was the entire configuration step mode = config.logger.mode # 'WARNING'port = config.int('PORT') # 5050pw = config.get('DATABASE_PASSWORD')config.to_dict() -> every source, mergedВесь шаг настройки, взятый из README проекта.
Проблема
Каждый Python-проект переизобретает одни и те же полсотни строк: найти файл конфигурации, решить, перекрывает ли его окружение, разобрать те форматы, которые здесь случились, привести строки к типам и выдать понятную ошибку, когда чего-то не хватает. Работа несложная, и она никогда не бывает одинаковой дважды.
bestconfig убирает этот шаг. Config() поднимается от вызывающего файла к
корню проекта, подбирает каждый узнанный конфиг по дороге, подмешивает окружение
и отдаёт один объект.
Чего я хотел от API
Никаких путей. Файл в app/main.py должен находить config.yaml в корне
репозитория, и ему не надо объяснять, где тот лежит. Поиск идёт вверх, поэтому
перенос модуля не ломает его конфигурацию.
Умолчания под то, как люди на самом деле называют файлы. Все сочетания
config, configuration, settings, setting и conf с .json, .yaml,
.ini, .env и .cfg, плюс конкретные имена .env, env_file и config.py.
Большинство проектов не передаёт ни одного аргумента.
Отсутствующее значение ведёт себя по-разному в зависимости от того, как вы
спросили. config['key'] бросает, config.get('key') возвращает None,
config.get('key', default) возвращает умолчание, а
config.assert_contains('key') падает сразу на старте. Выбор способа доступа —
это выбор сценария отказа, в этом и смысл.
Типы разбираются один раз, на границе. YAML и JSON и так несут типы; строки
из .env затем интерпретируются как литералы Python, поэтому LIST=[1, 2]
приезжает списком. Когда такое угадывание не нужно, get_raw его выключает, а
config.int(...) и его соседи приводят типы явно, возвращая None, а не бросая
исключение, если значение не приводится.
Изменение в рантайме — полноценный случай. set, insert (словарь или
ещё один файл) и update_from_locals существуют потому, что конфигурация часто
выводится — собрать полное имя, склеить URL, — и такие производные значения
заслуживают лежать там же, где прочитанные с диска.
Чем это кончилось
Девять релизов на PyPI, сейчас 1.3.6, и самая залайканная библиотека из написанных мной. Это тот проект, который научил меня, что настоящая поверхность API — это поведение при отказе, а не счастливый путь: почти каждая задача и каждое изменение были про то, что происходит, когда значения нет.
- .yaml / .yml / .json
- Разбирается с родными типами — `port: 5050` приходит как int.
- .ini / .cfg
- Стандартные конфиги с секциями.
- .py
- Python-модули конфигурации; пропускаются, если сами создают Config(), чтобы не было рекурсии.
- .env / env_file
- Файлы KEY=VALUE, включая значения, разбираемые как литералы Python.
- Environment variables
- Уже существующие и только что заданные, слитые с файловыми источниками.
- dict
- Обычные словари, вставленные в рантайме через `config.insert(...)`.
Умолчания — это те имена, которыми люди и так пользуются, поэтому большинству проектов аргументы вообще не нужны.
- config.name
- Доступ через атрибут для обычного случая.
- config['name']
- Доступ как к словарю — бросает KeyError, если ключа нет.
- config.get('a.b.c', default)
- Путь с точками любой глубины, с умолчанием вместо исключения.
- config.int / float / str / list / dict
- Типизированные геттеры, возвращающие None, если приведение не удалось.
- config.get_raw('key')
- Полностью отключить интерпретацию типов.
- config.assert_contains('key')
- Громко упасть на старте, если обязательная настройка так и не загрузилась.
Одно значение, несколько способов его спросить — выбираются по тому, что должно случиться, если его нет.