воскресенье, 31 марта 2013 г.

Обработка конфигурации Flask

Перевод статьи: Configuration Handling

Новинка версии 0.3

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

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

Вне зависимости от того, каким образом загружена конфигурация, существует объект конфигурации, содержащий загруженные параметры: атрибут config объекта Flask. Это место, где Flask содержит свои настройки, а также то место, куда расширения могут поместить собственные настройки. Но здесь можно размещать и конфигурацию вашего приложения.

Основы конфигурации

config на самом деле является подклассом словаря и может изменяться точно так же, как и любой словарь:
app = Flask(__name__)
app.config['DEBUG'] = True
Некоторые параметры конфигурации передаются в объект Flask, которые тоже можно читать и писать:
app.debug = True
Для обновления нескольких ключей за раз можно воспользоваться методом словаря update():
app.config.update(
    DEBUG=True,
    SECRET_KEY='...'
)
Встроенные параметры конфигурации

Сам Flask использует следующие параметры конфигурации:
DEBUG Включить/выключить режим отладки.
TESTING Включить/выключить режим тестирования.
PROPAGATE_EXCEPTIONSЯвное включение или отключение исключений. Если не задано или явным образом задано значение None, то подразумевается истина, если истиной является TESTING или DEBUG.
PRESERVE_CONTEXT_ON_EXCEPTIONПо умолчанию в режиме отладки при возникновении исключения контекст запроса не извлекается из стека, позволяя отладчику анализировать данные. Такое поведение можно отключить с помощью этого параметра. Также можно воспользоваться этим параметром для его принудительного включения, если это может помочь в отладке приложений в эксплуатации (однако, это не рекомендуется).
SECRET_KEYСекретный ключ.
SESSION_COOKIE_NAMEИмя переменной (cookie) браузера для хранения сеанса.
SESSION_COOKIE_DOMAINДомен переменной браузера, используемой для хранения сеанса. Если не задан, переменная браузера будет действительной для всех поддоменов SERVER_NAME.
SESSION_COOKIE_PATHПуть к переменной браузера, используемой для хранения сеанса. Если не задан, переменная браузера будет действительной для всех APPLICATION_ROOT, если не задано значение '/'.
SESSION_COOKIE_HTTPONLYУказывает, должен ли у переменной браузера устанавливаться флаг httponly (что защищает переменную от доступа со стороны скриптов, работающих внутри браузера - прим. перев.). По умолчанию - истина.
SESSION_COOKIE_SECUREУказывает, должен ли у переменной браузера устанавливаться флаг secure (что позволяет передавать переменную только по защищённому протоколу HTTPS - прим. перев.). По умолчанию - ложь.
PERMANENT_SESSION_LIFETIME Непрерывное время жизни сеанса, как объект datetime.timedelta. Начиная с Flask 0.8 этот параметр может быть задан в виде целого числа с количеством секунд.
USE_X_SENDFILE Включить/отключить x-sendfile. (При использовании этой возможности представление может вернуть специально сформированный ответ со ссылкой на статический файл. Получив такой ответ от представления, веб-сервер отдаёт клиенту вместо ответа представления сам статический файл, найдя его по ссылке в локальной файловой системе. Это позволяет перенести нагрузку по отдаче больших файлов на веб-сервер, если перед отдачей файла представление должно решить, можно ли отдавать этот файл клиенту и какой именно файл нужно отдать по этой ссылке - прим. перев.)
LOGGER_NAMEИмя средства журналирования.
SERVER_NAMEИмя и номер порта сервера. Необходимо для поддержки поддоменов (например: 'myapp.dev:5000') Отметим, что localhost не поддерживает поддомены, поэтому установка параметра в значение "localhost" не поможет. Настройка SERVER_NAME также по умолчанию включает генерацию URL'ов без контекста запроса, но с контекстом приложения.
APPLICATION_ROOTЕсли приложение не занимает целый домен или поддомен, с помощью этого параметра можно задать путь к настроенному приложению. Значение этого параметра используется в качестве пути к переменной браузера для хранения сеанса. Если используются домены, значением этого параметра должно быть None.
MAX_CONTENT_LENGTHЕсли задать значение в байтах, Flask будет отклонять входящие запросы, объём содержимого которых больше этого значения, возвращая код статуса 413.
SEND_FILE_MAX_AGE_DEFAULTПо умолчанию задаёт время кэширования файла для использования совместно с send_static_file() (обработчик статических файлов по умолчанию) и send_file(), в секундах. Заменить это значение для каждого файла индивидуально можно с помощью обработчика get_send_file_max_age() Flask или Blueprint. По умолчанию - 43200 (12 часов).
TRAP_HTTP_EXCEPTIONSЕсли истина, Flask не выполняет обработчиков ошибок исключений HTTP, но вместо этого трактует исключение как любое другое и передаёт исключение выше. Этот параметр полезен для отладки сложных случаев, когда нужно найти, где именно произошло исключение HTTP.
TRAP_BAD_REQUEST_ERRORSВнутренние структуры данных Werkzeug, работающие с данными запроса порождают ошибки с особым ключом, также являющимся исключением запроса. Также, многие операции могут неявно приводить к исключениям BadRequest в случае ошибок целостности. Поскольку для отладки важно знать, где именно произошла ошибка, этот флаг может использоваться для отладки в подобных случаях. Если этот параметр истинен, произойдёт обычная выдача результата трассировки.
PREFERRED_URL_SCHEMEСхема, которую нужно использовать для генерации URL'ов, если она не указана явно. По умолчанию - http.

