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

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

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

Назначение контекста

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

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

Срок службы контекста

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

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

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

Ручное перемещение контекста

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

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.

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

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

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

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

Информацию о том, как использовать контекст запроса из интерактивной оболочки Python, смотрите в разделе Работа с оболочкой.

Как работает контекст

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

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

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

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

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

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

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

  1. Перед каждым запросом вызываются функции before_request(). Если одна из этих функций возвращает значение, остальные функции пропускаются. Возвращаемое значение рассматривается как ответ, и функция представления не вызывается.

  2. Если функции before_request() не вернули ответ, вызывается функция представления для найденного маршрута и возвращает ответ.

  3. Возвращаемое значение представления преобразуется в реальный объект ответа и передается в функции after_request(). Каждая функция возвращает измененный или новый объект ответа.

  4. После возврата ответа контексты разворачиваются, что вызывает функции teardown_request() и teardown_appcontext(). Эти функции вызываются даже в том случае, если в любой момент времени было вызвано необработанное исключение.

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

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

Обратные вызовы при разрушении

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

Во время тестирования может быть полезно отложить выскакивание контекстов после завершения запроса, чтобы к их данным можно было получить доступ в тестовой функции. Используйте 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 functions are called after the context with block exits

with app.test_client() as client:
    client.get('/')
    # the contexts are not popped even though the request ended
    print(request.path)

# the contexts are popped and teardown functions are called after
# the client with block exits

Сигналы

Если signals_available истинно, посылаются следующие сигналы:

  1. request_started отправляется до вызова функций before_request().

  2. request_finished отправляется после вызова функций after_request().

  3. got_request_exception отправляется, когда исключение начинает обрабатываться, но до того, как будет найдено или вызвано errorhandler().

  4. request_tearing_down отправляется после вызова функций teardown_request().

Примечания о доверенностях

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

В большинстве случаев вам не нужно заботиться об этом, но есть некоторые исключения, когда полезно знать, что этот объект на самом деле является прокси:

  • Прокси-объекты не могут подделывать свои типы под реальные типы объектов. Если вы хотите выполнить проверку экземпляра, вы должны сделать это на проксируемом объекте.

  • Ссылка на проксируемый объект необходима в некоторых ситуациях, например, для отправки Сигналы или передачи данных фоновому потоку.

Если вам нужно получить доступ к базовому объекту, который проксируется, используйте метод _get_current_object():

app = current_app._get_current_object()
my_signal.send(app)
Вернуться на верх