Создать конфигурационный файл python

Python: конфигурация проекта без боли

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

В статье речь пойдет только о локальных способах хранения настроек, здесь не разбираются случаи загрузки из сети.

После создания проекта рано или поздно возникает вопрос: куда записывать номер версии, где хранить токены, пароли, настройки, каким форматом файлов конфигурации воспользоваться: .json , .yaml , .env, .cfg , .ini или просто создать config.py и записывать туда переменные?

Для каждого из перечисленных вариантов есть библиотека на python, приведу примеры самых популярных форматов.

ENV

Переменные окружения могут передаваться программе напрямую командным интерпретатором: они записаны в .bashrc , выполнена команда export , или просто указаны в интерфейсе хостинга. Для того, чтобы их явно указать, удобно использовать .env файлы в директории проекта. Если прописать .env в . gitignore можно сложить туда все ключи и не боятся их опубликовать, в то время как остальные настройки будут в другом месте для использования в продакшн окружении

VERSION=2.5.11 PG_USER=postgres PG_DATABASE=my_project

Библиотека dotenv отлично справляется с задачей подгрузки таких файлов. Все переменные сразу попадают в os.environ

from dotenv import load_dotenv import os load_dotenv() print(os.getenv('VERSION'))

YAML

В последнее время стал популярен формат .yml , как один из самых приятных для чтения человеком

version: 2.5.11 logger: level: INFO format: '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
from yaml import load with open('config.yml', 'r') as f: data = load(f) print(data['version'])

JSON

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

import json with open('config.json', 'r') as f: data = json.load(f) print(data['version'])

PY

В файле с расширением .py объявляем все необходимые переменные. У данного подхода есть преимощество — можно некоторые значения вычислять на ходу: инкриментировать номер сборки, менять хост в зависимости от режима: DEBUG или RELEASE и тд.

VERSION = '2.5.11' PG_DATABASE = 'postgres' PG_PORT = 5432

Осталось только импортировать этот файл там, где он нужен

from config import VERSION print(VERSION)

INI

[DEFAULT] version = '2.5.11' [postgres] user = "postgres" database = "my-project"
import configparser config = configparser.ConfigParser() config.read('settings.ini') print(config["version"])

Все просто, разве нет?

Самое интересное начинается, когда в проекте появляется несколько источников конфигов. Например статичные настройки лежат в config.yml , токены и пароли в переменных окружения и .env , некоторые записаны в файле env_file , используемом докером, часть значений нужно переопределить из кода, так как они заранее не известны, часть извлечь из аргументов программы.

Читайте также:  Python date format rfc3339

Это ведет за собой менее красивый код, который обязан учитывать особенности хранения, приходится задумаваться, откуда мы берем очередное значения настроек, появляется несогласованность, ведь в одних местам мы обращаеся к os.environ , в других к глобальному словарю, в третьих, к сторонней библиотеке.

BestConfig

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

Поставил целью уменьшить количество кода чтения конфигов почти до нуля, но в тоже время оставить гибкость и возможность подкрутки под более специфичные задачи. Итак, давайе посмотрим, как импортировать например config.yml

from bestconfig import Config config = Config() print(config['version'])

И да, это все, что нужно сделать. Глобальный объект config уже будет содержать все необходимое. А теперь по порядку.

Что происходит при создании Config() :

  1. Запускается поиск файлов, похожих на конфиги: любые комбинации имени conf, config, configuration, setting, settings с расширениями yml, yaml, json, ini, cfg, env в текущей директории и в папках выше, вплодь до корня проекта. Также в хранилище добавляются переменные окружения
  2. Исходя из формата файла производится импорт содержимого
  3. Создается класс ConfigProvider , предоставляющий универсальный доступ ко всему содержимому
print(isinstance(Config(), ConfigProvider)) # True

Посмотрим на примерах, в чем же «универсальность» объекта config

Обращение по ключу

print(config.get('version')) # При отсутствии возвращает None print(config['verions']) # При отсутствии бросает KeyError print(config.version) # Тоже может бросить KeyError # Вложенные структуры print(config.postgres.port) print(config['postgres.port']) print(config.get('postgres.port')) # Очевидно, глубина вложенности не ограничена

Иногда мы хотим быть уверены, что значение принадлежит определенному типу

type(config.int('limit')) # int type(config.dict('logger')) # dict type(config.list('admins')) # list # И тд: float, str

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

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

config.assert_contains('key') # Что эквивалентно assert config.get('key')

Класс ConfigProvider наследуется от dict , поэтому его можно спокойно передавать в сторонние библиотеки.

import logging.config from config import config logging.config.dictConfig(config['logging']) print(config) # Выведется как словарь

Модификация

from argparse import ArgumentParser from config import config if __name__ == '__main__': # Парсим аргументы командной строки parser = ArgumentParser() parser.add_argument('log_level') args = parser.parse_args() # Обновляем общий конфиг config.insert()

Помимо словаря метод insert принимает названия файлов (с абсолютным или относильным путем) любого из поддерживаемых расширений

