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):\n 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的。
以我的水平,理解到這樣就基本滿意啦。