TL; DR:请务必使用拆解包装配方,确保拆解功能成功!
我还使用Flask开始了一项新工作,在我准备好拆包食谱之前,这个问题再次弹出。因此,我重新审视了这个问题,并最终弄清了发生了什么。
正如我认为的那样,每当新请求下线时,Flask都会将新的请求上下文推送到请求上下文堆栈中。这用于支持请求本地全局变量,例如会话。
Flask还具有与请求上下文分离的“应用程序”上下文概念。它旨在支持未发生HTTP的测试和CLI访问等功能。我知道这一点,而且我也知道那是Flask-sqlA放置其数据库会话的地方。
在正常操作期间,请求和应用程序上下文都在请求开始时被推送,并在结束时弹出。
但是,事实证明,当推送请求上下文时,请求上下文会检查是否存在现有的应用程序上下文,如果存在,则不会推送新的应用程序上下文!
因此,如果由于拆解功能的提高而在请求结束时未弹出应用程序上下文,那么它不仅会永远存在,甚至不会在其之上放置新的应用程序上下文。
这也解释了我在集成测试中没有理解的一些魔术。你可以插入一些测试数据,然后运行一些请求,即使你不提交,这些请求也将能够访问该数据。因为请求具有新的请求上下文,但由于重用了测试应用程序上下文,所以这是可能的,因此它重用了现有的数据库连接。因此,这确实是一个功能,而不是错误。
就是说,这确实意味着你必须绝对确保你的拆卸功能能够成功使用下面的拆卸功能包装器之类的东西。即使没有该功能,这也是一个好主意,以避免内存和数据库连接泄漏,但是鉴于这些发现,这一点尤其重要。因此,我将向Flask的文档提交PR。
我们最后放置的一件事是下面的代码(在我们的应用程序工厂中),该代码包装了所有拆卸函数以确保它记录了异常且不会进一步引发。这样可以确保始终成功弹出应用上下文。显然,这必须在你确定所有拆卸功能都已注册之后进行。
# Flask specifies that teardown functions should not raise.
# However, they might not have their own error handling,
# so we wrap them here to log any errors and prevent errors from
# propagating.
def wrap_teardown_func(teardown_func):
@wraps(teardown_func)
def log_teardown_error(*args, **kwargs):
try:
teardown_func(*args, **kwargs)
except Exception as exc:
app.logger.exception(exc)
return log_teardown_error
if app.teardown_request_funcs:
for bp, func_list in app.teardown_request_funcs.items():
for i, func in enumerate(func_list):
app.teardown_request_funcs[bp][i] = wrap_teardown_func(func)
if app.teardown_appcontext_funcs:
for i, func in enumerate(app.teardown_appcontext_funcs):
app.teardown_appcontext_funcs[i] = wrap_teardown_func(func)