在提交某种类型的模型后运行函数

时间:2015-09-03 14:47:01

标签: python flask sqlalchemy flask-sqlalchemy

我想在提交Post模型的实例时运行一个函数。我想在任何时间运行它,所以我宁愿不在任何地方明确调用该函数。我怎么能这样做?

def notify_subscribers(post):
    """ send email to subscribers """
    ...

post = Post("Hello World", "This is my first blog entry.")
session.commit() # How to run notify_subscribers with post as argument
                 # as soon as post is committed successfully?

post.title = "Hello World!!1"
session.commit() # Run notify_subscribers once again.

1 个答案:

答案 0 :(得分:3)

无论您在下面选择哪个选项,SQLAlchemy都会附带a big warning about the after_commit event(两种方式都会发送信号)。

  

调用after_commit()事件时,Session不在活动事务中,因此无法发出SQL

如果您的回调需要查询或提交数据库,则可能会出现意外问题。在这种情况下,您可以使用诸如Celery之类的任务队列在后台线程(具有单独的会话)中执行此任务。这可能是正确的方式,因为发送电子邮件需要很长时间,并且您不希望您的观点在发生时等待返回。

您可以收听的Flask-SQLAlchemy provides a signal发送所有插入/更新/删除操作。但是,the patch使得它实际上工作还没有在发布的版本中,你必须从git安装开发版本(信号存在于当前版本(2.0)中,但没有& #39; t正常工作)。如果您对此感到满意,请使用以下命令安装:

pip install https://github.com/mitsuhiko/flask-sqlalchemy/tarball/master

然后听取信号:

from flask_sqlalchemy import models_committed

def notify_subscribers(app, changes):
    new_posts = [target for target, op in changes if isinstance(target, Post) and op in ('insert', 'update')]
    # notify about the new and updated posts

models_committed.connect(notify_subscribers, app)

您也可以自己实现(主要是通过复制Flask-SQLAlchemy中的代码)。这有点棘手,因为模型更改发生在 flush 上,而不是发生在 commit 上,因此您需要在刷新发生时记录所有更改,然后在提交后使用它们

from sqlalchemy import event

class ModelChangeEvent(object):
    def __init__(self, session, *callbacks):
        self.model_changes = {}
        self.callbacks = callbacks

        event.listen(session, 'before_flush', self.record_ops)
        event.listen(session, 'before_commit', self.record_ops)
        event.listen(session, 'after_commit', self.after_commit)
        event.listen(session, 'after_rollback', self.after_rollback)

    def record_ops(self, session, flush_context=None, instances=None):
        for targets, operation in ((session.new, 'insert'), (session.dirty, 'update'), (session.deleted, 'delete')):
            for target in targets:
                state = inspect(target)
                key = state.identity_key if state.has_identity else id(target)
                self.model_changes[key] = (target, operation)

    def after_commit(self, session):
        if self._model_changes:
            changes = list(self.model_changes.values())

            for callback in self.callbacks:
                callback(changes=changes)

            self.model_changes.clear()

    def after_rollback(self, session):
        self.model_changes.clear()
def notify_subscribers(changes):
    new_posts = [target for target, op in changes if isinstance(target, Post) and op in ('insert', 'update')]
    # notify about new and updated posts

# pass all the callbacks (if you have more than notify_subscribers)
mce = ModelChangeEvent(db.session, notify_subscribers)
# or you can append more callbacks
mce.callbacks.append(my_other_callback)