Предварительное изучение контекста в Flask
всех с Новым годом! Поскольку гала-концерт Весеннего фестиваля в этом году очень красивый, я не думаю, что могу себе это позволить, поэтому я напишу для всех пикантное развлечение по гидрологии цыплят Это также один из нескольких флагов, которые были установлены ранее.
текст
Друзья, которые занимались разработкой Flask, знают, что во Flask есть две концепции: одна называется контекстом приложения, а другая называется контекстом запроса. Это два очень уникальных механизма в Flask.
С того момента, как приложение Flask считывает конфигурацию и запускается, оно входит в контекст приложения, где мы можем получить доступ к файлам конфигурации, открыть файлы ресурсов и обратно построить URL-адреса с помощью правил маршрутизации. Когда промежуточное ПО WSGI вызывает приложение Flask, оно входит в контекст запроса. Мы можем получать такие операции, как HTTP HEADER, а также выполнять такие операции, как SESSION.
Однако, как игроку в острую курицу, часто непонятно, почему существуют эти два Контекста, ничего страшного, давайте поговорим об этом медленно.
Предварительные знания
В первую очередь должно быть понятно, что мы хотим изолировать данные разных потоков в одном процессе, тогда мы отдадим приоритетthreading.local
, для достижения требования изоляции данных друг от друга. Но теперь есть проблема, теперь наша модель параллелизма может быть не только в традиционном пониманиипроцесс-потокМодель. это также может бытьсопрограмма (сопрограмма)Модель. Распространенным является Greenlet/Eventlet. при этих обстоятельствах,threading.local
Он не может хорошо удовлетворить наши потребности. тогдаWerkzeugРеализован собственный локальный, т.е.werkzeug.local.Local
ТакWerkzeugСамостоятельная реализация Локальная и стандартнаяthreading.local
В чем разница по сравнению с? Мы помним, что самая большая разница в том, что
Первый будет использовать идентификатор Greenlet вместо идентификатора потока для поддержки планирования Gevent или Eventlet, когда доступен Greenlet, последний поддерживает только многопоточное планирование;
Werkzeug также реализует две структуры данных, одна из которых называетсяLocalStack
, называетсяLocalProxy
LocalStack
основан наLocal
Реализована структура стека. Свойства стекапоследний пришел первый вышел. Когда мы входим в Context, помещаем текущий объект в стек. Тогда мы также можем получить верхний элемент стека. Тем самым получая текущую контекстную информацию.
LocalProxy
Это реализация шаблона прокси. При создании экземпляра передатьcallable
параметр. Затем, когда этот параметр вызывается, он возвращаетLocal
объект. Все наши последующие операции, такие как вызовы свойств, численные вычисления и т. д., будут перенаправляться на возвращаемое значение этого параметра.Local
на объекте.
Теперь вы можете не знать, почему мы используем LocalProxy для работы, давайте покажем вам пример
from werkzeug.local import LocalStack
test_stack = LocalStack()
test_stack.push({'abc': '123'})
test_stack.push({'abc': '1234'})
def get_item():
return test_stack.pop()
item = get_item()
print(item['abc'])
print(item['abc'])
Видите ли, все значения, которые мы здесь выводим, однородны.1234
, но что мы хотим сделать здесь, так это то, что значение, получаемое каждый раз, является последним элементом на вершине стека, тогда мы должны использовать прокси-режим в это время.
from werkzeug.local import LocalStack, LocalProxy
test_stack = LocalStack()
test_stack.push({'abc': '123'})
test_stack.push({'abc': '1234'})
def get_item():
return test_stack.pop()
item = LocalProxy(get_item)
print(item['abc'])
print(item['abc'])
Видите ли, вот мы и есть магия Proxy.
Context
Поскольку колба реализована на основе Werkzeug, контекст приложения и контекст запроса основаны на реализации LocalStack, упомянутой выше.
Из именования вы должны увидеть, что контекст приложения представляет собой контекст приложения и может содержать различную информацию о конфигурации, такую как конфигурация журнала, конфигурация базы данных и т. д. Контекст запроса представляет собой контекст запроса, и мы можем получить различную информацию в текущем запросе. Например, информация, которую несет тело.
Два контекста определены вflask.ctxфайл, соответственноAppContext
а такжеRequestContext
. Акт создания контекста заключается в том, чтобы втолкнуть его вflask.globalsопределено в файле_app_ctx_stack
а также_request_ctx_stack
середина. Как упоминалось ранее, LocalStack изолирован «потоками» (здесь могут быть потоки в традиционном понимании, а может быть и Greenlet). В то же время Flask обрабатывает только один запрос на поток, поэтому можно добиться изоляции запросов.
когдаapp = Flask(__name__)
При создании приложения Flask контекст приложения не помещается в стек автоматически. Так что в это время вершина локального стека пуста, и current_app также находится в несвязанном состоянии.
from flask import Flask
from flask.globals import _app_ctx_stack, _request_ctx_stack
app = Flask(__name__)
_app_ctx_stack.top
_request_ctx_stack.top
_app_ctx_stack()
# <LocalProxy unbound>
from flask import current_app
current_app
# <LocalProxy unbound>
Как и в сети, когда приходит запрос, мы начинаем выполнять контекстно-зависимые операции. Весь процесс выглядит следующим образом:
Хорошо, теперь есть проблема:
-
Зачем различать контекст приложения и контекст запроса
-
Зачем использовать структуру стека для реализации контекста?
Сообщение в блоге мистера Белки Орео, которое я читал давным-давноКонтекстный механизм Flaskответил на этот вопрос
Эти два подхода дают нам возможность сосуществования нескольких приложений Flask и гибкого управления контекстом в не-веб-среде выполнения.
Мы знаем, что после вызова app.run() в приложении Flask процесс переходит в режим блокировки и начинает прослушивать запросы. На данный момент невозможно запустить другое приложение Flask в основном потоке. Итак, какие еще сценарии требуют сосуществования нескольких приложений Flask? Как упоминалось ранее, экземпляр приложения Flask является приложением WSGI, поэтому промежуточное ПО WSGI позволяет использовать комбинированный режим, например:
from werkzeug.wsgi import DispatcherMiddleware
from biubiu.app import create_app
from biubiu.admin.app import create_app as create_admin_app
application = DispatcherMiddleware(create_app(), {
'/admin': create_admin_app()
})
Учитель Орео приводит в статье такой пример: встроенное промежуточное ПО Werkzeug объединяет два приложения Flask в одно приложение WSGI. В этом случае оба приложения работают одновременно, но запросы распределяются между разными приложениями для обработки в соответствии с разными URL-адресами.
Но теперь у многих друзей возникает вопрос, почему здесь не используется Blueprint?
-
Blueprint работает в том же приложении. Соответствующая информация, висящая в контексте приложения, непротиворечива. Но если вы хотите изолировать информацию друг друга, для изоляции удобнее использовать App Context, чем использовать имена переменных или что-то в этом роде.
-
Режим промежуточного программного обеспечения — это функция, разрешенная в WSGI, другими словами, мы также можем использовать Flask с другой веб-платформой (например, Django), которая соответствует протоколу WSGI.
Но разделение Flask на два контекста более значимо для не-веб-приложений. В официальной документации Flask есть такой пункт
Основная причина существования контекста приложения заключается в том, что в прошлом множество функций было привязано к контексту запроса из-за отсутствия лучшего решения.Поскольку одним из столпов дизайна Flask является то, что вы можете иметь более одного приложения в одном и том же Процесс Питона.
Другими словами, смысл существования контекста приложения заключается в нескольких сценариях приложения Flask в процессе.Наиболее распространенный сценарий заключается в том, что мы используем Flask для выполнения некоторого автономного кода скрипта.
Хорошо, давайте поговорим о сценариях Flask, не связанных с веб-приложениями.
Например, у нас есть плагин под названием Flask-SQLAlchemy. Тогда вот сценарий использования Сначала у нас теперь есть такой код
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
database = Flask(__name__)
database.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(database)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return '<User %r>' % self.username
Здесь вы должны обратить внимание на первые несколько ключевых моментов, первый из нихdatabase.config
, Да, да, Flask-SQLAlchemy получает соответствующую информацию о конфигурации от текущего приложения, чтобы установить связь с базой данных. Далее есть два способа передать приложение.Первый способ напрямую db = SQLAlchemy(база данных) как показано на рисунке выше, что легко понять.Второй способ, если мы его не передаем, то Flask- SQLAlchemy передаст ему current_app.Получите текущее приложение, а затем получите соответствующую конфигурацию, чтобы установить связь.
Итак, вопрос в том, почему существует второй метод?
Давайте представим сценарий, теперь два моих приложения с разными конфигурациями базы данных используют одну модель, так что мне делать? на самом деле это очень просто
Сначала напишите файл модели, например, назовите его data/user_model.py.
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return '<User %r>' % self.username
Итак, в нашем файле приложения мы можем написать так
from data.user_model import User
database = Flask(__name__)
database.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
with database.app_context():
db.init_app(current_app)
db.create_all()
admin = User(username='admin', email='admin@example.com')
db.session.add(admin)
db.session.commit()
print(User.query.filter_by(username="admin").first())
database1 = Flask(__name__)
database1.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test1.db'
with database1.app_context():
db.init_app(current_app)
db.create_all()
admin = User(username='admin_test', email='admin@example.com')
db.session.add(admin)
db.session.commit()
print(User.query.filter_by(username="admin").first())
Вы можете видеть, что это легче понять.Через контекст приложения наш Flask-SQLAlchemy может получить текущее приложение через current_app, а затем получить соответствующую информацию о конфигурации.
Этого примера недостаточно, давайте теперь изменим другой пример
from flask import Flask, current_app
import logging
app = Flask("app1")
app2 = Flask("app2")
app.config.logger = logging.getLogger("app1.logger")
app2.config.logger = logging.getLogger("app2.logger")
app.logger.addHandler(logging.FileHandler("app_log.txt"))
app2.logger.addHandler(logging.FileHandler("app2_log.txt"))
with app.app_context():
with app2.app_context():
try:
raise ValueError("app2 error")
except Exception as e:
current_app.config.logger.exception(e)
try:
raise ValueError("app1 error")
except Exception as e:
current_app.config.logger.exception(e)
Ну, этот код очень понятен, смысл очень ясен, это вывод лога путем получения логгера в приложении в текущем контексте. В то же время этот код также ясно объясняет, почему мы используем такую структуру данных, как стек, для поддержания контекста.
Первый взглядapp_context()
исходный код
def app_context(self):
"""Binds the application only. For as long as the application is bound
to the current context the :data:`flask.current_app` points to that
application. An application context is automatically created when a
request context is pushed if necessary.
Example usage::
with app.app_context():
...
.. versionadded:: 0.9
"""
return AppContext(self)
Ну, это очень просто, просто создайте объект AppContext для возврата, а затем мы посмотрим на соответствующий код.
class AppContext(object):
"""The application context binds an application object implicitly
to the current thread or greenlet, similar to how the
:class:`RequestContext` binds request information. The application
context is also implicitly created if a request context is created
but the application is not on top of the individual application
context.
"""
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
# Like request context, app contexts can be pushed multiple times
# but there a basic "refcount" is enough to track them.
self._refcnt = 0
def push(self):
"""Binds the app context to the current context."""
self._refcnt += 1
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
_app_ctx_stack.push(self)
appcontext_pushed.send(self.app)
def pop(self, exc=_sentinel):
"""Pops the app context."""
try:
self._refcnt -= 1
if self._refcnt <= 0:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
finally:
rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
appcontext_popped.send(self.app)
def __enter__(self):
self.push()
return self
def __exit__(self, exc_type, exc_value, tb):
self.pop(exc_value)
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
reraise(exc_type, exc_value, tb)
эммм, во-первыхpush
Путь в том, чтобы подтолкнуть себя к_app_ctx_stack
,иpop
Метод заключается в том, чтобы сбросить себя с вершины стека. Затем мы видим, что смысл двух методов очень ясен: при входе в контекстный менеджер втолкнуть себя в стек, а затем при выходе из контекстного менеджера втолкнуть себя.
Все мы знаем, что одним из свойств стека является то, что он входит последним, а выходит первым, а вершина стека всегда является последним вставленным элементом. И посмотри на насcurrent_app
исходный код
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app
current_app = LocalProxy(_find_app)
Ну очень понятно, то есть получить элемент наверху текущего стека, а потом выполнять связанные с ним операции.
Что ж, посредством этой непрерывной работы стека мы можем сделатьcurrent_app
Выбранный элемент — это приложение в нашем текущем контексте.
Дополнительное пояснение: г
g также является одной из нескольких глобальных переменных, которые мы обычно используем. Вначале эта переменная монтируется в контексте запроса. Но после 0.10 g монтируется в контексте приложения. Некоторые учащиеся могут не знать, почему они это делают.
Во-первых, скажите мне, что g используется для
У чиновника есть этот абзац в контексте.
The application context is created and destroyed as necessary. It never moves between threads and it will not be shared between requests. As such it is the perfect place to store database connection information and other things. The internal stack object is called flask._app_ctx_stack. Extensions are free to store additional information on the topmost level, assuming they pick a sufficiently unique name and should put their information there, instead of on the flask.g object which is reserved for user code.
Общая идея заключается в том, что конфигурация базы данных и другая важная информация о конфигурации монтируются в объект App. Но если это какой-то пользовательский код, например, если вы не хотите передавать данные послойно в функцию, а потом нужно передать какие-то переменные, то можно повесить на g.
В то же время, как упоминалось ранее, Flask можно использовать не только как веб-фреймворк, но и в некоторых случаях, не связанных с веб-сайтами. В этом случае, если g принадлежит контексту запроса, то нам нужно построить запрос вручную, если мы хотим использовать g, что, несомненно, неразумно.
Наконец
Я написал эту статью в канун Нового года, и теперь, когда она опубликована, моя острая курочка тоже беспомощна. Механизм контекста Flask — одна из его наиболее важных особенностей. Разумно используя механизм контекста, мы можем лучше использовать flask в большем количестве случаев. Что ж, это конец этой деятельности по написанию статьи о пряной курице. Надеюсь, ты не забросаешь меня тухлыми яйцами! И счастливого нового года!