Статьи

Файлы конфигурации, переменные среды и параметры командной строки

У нас есть три основных уровня конфигурации для приложений. Внутри каждого уровня у нас есть подуровни, что еще больше усложняет задачу. Организация слоев тоже немного взаимозаменяема. Принятие правильных решений может быть довольно сложным, поскольку существует множество вариаций на тему «конфигурации». Настольное графическое приложение с файлом настроек предъявляет очень большие требования к более крупным и сложным приложениям.

Наиболее динамичные параметры конфигурации — это аргументы командной строки . На этом уровне конфигурации у нас есть два подуровня значений по умолчанию и пользовательских переопределений этих значений по умолчанию. Откуда берутся настройки по умолчанию? Они могут быть подключены, но чаще всего они поступают из переменных среды или файлов параметров или из обоих.

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

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

Обратите внимание, что мы можем сделать это в Linux:

http://slott-softwarearchitect.blogspot.com/2015/03/configuration-files-environment.html

Это установит переменную окружения как часть выполнения команды.

Эти файлы конфигурации также могут иметь русов. У нас может быть глобальный файл конфигурации в / etc / our-app. Мы можем искать ~ / .our-app-rc в качестве универсальной конфигурации пользователя. Мы также можем найти our-app.config в текущем рабочем каталоге как окончательный набор переопределений, которые будут использоваться для текущего вызова.

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

Проблемы с представительством

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

Некоторые люди безнадежно влюблены в файлы .ini в стиле Windows. Модуль configparser проанализирует их. Я безнадежно называю это любовью, потому что синтаксис довольно ограничен. Посмотрите на модуль logging.config, чтобы увидеть, насколько сложен формат файла .ini для нетривиальных случаев.

Некоторые люди любят файлы свойств в стиле Java. Их преимущество в том, что в Python их действительно легко разобрать. Действительно, сканирование файла свойств — отличное упражнение в программировании на Python с функциональным стилем.
Я тоже не полностью продан этим, потому что они не очень хорошо справляются с нетривиальными делами.

Использование JSON или YAML для свойств имеет некоторые реальные преимущества. В этих двух обозначениях есть много изощренности. В то время как JSON имеет первоклассную поддержку, YAML требуется дополнительный модуль.

Мы также можем использовать Python в качестве языка для конфигурации. Для хороших примеров этого посмотрите файл настроек проекта Django . Использование Python имеет множество преимуществ. Единственным возможным недостатком является потраченное время на споры с людьми, которые называют это «уязвимостью безопасности».

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

Мой текущий любимый 

Мой любимый способ обработки конфигурации — это определение некоторого класса конфигурации и использование объекта класса во всем приложении. Из-за обработки импорта в Python один экземпляр определения класса легко гарантировать.

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

class Defaults:
    mongo_uri = "mongodb://localhost:27017" 
    some_param = "xyz" 

class Dev(Defaults):
    mongo_uri = "mongodb://sandbox:27017"

class QA(Defaults):
    mongo_uri = "mongodb://username:password@qa02:27017/?authMechanism=PLAIN&authSource=$external"

Да. Пароль виден. Если мы хотим возиться с более высокими уровнями секретности в файлах конфигурации, мы можем использовать PyCrypto и генератор ключей, чтобы использовать зашифрованный пароль, который вводится в URI. Это тема для другого поста. Люди, которые могут редактировать файлы конфигурации, часто знают пароли. От кого мы пытаемся что-то скрыть?

Как выбрать активную конфигурацию для использования из доступных вариантов в этом файле? У нас есть несколько способов.

  • Добавьте строку в модуль конфигурации. Например, Config = QA назовет выбранную среду. Мы должны изменить файл конфигурации, когда наш код проходит через среды от разработки до производства. Мы можем использовать импорт конфигурации из Config для получения правильной конфигурации во всех других модулях приложения.
  • Положитесь на переменную среды, чтобы указать, какую конфигурацию использовать. В корпоративных контекстах переменная среды часто доступна. Мы можем импортировать os и использовать Config = globals () [os.environ [‘OURAPP_ENVIRONMENT’]], чтобы выбрать конфигурацию на основе переменной среды. 
  • В некоторых местах мы можем полагаться на имя хоста, чтобы выбрать конфигурацию. Мы можем использовать os.uname () [1], чтобы получить имя сервера. Мы можем добавить отображение из имени сервера в конфигурацию и использовать это: Config = host_map (os.uname () [1], Defaults).
  • Используйте параметры командной строки, такие как «—env = QA». Это может быть немного сложнее, чем описанные выше методы, но, похоже, в конечном итоге хорошо сработает.

Аргументы командной строки для выбора конкретной конфигурации

To select a configuration using command-line arguments, we must decompose configuration into two parts. The configuration alternatives shown above are placed in a config_params.py module. The config.py module that’s used directly by the application will import the config_params.py module, parse the command-line options, and finally pick a configuration. This module can create the required module global, Config. Since it will only execute once, we can import it freely.

The config module will use argparse to create an object named options with the command-line options. We can then do this little dance:

import argparse
import sys
import config_params

parser= argparse.ArgumentParser()
parser.add_argument("--env", default="DEV")
options= parser.parse_args()

Config = getattr(config_params, options.env)
Config.options= options

Кажется, это работает достаточно хорошо. Мы можем гибко настроить config_params.py. Мы можем выбрать конфигурацию с помощью простой опции командной строки.

Если мы хотим элегантно сбросить конфигурацию, у нас есть небольшая борьба. Каждый класс в иерархии вводит имена: это небольшая работа, чтобы пройтись по решетке __class __.__ mro__, чтобы обнаружить все доступные имена и значения, которые унаследованы и переопределены от родителей.

Мы могли бы сделать что-то вроде этого, чтобы сгладить результирующие значения:

Base= getattr(config_params, options.env)
class Config(Base):
    def __repr__(self):
        names= {}
        for cls in reversed(self.__class__.__mro__):
            cls_names= dict((nm, (cls.__name__, val)) 
                for nm,val in cls.__dict__.items() 
                    if nm[0] != "_")
            names.update( cls_names )
        return ", ".join( "{0}.{1}={2}".format(class_val[0], nm, class_val[1]) 
            for nm,class_val in names.items() )

Это не ясно, это требуется. Но это круто для отладки.