Flask – это фреймворк, который может работать как в изолированной среде, так и в глобальной области видимости. Для того, чтобы осуществлять операции в последней, используются контексты. Сегодня более подробно разберемся, что это такое и как их использовать.
Понятие контекстов в Flask
Разработчики, которые уже имели дело с Django, могут обратить внимание на то, что функциями представления Flask request не используется в качестве первого аргумента. Чтобы получать доступ к данным, используется специальный объект, через который и осуществляется взаимодействие.
from flask import Flask, request @app.route('/') def requestdata(): return "Hello! Your IP is {} and you are using {}: ".format(request.remote_addr, request.user_agent)
Может показаться, что request – это глобальный объект. Но при детальном рассмотрении оказывается, что нет. Если бы это было так, то программа не смогла бы в большом количестве потоков различать два процесса, происходящие одновременно. Ведь программа этого типа все переменные распределяет по потокам.
Flask использует то, что называется контекстами. Они позволяют отдельным переменным мимикрировать под глобальные и работать соответствующим образом. Когда к ним обращается интерпретатор, то он может получить доступ к конкретному объекту.
С технической точки зрения, переменные такого типа рассматриваются, как локальные или внутрипоточные. Если обратиться к документации Flask, то в ней можно найти описание двух видов контекстов:
- Контекст приложения.
- Контекст запроса.
Первый нужен для того, чтобы хранить общие переменные, касающиеся всей программы. К таким относятся те, которые связаны с подключением к базе данных, настройками, и так далее. В свою очередь, контекст запроса нужен для того, чтобы хранить переменные конкретного запроса.
Если мы говорим о контексте приложения, то он предлагает объекты current_app или g. В первом случае происходит ссылка на экземпляр, обрабатывающий запрос. Второй же применяется для того, чтобы временно хранить данные во время его обработки.
Когда значение установлено, доступ к нему осуществляется с помощью любой функции представления. Если говорить о g, то в ней осуществляется сброс данных каждый раз после каждого выполненного запроса. Независимо от того, какой контекст используется — приложения или запроса — они предоставляют объекты request и session, какие хранят данные о текущем запросе и словарь со значениями, сохраненными между запросами, соответственно.
Когда происходит получение запроса, Flask активирует контексты приложения и запроса. После обработки запроса они удаляются. Когда контексты приложения активируются, все переменные программы становятся доступными для потока. Аналогично и с контекстами запроса. Когда он находится в активированном состоянии, все переменные в потоке могут предоставлять свои значения для функций и методов. В рамках функций представления пользователь может получить доступ ко всем объектам контекстов приложения и запроса. В этом случае задумываться о том, активированы ли контексты, или нет, нельзя. Если же попробовать получить доступ к ним вне функции представления или в консоли Python, то всплывет ошибка. Приведем пример, как она будет выглядеть.
>>> from flask import Flask, request, current_app >>> >>> request.method # получаем метод запроса Traceback (most recent call last): #... 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. >>>
request_method используется, чтобы вернуть HTTP-метод, который применяется в запросе. Но так, как самого его нет, то в этом случае и активации контекста запроса не происходит.
Аналогичное исключение появляется, если попробовать получить доступ к объекту, предоставляемому контекстом приложения.
>>> current_app.name # получим название приложения Traceback (most recent call last): #... RuntimeError: Working outside of application context. This typically means that you attempted to use functionality that needed to interface with the current application object in a way. To solve this set up an application context with app.app_context(). See the documentation for more information. >>>
Для получения доступа к тем объектам, которые предоставляются контекстами за рамками функции представления, сперва надо создать контекст, который соответствует задачам.
Как это сделать? Для создания контекста существует специальный метод, который называется app_context().
>>> from main2 import app >>> from flask import Flask, request, current_app >>> >>> app_context = app.app_context() >>> app_context.push() >>> >>> current_app.name 'main2'
Этот код выглядит немного сложным. Но у пользователя есть возможность упростить его. Это делается с помощью выражения with.
Аналогичным способом создается контекст запроса. Эта задача выполняется с использованием метода test_request_context() в экземпляре Flask. Необходимо учитывать, что при активации контекста запроса, контекст приложения создается в любом случае. Давайте приведем пример кода, который показывает, как правильно создавать контекст запроса.
>>> from main2 import app >>> from flask import request, current_app >>> >>> >>> with app.test_request_context('/products'): ... request.path # получим полный путь к запрашиваемой странице(без домена). ... request.method ... current_app.name ... '/products' 'GET' 'main2' >>>
То, что в этом фрагменте кода используется адрес /products, не имеет никакого принципиального значения. Он был выбран произвольно. Вы можете использовать тот, который удобно.
Использование контекста приложения
Как правило, контекст создается для того, чтобы сохранять в нем кэш ресурсов, который будет использоваться при формировании отдельного запроса или же для постоянного использования. То есть, если создается подключение к базе данных, то и ресурсы должны храниться там.
Чтобы сохранить что-то в контексте приложения, необходимо выбирать уникальные имена, поскольку к этому месту могут получать доступ приложения и расширения Flask.
Самым частым вариантом применения контекста приложения является разделение процесса управления ресурсами на два компонента:
- Неявное кеширование.
- Освобождение ресурса, основанное на демонтировании контекста.
Необходимо создать функцию get_X(), создающую ресурс X, если его еще нет в наличии. Если это не так, то она осуществляет возврат того же самого ресурса. А функция teardown_X() используется для того, чтобы зарегистрировать обработчик демонтирования.
Приведем пример использования контекста приложения для соединения с базой данных.
import sqlite3 from flask import g def get_db(): db = getattr(g, '_database', None) if db is None: db = g._database = connect_to_database() return db @app.teardown_appcontext def teardown_db(exception): db = getattr(g, '_database', None) if db is not None: db.close()
Обратите внимание, что здесь мы импортировали библиотеку sqlite3, которая используется для работы с базами данных. Она не относится к Flask, но может применяться совместно с этим фреймворком для написания сложных приложений, предназначенных для обработки больших массивов информации.
Здесь установление соединения будет осуществляться исключительно в том случае, если вызов get_db() происходит впервые. Также можно воспользоваться классом LocalProxy, чтобы сделать это неявно.
from werkzeug.local import LocalProxy db = LocalProxy(get_db)
Этот метод позволяет пользователю непосредственно подключиться к базе данных, которая вызывает саму себя.
Локальность контекста приложения
Контекст приложения может как создаваться, так и уничтожаться, если в этом есть надобность. Он не может быть общим для нескольких разных запросов, и может использоваться лишь в рамках одного потока. Поэтому лучше места для того, чтобы хранить информацию о подключении к базе данных не найти.
Внутренний объект стека имеет название flask._app_ctx_stack. Хранение дополнительной информации может осуществляться и расширениями. В этом случае она сохраняется на самом верхнем уровне, если ожидается, что они выбрали уникальное имя.
А под пользовательский код зарезервирован объект flask.g.
Как работает контекст запроса?
Для начала необходимо образец кода, который будет находиться внутри приложения 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)
Строки, похожие на эти, можно обнаружить в любой программе, сделанной на основе этого фреймворка.
Чтобы вернуть новый объект RequestContext, используется метод request_context(). Его необходимо использовать вместе с выражением with. Все, что вызывается из этого потока, стартуя этой точкой и заканчивая концом выражения, будет получать доступ к глобальному объекту запроса.
Контекст запроса – это стек, в самом верху которого находится текущий активный запрос. С помощью push() можно добавить контекст на верхушку стека, а с помощью pop() – убрать его. В процессе этого также осуществляется вызов функции teardown_request() приложения.
Выводы
Таким образом, контекст – это эффективный инструмент работы с приложениями, разработанными с помощью Flask.