为什么我的 Flask 应用程序的全局对象在应用程序拆卸时没有被删除?

Why isn't my Flask application's global object deleted on app teardown?

我正在编写一个带有全局对象的应用程序,该对象的寿命应与应用程序的寿命一样长。不同的端点应该改变全局对象。

下面是我的服务器,其中包含一个模型对象示例,将在虚拟端点调用时发生变化。

server.py

#!/usr/bin/env python3

from flask import Flask, g, request

class Foo(object):
    def __init__(self):
        self.bar = None

    def add_bar(self, bar):
        if self.bar is not None:
            raise Exception("You blew it!")
        self.bar = bar

def create_app():
    app = Flask(__name__)
    return app

app = create_app()

def get_foo():
    foo = getattr(g, '_foo', None)
    if foo is None:
        print("foo is None. Creating a foo")
        foo = g._foo = Foo()
    return foo

@app.teardown_appcontext
def teardown_foo(exception):
    foo = getattr(g, '_foo', None)
    if foo is not None:
        print("Deleting foo")
        del foo

@app.route("/add_bar", methods=['POST'])
def bar():
    bar = request.form.get("bar")
    foo.add_bar(bar)
    return "Success"    

if __name__ == "__main__":
    app = create_app()
    app.run('localhost', port=8080)

作为一名优秀的软件开发人员,我想测试一下。这是我的测试套件:

test.py

#!/usr/bin/env python3

from server import *
import unittest

class FlaskTestCase(unittest.TestCase):
    def setUp(self):
        app.config['TESTING'] = True
        print("Creating an app...")
        self.app = create_app()
        print("Created an app...")
        with self.app.app_context():
            self.foo = get_foo()

    def tearDown(self):
        del self.foo

    def test_add_bar(self):
        with self.app.test_client() as client:
            client.post("/add_bar", data={'bar': "12345"})
            assert self.foo.bar == "12345"

if __name__ == "__main__":
    unittest.main()

当我 运行 测试时,我注意到我的 foo 对象从未被删除(通过打印)。这是我 运行 测试套件时的输出:

13:35 $ ./test.py
Creating an app...
Created an app...
foo is None. Creating a foo
F
======================================================================
FAIL: test_add_bar (__main__.FlaskTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test.py", line 21, in test_add_bar
    assert self.foo.bar == "12345"
AssertionError

----------------------------------------------------------------------
Ran 1 test in 0.009s

FAILED (failures=1)

我一定是在应用程序上下文方面对我的全局对象做了一些不正确的事情。我仔细研究了 testing documents,但似乎找不到我需要的东西。

当应用程序上下文超出范围时(就像在我的测试中一样),我如何确保我的 foo 对象被销毁?为什么我的单元测试套件不改变其在 setUp 上创建的 foo 对象?

我以为可能是在修改server.py内的全局app对象,结果打印foo的时候发现在我的测试范围内没有定义套房。

因为del foo只解绑变量foo。如果您有 foo = g._foo,那么调用 del foo 不会解除绑定 g._foo,而只会解除 foo 本身。

del 不用于删除对象。它从作用域中释放一个变量名。

这是一个奇怪的问题。

问题是 server.py 中的全局 app 对象与原始 test.py 中创建的应用程序之间的干扰。具体来说,由于正在导入应用程序,因此它的生命周期就是测试套件的生命周期。

我使用 flask.Blueprint 删除了全局 app 对象。这是最后的 server.py:

#!/usr/bin/env python3

from flask import Flask, g, request, Blueprint

main = Blueprint('main', __name__)

def get_foo():
    foo = getattr(g, 'foo', None)
    if foo is None:
        foo = g.foo = Foo()
    return foo

@main.before_request
def before_request():
    foo = get_foo()

@main.route("/add_bar", methods=['POST'])
def bar():
    bar = request.form.get("bar")
    g.foo.add_bar(bar)
    return "Success"

class Foo(object):
    def __init__(self):
        self.bar = None

    def add_bar(self, bar):
        if self.bar is not None:
            raise Exception("You blew it!")
        self.bar = bar

def create_app():
    app = Flask(__name__)
    app.register_blueprint(main)
    @app.teardown_appcontext
    def teardown_foo(exception):
        if g.foo is not None:
            del g.foo
    return app

if __name__ == "__main__":
    app = create_app()
    with app.app_context():
        foo = get_foo()
        app.run('localhost', port=8080)

这是最后的 test.py:

#!/usr/bin/env python3

from server import create_app, get_foo
import unittest

class FlaskTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app()
        self.app_context = self.app.app_context()
        self.app_context.push()
        self.client = self.app.test_client()
        self.foo = get_foo()

    def tearDown(self):
        self.app_context.pop()
        del self.foo

    def test_add_bar_success(self):
        assert self.foo.bar is None
        self.client.post("/add_bar", data={'bar': "12345"})
        assert self.foo.bar == "12345"

    def test_foo_reset_on_new_test(self):
        assert self.foo.bar is None

if __name__ == "__main__":
    unittest.main()

test_foo_reset_on_new_test 说明与测试套件关联的 foo 对象在每次测试时都会重置。

所有测试都通过。