Сообщить об ошибке.

Контекст запроса приложения на Flask в Python

Контекст запроса веб-приложения на Flask отслеживает данные уровня запроса во время выполнения запроса. Вместо передачи каждой функции объекта запроса, которая выполняется во время запроса, Flask осуществляет доступ к прокси-объекту запроса flask.request и сеанса flask.session.

Такое поведение похоже на контекст приложения, который отслеживает данные уровня приложения независимо от запроса. Соответствующий контекст приложения передается при передаче контекста запроса.

Содержание:

Цель контекста запроса приложения Flask.

Когда приложение Flask обрабатывает запрос, то оно создает объект Request на основе среды, полученной от сервера WSGI. Поскольку рабочий процесс (поток, процесс или сопрограмма в зависимости от сервера) обрабатывает только один запрос за раз, данные запроса могут считаться глобальными для этого воркера во время этого запроса. Flask использует для этого термин "локальный контекст".

Flask автоматически проталкивает контекст запроса при обработке запроса. Функции просмотра, обработчики ошибок и другие функции, которые выполняются во время запроса, будут иметь доступ к прокси-объекту запроса flask.request, который указывает на объект запроса flask.Request для текущего запроса.

Время жизни контекста запроса приложения Flask.

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

Контекст уникален для каждого потока или другого типа воркера. Запрос flask.request не может быть передан другому потоку, другой поток будет иметь другой стек контекста и не будет знать о запросе, на который указывал родительский поток.

Локальные значения контекста реализованы с использованием Python contextvars и Werkzeug LocalProxy. Python автоматически управляет временем жизни контекстных переменных, а локальный прокси-сервер оборачивает этот низкоуровневый интерфейс, чтобы упростить работу с данными.

Проталкивание контекста запроса вручную.

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

RuntimeError: Working outside of request context.

This typically means that you attempted to use functionality that
needed an active HTTP request. Consult the documentation on testing
for information about how to avoid this problem.

Обычно это должно происходить только при тестировании кода, который ожидает активного запроса. Один из вариантов - это использовать тестовый клиент для имитации полного запроса. Или можно использовать app.test_request_context() в блоке with, и все, что выполняется в блоке, будет иметь доступ к запросу flask.request, заполненному тестовыми данными.

def generate_report(year):
    format = request.args.get('format')
    ...

with app.test_request_context(
        '/make_report/2017', data={'format': 'short'}):
    generate_report()

Если видите эту ошибку где-то еще в своем коде, не связанном с тестированием, то это, скорее всего, указывает на то, что этот код следует переместить в функцию-представление (views).

Как работает контекст запроса.

Для обработки каждого запроса вызывается метод Flask.wsgi_app(). Этот метод управляет контекстами во время запроса. Внутри, контексты запроса и приложения работают как стеки. При отправке контекстов становятся доступными прокси, которые зависят от них и указывают на информацию из верхнего элемента.

При запуске запроса создается и отправляется RequestContext, если контекст для этого приложения еще не является верхним контекстом, то создается и отправляется AppContext. Пока эти контексты передаются, прокси-объекты flask.current_app, flask.g, flask.request и flask.session доступны для исходного потока, обрабатывающего запрос.

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

После отправки запроса, генерации и отправки ответа приложения клиенту, контекст запроса, а затем и контекст приложения выталкиваются. Непосредственно перед их извлечением выполняются методы app.teardown_request() и app.teardown_appcontext(). Эти методы выполняются, даже если во время отправки ответа клиенту возникло необработанное исключение (т. е. в коде функции представлении возникла ошибка). Эти два метода полезны для освобождения открытых ресурсов во время подготовки запроса.

Обратные вызовы и ошибки запроса.

Flask отправляет запрос в несколько этапов, что может повлиять на запрос, ответ и способ обработки ошибок. Контексты активны на всех этих этапах.

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

  • Перед каждым запросом вызываются функции, декорированные @app.before_request(). Если одна из таких функций возвращает значение, то другие функции пропускаются. Возвращаемое значение рассматривается как ответ сервера клиенту, а функция-представление не вызывается.
  • Если функции, декорированные @app.before_request() не вернули ответ, то вызывается функция-представление для согласованного маршрута и возвращает ответ клиенту.
  • Возвращаемое значение функцией-представления преобразуется в фактический объект ответа и передается функциям, декорированным @app.after_request(). Каждая такая функция возвращает измененный или новый объект ответа.
  • После возврата ответа клиенту, контексты (запроса и приложения) выталкиваются, что вызывает методы экземпляра приложения app.teardown_request() и app.teardown_appcontext(). Эти функции вызываются, даже если было вызвано необработанное исключение в любой точке выше.

Если исключение возникает перед методами очистки .teardown*, то Flask пытается сопоставить это исключение с методом app.errorhandler(), чтобы обработать исключение и вернуть ответ клиенту. Если обработчик ошибок не найден или сам обработчик вызывает исключение, то Flask возвращает общий ответ 500 Internal Server Error. При этом методы очистки app.teardown* по-прежнему вызываются, и им передается объект исключения.

Если включен режим отладки, то необработанные исключения не преобразуются в ответ с кодом 500, а вместо этого передаются на сервер WSGI. Это позволяет серверу разработки представить интерактивный отладчик с трассировкой.

Обратные вызовы для освобождения ресурсов.

Обратные вызовы app.teardown* не зависят от отправки запроса и вызываются контекстами, когда они выталкиваются. Функции вызываются, даже если во время подготовки или отправки ответа клиенту возникает необработанное исключение, а даже для контекстов, помещенных вручную. Это означает, что нет никакой гарантии, что какие-либо другие части отправки запроса будут выполнены раньше. Обязательно пишите эти функции таким образом, чтобы они не зависели от других обратных вызовов и не терпели неудачу.

Во время тестирования может быть полезно отложить извлечение контекстов после завершения запроса, чтобы их данные могли быть доступны в тестовой функции. Используйте app.test_client() как блок with, чтобы сохранить контексты до выхода из блока with.

from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def hello():
    print('during view')
    return 'Hello, World!'

@app.teardown_request
def show_teardown(exception):
    print('after with block')

with app.test_request_context():
    print('during with block')

# Функции teardown вызываются 
# после контекста с выходами из блока

with app.test_client() as client:
    client.get('/')
    # Контексты не выталкиваются, даже если запрос завершился
    print(request.path)

# контексты выталкиваются и функции teardown 
# вызываются после выхода клиента с блоком

Сигналы, возникающие в контексте запроса приложения Flask.

Отправляются следующие сигналы:

  • app.request_started отправляется перед вызовом методов app.before_request() .
  • app.request_finished отправляется после вызова методов app.after_request().
  • app.got_request_exception отправляется, когда начинает обрабатываться исключение, но до того, как будет найден или вызван app.errorhandler().
  • app.request_tearing_down отправляется после вызова методов app.teardown_request().

Более подробно о сигналах Flask смотрите материал "Отладочные сигналы приложения Flask в Python".