理解Flask中的应用和请求上下文

这篇文章的目的是为了阐明 Flask 中的应用上下文和请求上下文是如何工作的。


这是 Flask上下文系列文章的第一部分:

  1. 基础知识: 理解Flask中的应用上下文和请求上下文(本文!)
  2. 进阶: 深入理解Flask的应用上下文和请求上下文

内容

目标

在这篇文章的最后,你应该能够解释:

  1. Flask 如何处理请求对象,以及它与其他 Web 框架有何不同?
  2. 应用上下文和请求上下文是什么?
  3. 哪些数据同时存储在 应用 和 请求 上下文中
  4. 如何在上下文中正确使用 current_app, test_request_contexttest_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_clientFlask 应用的测试客户端
test_request_context为测试推送请求上下文

结论

这篇博客文章只是浅尝辄止地介绍了应用程序和请求上下文。请务必查看本系列的第二部分,以深入了解:深入探讨Flask的应用程序和请求上下文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值