一尘不染

Flask上下文堆栈的目的是什么?

flask

我一直在使用请求/应用程序上下文有一段时间,但并没有完全了解它的工作原理或设计原因。当涉及到请求或应用程序上下文时,“堆栈”的目的是什么?这是两个单独的堆栈,还是都是一个堆栈的一部分?是将请求上下文压入堆栈,还是堆栈本身?我是否可以在彼此之上推送/弹出多个上下文?如果是这样,我为什么要这样做?

抱歉所有问题,但是阅读了请求上下文和应用程序上下文文档后,我仍然感到困惑。


阅读 529

收藏
2020-04-05

共1个答案

一尘不染

Multiple Apps

在你意识到Flask可以拥有多个应用程序之前,应用程序上下文(及其用途)确实令人困惑。想象一下你想让一个WSGI Python解释器运行多个Flask应用程序的情况。我们不是在这里谈论蓝图,而是在谈论完全不同的Flask应用程序。

你可以将其设置类似于“应用程序调度”示例中的Flask文档部分:

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

请注意,有两个完全不同的Flask应用程序被创建为“前端”和“后端”。换句话说,Flask(…)应用程序构造函数被调用了两次,创建了Flask应用程序的两个实例。

Contexts

当你使用Flask时,通常会最终使用全局变量来访问各种功能。例如,你可能有读取如下代码。

from flask import request

然后,在查看期间,你可能会request用来访问当前请求的信息。显然,request这不是正常的全局变量;实际上,它是上下文局部值。换句话说,幕后隐藏着一些魔术,上面写着“当我打电话时request.path,pathrequestCURRENT请求的对象获取属性”。两个不同的请求将对产生不同的结果request.path

实际上,即使你使用多个线程运行Flask,Flask也足够聪明,可以将请求对象隔离。这样,两个线程(每个线程处理一个不同的请求)就可以同时调用request.path并获取各自请求的正确信息。

Putting it Together

因此,我们已经看到Flask可以在同一个解释器中处理多个应用程序,并且由于Flask允许你使用“上下文本地”全局变量的方式,因此必须有某种机制来确定“当前” 请求是什么(为了做)之类的事情request.path

将这些想法放在一起,Flask必须具有某种方法来确定“当前”应用程序是什么也应该有意义!

你可能还具有类似于以下内容的代码:

from flask import url_for

像我们的request示例一样,该url_for函数的逻辑取决于当前环境。但是,在这种情况下,可以清楚地看到逻辑在很大程度上取决于哪个应用程序被视为“当前”应用程序。在上面显示的前端/后端示例中,“前端”和“后端”应用程序都可能具有“ /登录”路由,因此url_for('/login')应返回不同的内容,具体取决于视图是否正在处理针对前端或后端应用程序的请求。

要回答你的问题…
当涉及到请求或应用程序上下文时,“堆栈”的目的是什么?

从请求上下文文档中:

由于请求上下文在内部作为堆栈维护,因此你可以多次推送和弹出。这对于实现内部重定向之类的操作非常方便。

换句话说,即使你通常在这些“当前”请求或“当前”应用程序堆栈中包含0或1个项目,也可能会有更多或更多的项目。

给出的示例是让你的请求返回“内部重定向”结果的地方。假设某个用户请求A,但你想返回到该用户B。在大多数情况下,你将向该用户发出重定向,并将该用户指向资源B,这意味着该用户将运行第二个请求以获取B。稍微不同的处理方式是进行内部重定向,这意味着在处理A时,Flask将向自身发出对资源B的新请求,并将第二个请求的结果用作用户原始请求的结果。

这是两个单独的堆栈,还是都是一个堆栈的一部分?

它们是两个单独的堆栈。但是,这是一个实现细节。更重要的是没有堆栈,而是可以随时获取“当前”应用程序或请求(堆栈顶部)的事实。

是将请求上下文压入堆栈,还是堆栈本身?

“请求上下文”是“请求上下文堆栈”的一项。与“应用程序上下文”和“应用程序上下文堆栈”类似。

我是否可以在彼此之上推送/弹出多个上下文?如果是这样,我为什么要这样做?

在Flask应用程序中,通常不会这样做。内部重定向(如上所述)的一个示例。但是,即使在这种情况下,你可能最终也会让Flask处理新请求,因此Flask会为你完成所有推送/弹出操作。

但是,在某些情况下,你想自己操作堆栈。

在请求之外运行代码
人们遇到的一个典型问题是,他们使用Flask-SQLAlchemy扩展来使用如下所示的代码来建立SQL数据库和模型定义。

app = Flask(__name__)
db = SQLAlchemy() # Initialize the Flask-SQLAlchemy extension object
db.init_app(app)

然后,它们在应从外壳程序运行的脚本中使用appdb值。例如,“ setup_tables.py”脚本…

from myapp import app, db

# Set up models
db.create_all()

在这种情况下,Flask-SQLAlchemy扩展了解app应用程序,但在create_all()此过程中将抛出错误,抱怨没有应用程序上下文。该错误是合理的;你从未告诉Flask在运行该create_all方法时应处理哪个应用程序。

你可能想知道为什么with app.app_context()在视图中运行类似的函数时最终不需要此调用。原因是Flask在处理实际的Web请求时已经为你处理了应用程序上下文的管理。实际上,问题仅出现在这些视图函数(或其他此类回调)之外,例如在一次性脚本中使用模型时。

解决方案是自己推送应用程序上下文,这可以通过以下方法完成:

from myapp import app, db

# Set up models
with app.app_context():
    db.create_all()

这将推送一个新的应用程序上下文(使用的应用程序app,请记住可能有多个应用程序)。

测试中

你想操纵堆栈的另一种情况是进行测试。你可以创建一个处理请求的单元测试,然后检查结果:

import unittest
from flask import request

class MyTest(unittest.TestCase):
    def test_thing(self):
        with app.test_request_context('/?next=http://example.com/') as ctx:
            # You can now view attributes on request context stack by using `request`.

        # Now the request context stack is empty
2020-04-05