2017-05-25 32 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的应用程序实例来使用模型。 –

相关问题