config.insert('logging.json') # Кроме обычных методов dict-а есть set config.set('key', 'value')

Выше я приводил пример, в котором конфиг переменные объявляются прямо в .py файле. Если в проекте уже используется такой вариант, необязательно всё менять, на этот случай есть метод update_from_locals

from config import config VERSION = '2.5.11' LOGGING_LEVEL = 'INFO' config.update_from_locals()

В питоне есть встроенная функци locals() , она возвращает словарь локальных переменных, имеено ее и использует данный метод. Там, конечно, рассматриваются добавляемые объекты и лишние отсеиваются (подробнее в документации).

Читайте также:  Пороговая функция активации python

Кастомизация

По умолчанию Config() делает довольно много всего, с целью быть универсальным, но содежит ряд аргументов, регулирующих это поведение.

# Исключить из списка по умолчанию некоторые файлы Config(exclude=['server/setting.json']) # Указать свои пути Config('my-config.json', '../other-config.yml', 'app/settings.cfg') # Импортировать только config.yml и кидать исключение при его отсутствии # Игнорировать весь список по умолчанию Config('config.yml', exclude_default=True, raise_on_absent=True)

Установка

Best practices

Обобщу процесс взаимодействия с конфигами с использованием библиотеки bestconfig.

  1. Статичные настройки, не содержащие ключей лежат в файле config.yml в корне проекта
  2. Переменные окружения и ключи задаются в .env и игнорируются системой контроля версий
  3. Файл config.py содержит создает класс Config и выполняет другие настройки (например логгирование, версия проекта, обработка параметров запуска и тд)
  4. В других местах проекта делаем from config import config и используемый нужные данные удобным образом

Резюме

Библиотека создана чтобы максимально упростить распространенную задачу по обращению к файлам настроки, ее основные фичи и особенности:

  • Поддержка множества форматов файлов
  • Интерфейс доступа к данным «на любой вкус» (по ключу, через точку, через get и тд)
  • Маленький вес (примерно 15 кб)
  • Большое тестовое покрытие
  • Чистый, типизированный, расширяемый python код на модульной архитектуре
  • Открытость к изменениям и дополнениям (pull request-ы приветствуются)

Есть и недостатки:

  • У проекта появляются лишние зависимости. Например вы не используете yaml, однако библиотека подтягивает pyyaml
  • Захват лишнего. Вы можете написать Config() и даже не обратить внимание, сколько всего попало в config, одни переменные окружения чего стоят — их может быть очень много. И вывод print(config) становится абсолютно не читаемым

Теперь, при создании нового sandbox проекта или усложении текущего еще одним видом конфигурационных файлов, вы можете выбрать из своего арсенала еще один инструмент!

Источник

ConfigParser в Python — как его использовать?

Файлы конфигурации используются как пользователями, так и программистами. Как правило, их используют для хранения настроек вашего приложения, или операционной системы. Библиотека в ядре Python включает в себя модуль, под названием configparser, который вы можете использовать для создания и работы с файлами конфигурации. Что ж, давайте выделим несколько минут на то, чтобы узнать, как это работает.

Совместимость с Python 3

Из за нововведений в стандарте PEP 8 модуль ConfigParser в Python 3 был переименован в configparser. Возможные ошибки:
ImportError: no module named ConfigParser

Решение проблемы совместимости

Создание файла

Создание файла config при помощи configparser невероятно просто. Давайте напишем небольшой код, чтобы посмотреть, как это работает:

Данный код создает файл config с одной секцией, под названием Settings, которая будет содержать наши опции: font, font_size, font_style и font_info. Обратите внимание на то, что в Python 3 нам нужно указать, что мы пишем файл в режиме write-only, или “w”. А в Python 2.7, мы использовали “wb” для написания в бинарном режиме.

Читайте также:  Center jpg image html

Как читать, обновлять и удалять опции

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

Этот код сначала проверяет, существует ли файл config в принципе. Если его нет, то он использует созданную нами ранее функцию createConfig, чтобы создать файл. Далее мы создаем объект ConfigParser и указываем путь к файлу config для чтения. Чтобы прочесть опцию в вашем config файле, мы вызываем метод нашего объекта ConfigParser, указываем ему наименование секции и опции.

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

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

Это вернет значение параметра. Если вы хотите изменить значение опции, вам нужно использовать метод set, в котором вы указываете название секции, опции, и новое значение. Наконец, мы можем использовать метод remove_option, чтобы удалить опцию. В нашем примере мы изменили значение font_size, и задали ему размер 12, затем мы удалили опцию font_style. После этого мы записали наши изменения на диск. Этот пример на на столько хорош, давайте упростим наш код. Для этого мы разделим наш код на на несколько функции:

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

Как использовать интерполяцию

Модуль configparser также подразумевает возможность интерполяции, что значит, что вы можете использовать существующие опции, для создания другой опции. Мы на самом деле это делали с опцией font_info, чьи параметры основаны на опциях font и font_size. Мы можем изменить интерполированное значение при помощи словаря Python. Давайте уделим несколько минут, и взглянем на оба случая.

Источник

Оцените статью