Подробнее о SERVER_NAME

SERVER_NAME - это параметр, который используется для поддержки поддоменов. Flask не может догадаться о том, какая часть доменного имени является поддоменом, не зная имя сервера. Этот же параметр используется для настройки переменной браузера, в которой хранится сеанс.

Помните, что не только Flask не может узнать поддомен, ваш веб-браузер тоже не может. Большинство соверменных браузеров не разрешают междоменные переменные браузера, если в имени сервера нет точек. Поэтому если имя сервера 'localhost', вы не сможете задать переменную браузера для 'localhost' и каждого из его поддоменов. В этом случае выберите другое имя сервера, например 'myapplication.local' и добавьте это имя и поддомены, которые вы хотите использовать, в файл hosts или настройте локальный bind.

Новинка версии 0.4: LOGGER_NAME

Новинка версии 0.5: SERVER_NAME

Новинка версии 0.6: MAX_CONTENT_LENGTH

Новинки версии 0.7: PROPAGATE_EXCEPTIONS, PRESERVE_CONTEXT_ON_EXCEPTION

Новинки версии 0.8: TRAP_BAD_REQUEST_ERRORS, TRAP_HTTP_EXCEPTIONS, APPLICATION_ROOT, SESSION_COOKIE_DOMAIN, SESSION_COOKIE_PATH, SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SECURE

Новинка версии 0.9: PREFERRED_URL_SCHEME

Задание конфигурации с помощью файлов

Конфигурация становится более удобной, если разместить её в отдельном файле. Лучше, если он находится за пределами пакета с приложением. Это позволяет создавать пакеты и распространять приложения с помощью различных инструментов обработки пакетов (Развёртывание приложений при помощи distribute) и впоследствии - изменять конфигурацию.

Далее показан обычный пример:
app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
Сначала грузится конфигурация из модуля yourapplication.default_settings, а затем её значения заменяет содержимое файла, указанного в переменной окружения YOURAPPLICATION_SETTINGS. Эта переменная окружения может быть задана в Linux или OS X при помощи команды export оболочки перед запуском сервера:
$ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg
$ python run-app.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader...
В системах Windows воспользуйтесь встроенной командой set:
>set YOURAPPLICATION_SETTINGS=\path\to\settings.cfg
Файлы конфигурации являются обычными файлами Python. В объект конфигурации сохраняются только переменные с именами в верхнем регистре, так что убедитесь в том, что имена ваших параметров заданы в верхнем регистре.

Вот пример файла конфигурации:
# Пример конфигурации
DEBUG = False
SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
Убедитесь в том, что файл загружается как можно раньше, чтобы расширения могли получить доступ к собственным настройкам при запуске. Существуют другие методы загрузки объекта конфигурации из отдельных файлов. За более полной информацией обратитесь к документации объекта Config.

Лучшие способы задания конфигурации

Недостаток описанного выше подхода заключается в усложнении тестирования. Нет стопроцентного способа решения этой проблемы, но вот несколько рекомендаций опытных пользователей:
  1. Создайте ваше приложение внутри функции и зарегистрируйте в ней blueprint'ы. Таким образом вы можете создать несколько экземпляров вашего приложения с разными конфигурациями, что значительно упростит модульное тестирование. Вы можете воспользоваться функцией, чтобы передать в неё необходимую конфигурацию.
  2. Не пишите код, которому требуется конфигурация при импорте. Если ограничиться чтением конфигурации по запросу, возможно будет переконфигурировать объект позже.
Режим разработки и режим эксплуатации

Большинству приложений нужно более одной конфигурации. По меньшей мере нужна отдельная конфигурация для рабочего сервера и ещё одна для разработки. Простейший способ управления ими - это создать конфигурацию по умолчанию, которая загружается всегда и которую можно поместить в систему управления версиями, и частичные конфигурации, которые заменяют необходимые значения следующим образом:
app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
Теперь просто создайте отдельный файл config.py, выполните команду export YOURAPPLICATION_SETTINGS=/path/to/config.py и готово. Однако, существуют альтернативные способы. Например, можно воспользоваться импортом и подклассами.

