摘要:最近在開發的時候遇到這樣一個問題我就好奇了這樣還報不在中的錯沒有顯示調用啊加一行測試無奈,一個一個翻到之間調用的每一個函數,終于在找到可疑點但是這里也沒有顯式提交。為什么接下來總結下大神們的探討。
最近在開發mdwiki的時候遇到這樣一個問題.Post is unbond to session.
我就好奇了
post=Post.query.filter_by(location=location).first() abspath=util.getAbsPostPath(post.location) tagsList=[] ... print(post in session) #False post.tags=tagsList
這樣還報post不在session中的錯?沒有顯示調用db.session.commit()啊.
加一行測試:
print(post in session) #False
無奈,一個一個翻post=Post.query.filter_by(location=location).first()到post.tags=tagsList之間調用的每一個函數,終于在util.getAbsPostPath找到可疑點
def getAbsPostPath(location): with current_app.app_context(): abspath=os.path.join(current_app.config["PAGE_DIR"],location.replace("/",os.sep))+".md" return abspath
但是這里也沒有顯式提交。只是多push了一個app_context,也不至于這樣吧?
無奈之下查看Flask-SQLAlchemy源碼,還好這貨只有兩個文件,比較少。
有這么一段:
# 0.9 and later if hasattr(app, "teardown_appcontext"): teardown = app.teardown_appcontext # 0.7 to 0.8 elif hasattr(app, "teardown_request"): teardown = app.teardown_request # Older Flask versions else: if app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"]: raise RuntimeError("Commit on teardown requires Flask >= 0.7") teardown = app.after_request @teardown def shutdown_session(response_or_exc): if app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"]: if response_or_exc is None: self.session.commit() self.session.remove() return response_or_exc
這下明白了,原理是它監聽了app.teardown_appcontext事件,在該事件發生時會調用
self.session.remove()移除session。這樣一來把這一行注釋掉,直接使用config模塊就解決問題了
def getAbsPostPath(location): # with current_app.app_context(): abspath=os.path.join(config.PAGE_DIR,location.replace("/",os.sep))+".md"
但同時看到了這個選項SQLALCHEMY_COMMIT_ON_TEARDOWN,是不是Flask-SQLAlchemy可以配置請求執行完邏輯之后自動提交,而不用我們每次都手動調用session.commit()?通過源碼看答案是肯定的。
但是好奇的我還是google之,然后在github上看到了這樣幾段有趣的討論:先貼地址
https://github.com/mitsuhiko/...
https://github.com/mitsuhiko/...
https://github.com/rosariomgo...
然后在官網看到這樣一段:
Consider SQLALCHEMY_COMMIT_ON_TEARDOWN harmful and remove from docs.
什么?考慮移除這一特性?
剛剛知道這么方便的特性,準備用來著,就要被移除?更何況源碼中也沒有提示要移除啊。
其實是這樣的,作者準備在3.0版本移除SQLALCHEMY_COMMIT_ON_TEARDOWN這一特性,目前自2.1以后從文檔中移除了相關介紹。
為什么?接下來總結下大神們的探討。
mattupstate commented on 31 Jan 2015: I"d guess that the reason is due
to the teardown_appcontext callback carrying a bug that, even if you
catch an exception during the app context, the response_or_exc will
never be None. In other words, teardown_appcontext suffers from a
general Python exception handling bug.
這位mattupstate說teardown_appcontext回調存在一個bug,就是即使你正確地捕獲了所有的bug,但是回調函數的第一個參數response_or_exc仍然不會為None。這一點令人費解。于是我試驗了一發,包括沒有bug的情形,主動拋出并捕獲的情形,以及after_request中捕獲并拋出的情形,都發現response_or_exc為None,沒有重現他所說的。why?好想知道為什么。猜測可能是我Python版本,Flask版本的關系?繼續看吧
immunda commented on 3 Feb 2015 Sorry for the silence on this. Yep,
that"s the motivation, moving away from the (flawed) magic. I"m
waiting to deprecate it entirely (3.0), because there"s plans to
introduce a more explicit transaction decorator first.
我去,連Flask-SQLAlchemy作者都支持這一觀點,好吧,雖然我沒有重現該問題,但是還是就這么認為吧,不用這個特性了。但是還是好奇地看了一下其他的觀點。
原來實際上問題是這樣的,見https://github.com/mitsuhiko/...
先貼上FLask-SQLAlchemy那部分代碼:
@teardown def shutdown_session(response_or_exc): if app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"]: if response_or_exc is None: self.session.commit() self.session.remove() return response_or_exc
如果在app.teardown_request中或者在self.session.commit()時發生異常,而這個異常在這里并沒有被捕獲,那么self.session.remove()也就沒有執行,那么這就會影響到下一個請求,下一個請求獲取到的session其實是上一個帶回滾狀態的session,從而導致請求沒有按預期效果執行而失敗。至此問題算明白了。并不是mattupstate這哥們形容的那樣。那么這應該是flask實現機制導致的吧。繼續挖。
https://github.com/pallets/fl...
http://stackoverflow.com/ques...
這哥們garaden給Flask提交了代碼合并請求,關鍵部分如下
+ 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)
如果合并了這部分代碼之后,那么以后注冊app.teardown_request和app.teardown_appcontext,時異常將會自動被捕獲。這在https://github.com/pallets/fl...可以看到新版本Flask已經合并了這部分代碼,不存在該問題了。
后面討論看到
Recently PR pallets/flask#1822 got merged into Flask. Will this maybe
change the fact whether SQLALCHEMY_COMMIT_ON_TEARDOWN will still be
removed in future?
但這對于解決FLask-SQLAlchemy中的問題好像還是沒有幫助?是不是我理解錯了?如果session.commit發生異常,session.remove這樣還是不會執行?
后面看了https://github.com/pallets/fl...中的代碼,優化了application context從棧中pop的邏輯,這次的代碼提交確保了tear_down回調處理發生異常時不會導致application context無法從棧中彈出而影響后續請求。這下大致明白了。Flask-SQLAlchemy中的db.session依賴于Application Context,所以如果這次Flask能確保無論如何最后會正確彈出application context,那么db.session也隨之銷毀了,那就不存在后續的影響了。但是,最后這句話我也不敢保證,只能是猜想。
所以,言歸正傳,如果不用SQLALCHEMY_COMMIT_ON_TEARDOWN這一特性,那么我們怎么確保每次自動提交session呢?
第一種:不是自動,全手動模式commit(),看討論還是有很多人喜歡這種方式的,不過我討厭每次都調用commit()
第二種:在after_request中進行提交commit,在teardown_request進行remove
雖說Flask已經修正不需要捕獲也可以,但是為了編碼的優雅(暫時找不到好點的詞),還是在dbsession_clean中進行了異常捕獲。
@app.after_request def after_clean(resp,*args,**kwargs): db.session.commit() return resp @app.teardown_request def dbsession_clean(exception=None): try: db.session.remove() finally: pass
第三種:使用自定義裝飾器
def route(app_or_sub,rule,**options): def decorator(f): @wraps(f) def decorated_view(*args,**kwargs): res=f(*args,**kwargs) db.session.commit() return res endpoint = options.pop("endpoint", None) app_or_sub.add_url_rule(rule, endpoint, decorated_view, **options) return decorated_view return decorator
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/38270.html
摘要:程序中最常用的莫過于關系型數據庫了,也稱數據庫。對象是類的實例,表示程序使用的數據庫。本文由發表于個人博客,采用自由轉載保持署名非商用禁止演繹協議發布。非商業轉載請注明作者及出處。本文標題為插件系列本文鏈接為更多閱讀 簡介 Web 開發中,一個重要的組成部分便是數據庫了。Web 程序中最常用的莫過于關系型數據庫了,也稱 SQL 數據庫。另外,文檔數據庫(如 mongodb)、鍵值對數據...
摘要:以下內容介紹了的基礎查詢語句,下篇文章將介紹其高級查詢聚合自關聯連接子查詢等模型類用戶表地址信息關聯表商品信息訂單表增一對一構建對象添加對象提交事務一對多主表子表子表賦值對象添加提交多對多生成或獲取商品對象生成訂單對象訂單表與商品表關聯添加 以下內容介紹了Sqlalchemy的基礎查詢語句,下篇文章將介紹其高級查詢(聚合、自關聯、連接、子查詢等) 模型類 # 用戶表 class Use...
摘要:關系關系數據庫通過使用關系在不同的表中建立連接。以下部分將介紹最常見的數據庫操作。如果數據庫已存在函數不會重新創建或更新數據庫表。到目前為止對象只存于中,他們還沒有被寫入數據庫。數據庫會話也叫事務。刪除行數據庫會話同樣有方法。 7、關系 關系數據庫通過使用關系在不同的表中建立連接。圖像5-1的關系圖表達了用戶和用戶角色之間的簡單關系。這個角色和用戶是一對多關系,因為一個角色可以從屬于...
摘要:關系關系數據庫通過使用關系在不同的表中建立連接。以下部分將介紹最常見的數據庫操作。如果數據庫已存在函數不會重新創建或更新數據庫表。到目前為止對象只存于中,他們還沒有被寫入數據庫。數據庫會話也叫事務。刪除行數據庫會話同樣有方法。 7、關系 關系數據庫通過使用關系在不同的表中建立連接。圖像5-1的關系圖表達了用戶和用戶角色之間的簡單關系。這個角色和用戶是一對多關系,因為一個角色可以從屬于...
閱讀 2529·2021-09-24 10:29
閱讀 3810·2021-09-22 15:46
閱讀 2580·2021-09-04 16:41
閱讀 2986·2019-08-30 15:53
閱讀 1265·2019-08-30 14:24
閱讀 3058·2019-08-30 13:19
閱讀 2174·2019-08-29 14:17
閱讀 3526·2019-08-29 12:55