这篇文章的目的是为了阐明 Flask 中的应用上下文和请求上下文是如何工作的。
这是 Flask上下文
系列文章的第一部分:
内容
目标
在这篇文章的最后,你应该能够解释:
- Flask 如何处理请求对象,以及它与其他 Web 框架有何不同?
- 应用上下文和请求上下文是什么?
- 哪些数据同时存储在 应用 和 请求 上下文中
- 如何在上下文中正确使用
current_app
,test_request_context
和test_client
您还应该能够修复以下错误:
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 some way. To solve
this, set up an application context with app.app_context().
Flask中的上下文
与 Django 和其他 Web 框架不同,Flask 视图函数不接受包含 HTTP 请求元数据的请求对象。
Django例子:
def users(request):
if request.method == 'POST':
# Save the form data to the database
# Send response
else:
# Get all users from the database
# Send response
使用 Flask,您可以像下面这样导入 request
对象:
from flask import request
@app.route('/users', methods=['GET', 'POST'])
def users():
if request.method == 'POST':
# Save the form data to the database
# Send response
else:
# Get all users from the database
# Send response
在 Flask
示例中,请求对象看起来、感觉起来和行为都像一个全局变量,但它不是。
如果请求对象是一个全局变量,那么您将无法运行一个多线程 Flask 应用,因为全局变量不是线程安全的。
相反,Flask 使用上下文使得许多对象仅在特定上下文(线程、进程或协同程序)中起到类似全局的作用。在 Flask 中,这称为Context Local)。
Context locals与 Python 用于存储特定于线程的数据的thread-local实现相似,但最终不同。Flask的实现更加通用,以允许工作线程为线程、进程或协程。
Flask
上下文中存储的数据
当接收到请求时,Flask 提供两个上下文:
上下文 | 描述 | 可用对象 |
---|---|---|
应用上下文 | 跟踪应用级数据(配置变量、日志记录器、数据库连接) | current_app , g |
请求上下文 | 跟踪请求级别的数据(URL、 HTTP 方法、请求头、请求数据、会话信息) | request , session |
值得注意的是,上面的每个对象通常都被称为“代理”。这仅仅意味着它们是对象的全局风格的代理。关于这方面的更多信息,请查看本系列的第二篇文章。
Flask在接收请求时处理这些上下文的创建。由于应用程序的状态不同,您并不总是可以访问特定的对象,因此它们可能会引起混淆。
我们来看几个例子。
应用上下文示例
假设你有以下Flask应用程序:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Welcome!'
if __name__ == '__main__':
app.run()
首先,让我们看看如何使用 current_app 对象访问应用上下文。
Within the Python shell, if you try to access the current_app.config
object outside of a view function, you should see the following error:
在python shell中,如果您尝试在视图函数之外访问 current_app.config
对象,会看到以下的错误:
$ python
>>> from flask import current_app
>>> current_app.config
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "werkzeug/local.py", line 347, in __getattr__
return getattr(self._get_current_object(), name)
File "werkzeug/local.py", line 306, in _get_current_object
return self.__local()
File "flask/globals.py", line 52, in _find_app
raise RuntimeError(_app_ctx_err_msg)
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 some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
要在视图函数之外访问应用上下文和请求上下文对象,您需要先创建适当的上下文
# without a context manager
$ python
>>> from app import app
>>> from flask import current_app
>>>
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>>
>>> current_app.config["ENV"]
'production'
>>> app_ctx.pop()
>>>
# with a context manager
$ python
>>> from app import app
>>> from flask import current_app
>>>
>>> with app.app_context():
... current_app.config["ENV"]
...
'production'
>>>
请求上下文示例
您可以使用 test_request_context 方法创建请求上下文:
# without a context manager
$ python
>>> from app import app
>>> from flask import request
>>>
>>> request_ctx = app.test_request_context()
>>> request_ctx.push()
>>>
>>> request.method
'GET'
>>>
>>> request.path
'/'
>>>
>>> request_ctx.pop()
>>>
# with a context manager
$ python
>>> from app import app
>>> from flask import request
>>>
>>> with app.test_request_context('/'):
... request.method
... request.path
...
'GET'
'/'
>>>
test_request_context
通常在测试过程中使用,当您希望使用请求数据但又不想承受完整请求开销时,这个函数就会派上用场。
测试例子
在应用程序进行测试时,最常遇到应用上下文和请求上下文问题的情况是:
import pytest
from flask import current_app
from app import app
@pytest.fixture
def client():
with app.test_client() as client:
assert current_app.config["ENV"] == "production" # Error!
yield client
def test_index_page(client):
response = client.get('/')
assert response.status_code == 200
assert b'Welcome!' in response.data
运行测试时,测试将在 fixture 中失败:
$ pytest
________________________ ERROR at setup of test_index_page _____________________
@pytest.fixture
def client():
with app.test_client() as client:
> assert current_app.config["ENV"] == "production"
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
def _find_app():
top = _app_ctx_stack.top
if top is None:
> raise RuntimeError(_app_ctx_err_msg)
E RuntimeError: Working outside of application context.
E
E This typically means that you attempted to use functionality that needed
E to interface with the current application object in some way. To solve
E this, set up an application context with app.app_context(). See the
E documentation for more information.
================================= 1 error in 0.13s =================================
要修复这个问题,请在访问 current_app
之前创建一个应用上下文:
import pytest
from flask import current_app
from app import app
@pytest.fixture
def client():
with app.test_client() as client:
with app.app_context(): # New!!
assert current_app.config["ENV"] == "production"
yield client
def test_index_page(client):
response = client.get('/')
assert response.status_code == 200
assert b'Welcome!' in response.data
总结
总结一下,在视图函数、CLI命令和测试函数中使用以下对象:
对象 | 上下文 | 常见错误 | 解决方案 |
---|---|---|---|
current_app | 应用环境 | 在应用上下文之外工作 | with app.app_context(): |
g | 应用环境 | 在应用上下文之外工作 | with app.test_request_context('/'): |
request | 请求上下文 | 在请求上下文之外工作 | with app.test_request_context('/'): |
session | 请求上下文 | 在请求上下文之外工作 | with app.test_request_context('/'): |
在测试时应使用下列方法:
Flask 方法 | Description描述 |
---|---|
test_client | Flask 应用的测试客户端 |
test_request_context | 为测试推送请求上下文 |
结论
这篇博客文章只是浅尝辄止地介绍了应用程序和请求上下文。请务必查看本系列的第二部分,以深入了解:深入探讨Flask的应用程序和请求上下文。