Перевод статьи: The Request Context
Автор: Армин Роначер (Armin Ronacher)
Этот документ описывает поведение Flask 0.7, которое в основном совпадает со старым, но имеет некоторые небольшие отличия.
Рекомендуем сначала прочитать главу Контекст приложения.
Представим, что имеется служебная функция, которая возвращает URL, на который нужно перенаправить пользователя. Представим, что всегда нужно перенаправлять на URL из параметра next или на страницу, с которой перешли на текущую страницу, или на страницу-индекс:
from flask import request, url_for def redirect_url(): return request.args.get('next') or \ request.referrer or \ url_for('index')
Можно заметить, что функция обращается к объекту запроса. Если попытаться запустить её из оболочки Python, будет выброшено исключение:
>>> redirect_url() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'NoneType' object has no attribute 'request'
В этом есть определённый смысл, потому что в данный момент нет запроса, к которому мы пытаемся получить доступ. Итак, нам нужно создать запрос и связать его с текущим контекстом. Создадим RequestContext с помощью метода test_request_context:
>>> ctx = app.test_request_context('/?next=http://example.com/')
Этот контекст можно использовать одним из двух способов - используя выражение with или вызвав методы push() и pop():
>>> ctx.push()
После чего можно работать с объектом запроса:
>>> redirect_url() u'http://example.com/'
И так до тех пор, пока вы не вызовете pop:
>>> ctx.pop()
Поскольку контекст запроса изнутри представляет собой элемент стека, можно добавлять и вынимать его из стека множество раз. Это очень полезно для реализации таких функций, как внутреннее перенаправление.
За более подробной информацией об использовании контекста запроса из интерактивной оболочки Python, обратитесь к главе Работа с оболочкой.
Если посмотреть изнутри на то, как работает приложение Flask WSGI, можно обнаружить фрагмент кода, который выглядит очень похожим на следующий:
def wsgi_app(self, environ): with self.request_context(environ): try: response = self.full_dispatch_request() except Exception, e: response = self.make_response(self.handle_exception(e)) return response(environ, start_response)
Метод request_context() возвращает новый объект RequestContext и использует его в выражении with для связывания контекста. Всё, что будет вызвано из этого потока, начиная с этой точки и до конца выражения with, будет иметь доступ к глобальному объекту запроса (flask.request и т.п.).
Контекст запроса изнутри работает как стек: на самом верху стека находится текущий активный запрос. push() добавляет контекст на верхушку стека, а pop() вынимает его из стека. При изъятии также вызываются функции teardown_request() приложения.
Стоит также отметить, что при добавлении контекста запроса в стек также создаётся контекст приложения, если его ещё не было.
Что случится, если произойдёт ошибка во время обработки запроса во Flask? Частично это поведение изменилось в версии 0.7, потому что желательно знать, что на самом деле произошло. Новое поведение очень простое:
И так, что же происходит в случае ошибки? В рабочем режиме неотловленные исключения приводят к тому, что обработчик выводит сообщение об ошибке 500 на сервере. В режиме разработки, однако, приложение не обрабатывает исключение и передаёт его наверх, серверу WSGI. Таким образом, средства интерактивной отладки могут предоставить информацию для отладки.
Важное изменение в версии 0.7 заключается в том, что внутреннее сообщение сервера об ошибке теперь больше не подвергается пост-обработке с помощью функций обратного вызова after_request() и больше нет гарантии того, что они будут выполнены. Таким образом, внутренняя обработка кода выглядит понятнее и удобнее в настройке.
Предполагается, что вместо них должны использоваться новые функции teardown_request(), специально предназначенные для действий, которые нужно выполнять по окончании запроса при любом его исходе.
Функции обратного вызова teardown_request() - это особые функции обратного вызова, которые выполняются отдельно. Строго говоря, они не зависят от действительной обработки запроса и связаны с жизненным циклом объекта RequestContext. Когда контекст запроса вынимается из стека, вызываются функции teardown_request().
Это важно знать, если жизнь контекста запроса будет удлинена при использовании клиента для тестировании с помощью выражения with или при использовании контекста запроса из командной строки:
with app.test_client() as client: resp = client.get('/foo') # Функции teardown_request() ещё не вызываются в этом месте # не смотря на то, что получен объект ответа # Только когда код достигнет этой точки, функции teardown_request() # будут вызваны. Ещё это может произойти, если произойдёт # переключение на другой запрос из клиента для тестирования
В этом можно убедиться, воспользовавшись командной строкой:
>>> app = Flask(__name__) >>> @app.teardown_request ... def teardown_request(exception=None): ... print 'это запущено после запроса' ... >>> ctx = app.test_request_context() >>> ctx.push() >>> ctx.pop() это запущено после запроса >>>
Учтите, что функции обратного вызова выполняются всегда, независимо от того, были ли выполнены функции обратного вызова before_request() и произошло ли исключение. Некоторые части системы тестирования могут также создавать временный контекст без вызова обработчиков before_request(). Убедитесь, что ваши обработчики teardown_request() в таких случаях никогда не приводят к ошибкам.
Некоторые из объектов, предоставляемых Flask, являются посредниками к другим объектам. Причина в том, что эти посредники являются общими для потоков и они скрыто передают объект для обработки соответствующему потоку.
В большинстве случаев об этом не стоит беспокоиться, но существует несколько исключительных случаев, когда хорошо знать, что объект на самом деле является посредником:
Если нужно получить доступ к объекту, доступному через посредника, можно воспользоваться методом the _get_current_object():
app = current_app._get_current_object() my_signal.send(app)
Случилась ли ошибка или нет, в конце обработки запроса контекст запроса извлекается из стека и все связанные с ним данные удаляются. Однако, во время разработки это может стать проблемой, потому что может потребоваться извлечь информацию из запроса, если произошло исключение. Во Flask 0.6 и более ранних версиях в режиме отладки, если произошло исключение, контекст запроса не извлекается, так что средство интерактивной отладки всё ещё может предоставить вам необходимую информацию.
Начиная со Flask 0.7 имеется возможность управлять этим поведением при помощи настройки параметра конфигурации PRESERVE_CONTEXT_ON_EXCEPTION. По умолчанию он связан с настройкой DEBUG. Если приложение находится в отладочном режиме, то контекст защищается, а если в рабочем, то - нет.
Не следует принудительно включать PRESERVE_CONTEXT_ON_EXCEPTION в рабочем режиме, потому что это может привести к утечке памяти приложения при каждом исключении. Однако, эта настройка может оказаться полезной в режиме разработки, чтобы воспроизвести ошибку, которая происходит только с настройками рабочего режима.
Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.