2017-05-25 62 views
0

我有一個端點來從我的數據庫中刪除一個對象。我用下面的代碼刪除:SQLAlchemy在刪除後仍然能夠從會話中獲取對象

my_object = Object.query.get(id) 
db.session.delete(my_object) 
db.session.commit() 
return json.dumps({'success': True} 

我有一個API測試,測試,我創建一個對象端點,然後使用端點將其刪除。我試圖在發生刪除後斷言它不在數據庫中。

my_object = MyObject() 
db.session.add(my_object) 
db.session.commit() 
response = requests.delete('{}/my-object/{}'.format(
    os.environ.get('MY_URL'), 
    my_object.id 
)) 
self.assertTrue(response.json()['success']) // this passes 
self.assertEqual(200, response.status_code) // this passes 
result = db.session.query(MyObject).get(my_object.id) 
print(result.id) // prints the id of the job even though it is deleted from the database 

我認爲這與某些SQLAlchemy會話緩存有關。我試過db.session.flush()db.session.expire_all()無濟於事。該對象實際上是從數據庫中刪除的。所以我希望查詢結果是None

我在文檔中看到了這一點,但還沒有完全包裹我的頭。 when to commit and close a session

感謝您的幫助。

+0

您是否曾嘗試在查詢之前但在刪除之後提交?或者,也許開一個新的會議會更清潔。 – 9000

+1

有兩個問題可以幫助縮小這個問題:你是否在同一個進程中運行你的服務器?在每次測試之後,您的測試框架是否使用事務將數據庫回滾到之前的狀態? –

+0

@ 9000我不確定我是否理解第一個問題。我可以嘗試打開一個新的會話,看看是否有效。 – brandonbanks

回答

2

因此,在您的測試代碼中,您將對象添加到會話並提交它。它被保存到數據庫,並且是您會話的身份映射。

然後你打你的應用程序,它有它自己的會話。它刪除對象並提交,現在它已從數據庫中消失。但是...

您以前的會話對此沒有任何瞭解,當您使用.get()時,它將返回其身份映射中的內容:帶有ID的Python對象。除非關閉會話或強制刷新數據庫,否則它將不會重新讀取(我不記得OTOH如何執行此操作,但可以,它位於文檔的某處)。如果你使用了一個乾淨的第三個會話,它會有一個新的標識映射,並且不會保留對python對象的引用,所以你會得到你所期望的,即。沒有結果。這是由設計決定的,因爲Identity Map允許SQLAlchemy將一系列更改鏈接到一個最佳SQL查詢中,該查詢僅在您提交時纔會觸發。

所以是的,你看到從身份地圖,仍然活着的提取。 (你甚至可以在交互式解釋器中彈出它,並且可以捅過去)而且它是有道理的,因爲假設你有兩個不同的Web請求線程,另一個是在另一個請求刪除對象時用對象做一些更長時間的東西。第一個線程不應該在使用該對象的Python代碼上使用barf,因爲無論您身在何處,都會觸發隨機異常。它應該認爲它可以做到這一點,然後在提交時失敗,觸發回滾。

HTH

+0

所以我有兩個會話。當我打開請求時,一個在測試中,另一個在應用中。所以我需要刪除會話並創建一個新的會話來測試它? – brandonbanks

+0

這有助於以這種方式思考。我無法從數據庫中刷新數據。我嘗試過'db.session.refresh(db.session.query(MyObject).get(my_object.id))',但即使對象存在於數據庫中,也會返回None。 – brandonbanks

0

db.session.expunge_all()

每個請求db.session後, 「從這個會話刪除所有對象實例......」

http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.expunge_all

或者簡單觸發.remove()

例如在Flask中使用SQLAlchemy scoped_session:

@app.teardown_appcontext 
def shutdown_session(exception=None): 
    db.session.remove() 

「一如既往,scoped_session.remove()方法會刪除與該線程關聯的當前Session(如果有)。然而,threading.local()對象的一個​​優點是,如果應用程序線程本身結束,那麼線程的「存儲」也會被垃圾收集。因此,使用線程本地作用域和一個生成和拆除線程的應用程序實際上是「安全的」,而無需調用scoped_session.remove()。然而,事務本身的範圍,即通過Session.commit()或Session.rollback()結束它們,通常仍然是必須在適當的時候明確安排的事情,除非應用程序實際上綁定了線程的生命週期到交易的壽命。」

http://docs.sqlalchemy.org/en/latest/orm/contextual.html#thread-local-scope http://docs.sqlalchemy.org/en/latest/orm/contextual.html#using-thread-local-scope-with-web-applications

+0

我試過'db.session.expunge_all()',我得到一個'DetachedInstanceError'。說對象不會綁定到一個會話。 – brandonbanks

1

了相當數量的閱讀和測試,我發現創建一個新的會話是最簡單的解決方案後,我無法弄清楚如何刷新即使記錄已過時,數據庫中的記錄也是如此。

下面是我做的一些閱讀:

when to commit and close a session

is the session a cache

is the session thread safe

understanding sqlalchemy session

這是我如何通過創建一個新的數據庫連接,並使用Flask-SQLAlchemy會議解決了這個問題:

[my other imports...] 
from flask.ext.sqlalchemy import SQLAlchemy 

[my other code...] 

def test_it_should_delete_my_object(self): 
    my_object = MyObject() 
    db.session.add(my_object) 
    db.session.commit() 
    response = requests.delete('{}/my-object/{}'.format(
     os.environ.get('MY_URL'), 
     my_object.id 
    )) 
    self.assertTrue(response.json()['success']) 
    self.assertEqual(200, response.status_code) 

    new_db = SQLAlchemy(app) // establishing a new connection 
    result = new_db.session.query(MyObject).get(my_object.id) // by using a new 
    self.assertIsNone(result) 

謝謝大家的幫助。要做更多的研究。

+1

是的,這就是你應該做的,關閉會議並創建新的會議。在SQLAlchemy中創建會話很便宜。對於什麼是值得的,我不推薦Flask-SQLAlchemy,我個人更喜歡使用普通的sqlalchemy。 Flask-SQLAlchemy不會以犧牲魔法爲代價增加許多便利。你會發現使用Flask-SQLAlchemy的人很多問題,他們對會話管理正在做什麼感到困惑。 –

+0

感謝您的意見。我會考慮使用SQLAlchemy。 – brandonbanks

+0

對於沒有Flask-SQLAlchemy的純SQLAlchemy的+1。您可以在應用程序之外使用會話和模型。例如在DB維護模塊的一些後臺任務中。使用Flask-SQLAlchemy,你將需要一個app_context的應用程序實例來使用模型。 –

相關問題