小编典典

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

all

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

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


阅读 61

收藏
2022-08-20

共1个答案

小编典典

多个应用程序

在您意识到 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
应用程序的两个实例。

上下文

当您使用 Flask 时,您通常最终会使用全局变量来访问各种功能。例如,您可能有这样的代码…

from flask import request

然后,在查看期间,您可能会使用request来访问当前请求的信息。显然,request不是一个普通的全局变量;实际上,它是一个上下文本地值。换句话说,幕后有一些魔术说“当我调用 时,从CURRENT
请求的对象中request.path获取path属性”。request两个不同的请求对于request.path.

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

把它放在一起

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

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

您可能还有类似于以下的代码:

from flask import url_for

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

为了回答你的问题…

当涉及到请求或应用程序上下文时,“堆栈”的目的是什么?

从请求上下文文档中:

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

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

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

这两个独立的堆栈,还是它们都是一个堆栈的一部分?

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

请求上下文是推送到堆栈上,还是堆栈本身?

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

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

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

但是,在某些情况下,您希望自己操作堆栈。

在请求之外运行代码

人们遇到的一个典型问题是他们使用 Flask-SQLAlchemy 扩展来设置 SQL 数据库和模型定义,使用代码如下所示......

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

然后他们在应该从 shell 运行的脚本中使用app和值。db例如,“setup_tables.py”脚本…

from myapp import app, db

# Set up models
db.create_all()

在这种情况下,Flask-SQLAlchemy
扩展知道app应用程序,但在此期间create_all()会抛出一个错误,抱怨没有应用程序上下文。这个错误是有道理的;create_all你从来没有告诉
Flask 在运行该方法时它应该处理什么应用程序。

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
2022-08-20