让flask在出现语法错误时仍然自动重启

1问题描述:

flask自带的reload只能在语法没毛病的情况下auto_relaod,但是如果有语法错误,进程就会报错退出。

这时修改完语法错误,还得在控制台按“↑”和“enter”重新执行一次python3 app.py 重新启动flask。

希望省掉手动重启flask的过程

2解决方案

用flask-failsafe插件。

这样语法错误也会reload,语法错误修正,系统继续运行。这样手和光标不用频繁切到控制台,也省了“↑”和“enter”的按键。懒人专用。

这个插件使用很简单:

myapp模拟flask应用。main.py作为启动脚本。核心就是装饰器,监控各种文件改动。然后reraise。

3原理

(好奇心不强的可以不看)

虽然flask_failsafe是代码只有80行的一个单独py文件,但是还不是太能看懂啊啊……把我理解的作为注释加进去了:

import functools
import
sys import traceback import flask PY2 = sys.version_info[0] == 2 def failsafe(func): """ Wraps an app factory to provide a fallback in case of import errors. Takes a factory function to generate a Flask app. If there is an error creating the app, it will return a dummy app that just returns the Flask error page for the exception. This works with the Flask code reloader so that if the app fails during initialization it will still monitor those files for changes and reload the app. """ @functools.wraps(func) def wrapper(*args, **kwargs): extra_files = [] try:
    #如果create_app运行不报错。就直接在开头退出。
return func(*args, **kwargs) except:
#捕获异常,并打印 exc_type, exc_val, exc_tb
= sys.exc_info() traceback.print_exc()
#添加异常中依赖的文件,和“SyntaxError”爆出来的文件。 tb
= exc_tb while tb: filename = tb.tb_frame.f_code.co_filename extra_files.append(filename) tb = tb.tb_next if isinstance(exc_val, SyntaxError): extra_files.append(exc_val.filename) #启动dummy app,专门用来显示错误的文件 app = _FailSafeFlask(extra_files) app.debug = True @app.route('/') @app.route('/<path:path>') def index(path='/'): reraise(exc_type, exc_val, exc_tb) return app return wrapper if PY2: exec('def reraise(tp, value, tb=None): raise tp, value, tb') else: def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value class _FailSafeFlask(flask.Flask): """ Binds the extra_args parameter of run() to include the extra files we want to monitor for changes. """ def __init__(self, extra_files): flask.Flask.__init__(self, __name__) self.extra_files = extra_files def run(self, *args, **kwargs): extra_files = self.extra_files if 'extra_files' in kwargs: extra_files = extra_files + kwargs['extra_files'] kwargs['extra_files'] = extra_files flask.Flask.run(self, *args, **kwargs)

关键好像是最后2行 flask自己会跟踪'extra_files'文件的改动。又百度了一下,准确地说是werkzeug用子进程实现的这个功能。后面会简单说到原理。

当extra_files有改动后,dummy_app跟踪改动,flask的auto_loader机制会保证dummy_app先退出,再重启

关键就是这个重启,为什么能重复执行我们写的create_app,而不是简单重启dummy_app.run()呢?

根据flask的auto_reloader机制的讲解,

当从进程执行到run_with_reloader时,便进入了条件分支,而不会再无限启新的「从进程」耗光资源。而分支内部可以看到,开了一条线程来跑main_func也就是启动web服务的方法,紧接着启动reloader_loop也就是监视文件改动的循环。

大概是:werkzeug的自动重启机制是:用我们传进去的命令行参数,通过subprocess建立新的python解释器。也就是在新进程执行"python3 main.py"

这也就保证了main.py里我们的create_app()会在文件被修改后执行,这个和flask-failsafe没有关系。

对比一下重启过程:

普通的flask app是这样的:

1文件被改动,werkzeug的子进程会监控到改动,然后报exitcode 3退出

2 werkzeug试图重启subprocess(用我们开始传入的参数python main.py 执行create_app() 和app.run())

3 如果重启中出现语法错误,就会报错非exitcode 3,先退出子进程,再退出主进程;

而文艺的flask-failsafe 1 2是一样的,但之后就不一样了,它要实现“如果create_app失败,不会退出主进程,继续监控出错的文件,出错文件被改动后试图重新运行create_app”:

3 如果重启时在create_app里出现语法错误,捕获异常,根据异常报出来的文件构造extra_files

4 用extra_files参数启动一个dummy_app代替用户本来想创建的app

5 dummy_app.run()正常运行,但其实dummy_app什么都不做,因为这时有文件是错的。

6如果有文件改动了,重复1

这个dummy_app巧妙性在于,充分利用了werkzeug的强大的自动重启机制:

  1它会正常启动,正常run。因为它本身并没有import或用到这些出错的extra_files,只是个超级简单的app

       2它保留了我们传入的arg和kwarg用于启动subprocess。在这一点上,它的行为和原生的app一样,所以它一旦退出,subprocess重启,会导致重启我们写的create_app,试图创建我们的app, 而不是简单重建自己。

      3它会跟踪错误的extra_files的改动而触发werkzeug的重启机制。

所以,如果没有werkzeug基于subprocess,重启新解释器的重启机制,是没法实现重新执行create_app的。

以我的水平,理解到这样就基本满意啦。

原文地址:https://www.cnblogs.com/xuanmanstein/p/7635007.html