使用 Flask dev 服务器重新加载器处理多个应用程序对象的 atexit

Handling atexit for multiple app objects with Flask dev server reloader

这是另一个烧瓶开发服务器重新加载器问题。有一百万个问题问为什么它加载所有东西两次,而这不是其中之一。我知道它会加载所有内容两次,我的问题涉及处理这个现实,但我还没有找到我认为可以解决我正在尝试做的事情的答案。

我的问题是,如何在退出时清除所有应用程序对象?

我目前的做法如下所示。在这个例子中,我 运行 我的清理代码使用了 atexit 函数。

from flask import Flask

app = Flask(__name__)
print("start_app_id: ", '{}'.format(id(app)))

import atexit
@atexit.register
def shutdown():
    print("AtExit_app_id: ", '{}'.format(id(app)))
    #do some cleanup on the app object here

if __name__ == "__main__":
    import os
    if os.environ.get('WERKZEUG_RUN_MAIN') == "true":
        print("reloaded_main_app_id: ", '{}'.format(id(app)))
    else:
        print("first_main_app_id: ", '{}'.format(id(app)))

    app.run(host='0.0.0.0', debug=True)

这段代码的输出结果如下:

start_app_id:  140521561348864
first_main_app_id:  140521561348864
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
start_app_id:  140105598483312
reloaded_main_app_id:  140105598483312
 * Debugger is active!
 * Debugger pin code: xxx-xxx-xxx
^CAtExit_app_id:  140521561348864

请注意,首次加载时,会创建一个 ID 为 '864 的应用程序对象。在自动重新加载期间,将创建 ID 为“312”的新应用程序对象。然后,当我按下 Ctrl-C(最后一行)时,atexit 例程被调用,原始的 '864 应用程序对象是可以使用 app 变量访问的对象——而不是较新的 '312 应用程序对象。

我希望能够在服务器关闭或被 Ctrl-C(在本例中为 '864 和 '312)时对所有浮动的应用程序对象进行清理。有关如何执行此操作的任何建议?

或者,如果我可以 运行 在重新加载后创建的较新的 '312 对象上进行清理,我也可以进行这项工作——但是我目前的方法只允许我清理原始应用程序对象。

谢谢。

UPDATE1: 我发现 link 建议使用 try/finally 而不是 atexit 挂钩来完成我上面打算做的事情。切换到这个会导致与 atexit 完全相同的行为,因此对我的问题没有帮助:

from flask import Flask

app = Flask(__name__)
print("start_app_id: ", '{}'.format(id(app)))

if __name__ == "__main__":
    import os
    if os.environ.get('WERKZEUG_RUN_MAIN') == "true":
        print("reloaded_main_app_id: ", '{}'.format(id(app)))
    else:
        print("first_main_app_id: ", '{}'.format(id(app)))

    try:
        app.run(host='0.0.0.0', debug=True)
    finally:
        print("Finally_app_id: ", '{}'.format(id(app)))
        #do app cleanup code here

在深入研究 werkzeug 源代码后,我找到了答案。答案是不可能做我想做的事——这是设计使然。

使用 flask 开发服务器 (werkzeug) 时,无法在终止时清除所有现有的应用程序对象(例如 ctrl-C),因为 werkzeug 服务器会捕获键盘中断异常并 "passes" 在其上。您可以在 run_with_reloader 函数中 werkzeug 的 _reloader.py 的最后几行中看到这一点:

def run_with_reloader(main_func, extra_files=None, interval=1,
                      reloader_type='auto'):
    """Run the given function in an independent python interpreter."""
    import signal
    reloader = reloader_loops[reloader_type](extra_files, interval)
    signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
    try:
        if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
            t = threading.Thread(target=main_func, args=())
            t.setDaemon(True)
            t.start()
            reloader.run()
        else:
            sys.exit(reloader.restart_with_reloader())
    except KeyboardInterrupt:
        pass

如果将上面的 "except KeyboardInterrupt:" 替换为 "finally:",然后 运行 原始问题中的第二个代码片段,您会发现两个创建的应用程序对象都被清理了如预期的。有趣的是,第一个代码片段(使用@atexit)在进行这些更改后仍然无法正常工作。

总之,您可以在使用 flask 开发服务器时清除所有现有的应用程序对象,但您需要修改 werkzeug 源才能这样做。