В мире Django распространён следующий способ: сделать явный импорт конфигурации, добавив строку from yourapplication.default_settings import * в начале файла, а затем заменить значения вручную. Можно сделать также, затем взять из переменной окружения вида YOURAPPLICATION_MODE необходимый режим - production, development и т.п., а затем импортировать заранее определённые файлы, основываясь на этом значении.

Другой любопытный способ - воспользоваться классами и наследованием конфигурации:
class Config(object):
    DEBUG = False
    TESTING = False
    DATABASE_URI = 'sqlite://:memory:'

class ProductionConfig(Config):
    DATABASE_URI = 'mysql://user@localhost/foo'

class DevelopmentConfig(Config):
    DEBUG = True

class TestingConfig(Config):
    TESTING = True
Для включения такой конфигурации, вам просто нужно вызвать from_object():
app.config.from_object('configmodule.ProductionConfig')
Есть много разных способов, которые можно выбрать для управления файлами конфигурации. Вот список хороших советов:
  • Храните файл конфигурации по умолчанию в системе управления версиями. Заполните объект конфигурации значениями по умолчанию или импортируйте его в ваших собственных файлах конфигурации перед тем, как заменить значения.
  • Воспользуйтесь переменной окружения для переключения между конфигурациями. Это можно сделать вне интерпретатора Python и это позволит упростить разработку и развёртывание, потому что вы можете быстро и легко переключаться между разными конфигурациями, совсем не прикасаясь к коду. Если вы часто работаете над разными проектами, можно создать собственный скрипт, который будет определять текущий каталог проекта, активировать virtualenv и экспортировать конфигурацию режима разработки.
  • Используйте инструмент fabric для внесения изменений на сервер эксплуатации и раздельных конфигураций на серверы эксплуатации. За более подробным описанием того, как это сделать, обратитесь к главе Развёртывание приложений при помощи fabric.
Каталоги экземпляров

Новинка версии 0.8.

Во Flask 0.8 появились каталоги экземпляров. Flask долгое время позволял ссылаться на пути относительно каталога приложения (с помощью Flask.root_path). Поэтому многие разработчики хранили конфигурацию рядом с приложением. К несчастью, это возможно только если приложение не находится внутри пакета, так как в таком случае root_path указывает внутрь пакета.

Во Flask 0.8 был введён новый атрибут: Flask.instance_path. Он вводит новое понятие, которое называется каталогом экземпляра. Каталог экземпляра задуман как каталог, не управляемый системой контроля версий и относящийся к развёрнутому приложению. Это подходящее место для того, чтобы поместить в него файлы, изменяемые в процессе работы или файлы конфигурации.

Можно явным образом указать путь к каталогу экземпляра при создании приложения Flask или можно разрешить Flask'у самому выбрать каталог экземпляра. Чтобы задать его явным образом, воспользуйтесь параметром instance_path:
app = Flask(__name__, instance_path='/path/to/instance/folder')
Помните, что если этот путь указан, он должен быть абсолютным.

Если параметр instance_path не указан, по умолчанию используются следующие места:
  • Не установленный модуль:
    /myapp.py
    /instance
  • Не установленный пакет:
    /myapp
        /__init__.py
    /instance
  • Установленный модуль или пакет:
    $PREFIX/lib/python2.X/site-packages/myapp
    $PREFIX/var/myapp-instance
    $PREFIX - это префикс, с которым установлен Python. Это может быть каталог /usr или путь в каталоге с виртуальным окружением, созданном virtualenv. Можно вывести на экран значение sys.prefix, чтобы увидеть его действительное значение.
Как только стало возможным загружать объект конфигурации из файлов с относительными именами, мы добавили возможность загружать файлы с именами относительно каталога экземпляра. При помощи переключателя instance_relative_config в конструкторе приложения можно указать, должны ли интерпретироваться относительные пути файлов относительно корня приложения (по умолчанию) или относительно каталога экземпляра:
app = Flask(__name__, instance_relative_config=True)
Ниже представлен полный пример настройки Flask для предварительной загрузки конфигурации из модуля и последующей замены параметров значениями из файла в каталоге конфигурации, если он существует:
app = Flask(__name__, instance_relative_config=True)
app.config.from_object('yourapplication.default_settings')
app.config.from_pyfile('application.cfg', silent=True)
Путь к каталогу экземпляра может быть найден при помощи Flask.instance_path. Flask также предоставляет более короткий способ открытия файлов из каталога экземпляра при помощи Flask.open_instance_resource().

Вот пример для обоих способов:
filename = os.path.join(app.instance_path, 'application.cfg')
with open(filename) as f:
    config = f.read()
# или при помощи open_instance_resource:
with app.open_instance_resource('application.cfg') as f:
    config = f.read()
Примечания переводчика
В качестве перевода для термина cookie было использовано понятие "переменных бразуера".

Информация о флагах httponly и secure взята из статьи HTTP cookie.

Информация о x-sendfile взята из статьи Передача файлов с помощью XSendfile с помощью NGINX.

Примечания переводчика

Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.

Комментариев нет: