Thi*_*ter 14 python postgresql sqlalchemy commit flask-sqlalchemy
我有一个表示文件的SQLAlchemy模型,因此包含实际文件的路径.由于删除了数据库行和文件(因此没有留下孤立的文件,没有行指向已删除的文件)delete(),我在模型类中添加了一个方法:
def delete(self):
if os.path.exists(self.path):
os.remove(self.path)
db.session.delete(self)
Run Code Online (Sandbox Code Playgroud)
这很好但有一个很大的缺点:在提交包含数据库删除的事务之前立即删除该文件.
一种选择是提交delete()方法 - 但我不想这样做,因为我可能没有完成当前的事务.所以我正在寻找一种延迟删除物理文件的方法,直到实际提交删除行的事务为止.
SQLAlchemy有一个after_delete事件,但根据文档,这是在发出SQL时触发的(即在刷新时),这太早了.它也有一个after_commit事件,但此时事务中删除的所有内容都可能已从SA中删除.
Thi*_*ter 15
在使用Flask-SQLAlchemy的Flask应用程序中使用SQLAlchemy时,它提供了一个接收元组列表的models_committed信号(model, operation).使用这个信号做我正在寻找的东西非常容易:
@models_committed.connect_via(app)
def on_models_committed(sender, changes):
for obj, change in changes:
if change == 'delete' and hasattr(obj, '__commit_delete__'):
obj.__commit_delete__()
Run Code Online (Sandbox Code Playgroud)
使用这个泛型函数,每个需要on-delete-commit代码的模型现在只需要一个方法__commit_delete__(self)并在该方法中做任何需要做的事情.
它也可以在没有Flask-SQLAlchemy的情况下完成,但是,在这种情况下它需要更多的代码:
after_delete事件完成的.after_commit事件完成的.这跟随其他基于事件的答案,但我想我会发布这个代码,因为我写它来解决几乎你的确切问题:
代码(下面)注册一个SessionExtension类,它在发生刷新时累积所有新的,已更改的和已删除的对象,然后在实际提交或回滚会话时清除或评估队列.对于具有外部文件附接的类,然后我实现obj.after_db_new(session),obj.after_db_update(session)和/或obj.after_db_delete(session)方法,其中SessionExtension调用适当; 然后,您可以填充这些方法来处理创建/保存/删除外部文件.
注意:我几乎肯定这可以使用SqlAlchemy的新事件系统以更干净的方式重写,它还有一些其他缺陷,但它在生产和工作中,所以我没有更新它:)
import logging; log = logging.getLogger(__name__)
from sqlalchemy.orm.session import SessionExtension
class TrackerExtension(SessionExtension):
def __init__(self):
self.new = set()
self.deleted = set()
self.dirty = set()
def after_flush(self, session, flush_context):
# NOTE: requires >= SA 0.5
self.new.update(obj for obj in session.new
if hasattr(obj, "after_db_new"))
self.deleted.update(obj for obj in session.deleted
if hasattr(obj, "after_db_delete"))
self.dirty.update(obj for obj in session.dirty
if hasattr(obj, "after_db_update"))
def after_commit(self, session):
# NOTE: this is rather hackneyed, in that it hides errors until
# the end, just so it can commit as many objects as possible.
# FIXME: could integrate this w/ twophase to make everything safer in case the methods fail.
log.debug("after commit: new=%r deleted=%r dirty=%r",
self.new, self.deleted, self.dirty)
ecount = 0
if self.new:
for obj in self.new:
try:
obj.after_db_new(session)
except:
ecount += 1
log.critical("error occurred in after_db_new: obj=%r",
obj, exc_info=True)
self.new.clear()
if self.deleted:
for obj in self.deleted:
try:
obj.after_db_delete(session)
except:
ecount += 1
log.critical("error occurred in after_db_delete: obj=%r",
obj, exc_info=True)
self.deleted.clear()
if self.dirty:
for obj in self.dirty:
try:
obj.after_db_update(session)
except:
ecount += 1
log.critical("error occurred in after_db_update: obj=%r",
obj, exc_info=True)
self.dirty.clear()
if ecount:
raise RuntimeError("%r object error during after_commit() ... "
"see traceback for more" % ecount)
def after_rollback(self, session):
self.new.clear()
self.deleted.clear()
self.dirty.clear()
# then add "extension=TrackerExtension()" to the Session constructor
Run Code Online (Sandbox Code Playgroud)