Перейти к содержимому
Борис Новичков
РезюмеGitHub ↗
ENRU
Все работы

bestconfig

Один объект Config поверх всех мест, где Python-проект прячет настройки

Библиотека конфигурации, которая убирает шаг настройки целиком. Создаёте Config() — и он поднимается от текущего файла к корню проекта, находит по пути файлы YAML, JSON, INI, .env и Python-конфиги, подмешивает переменные окружения и отдаёт результат через один типизированный доступ.

Выпущен
Роль
Автор
Период
март 2021 — апр. 2023
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')
Громко упасть на старте, если обязательная настройка так и не загрузилась.

Одно значение, несколько способов его спросить — выбираются по тому, что должно случиться, если его нет.