【问题标题】:signals or triggers in SQLAlchemySQLAlchemy 中的信号或触发器
【发布时间】:2010-11-16 08:54:29
【问题描述】:

SQLAlchemy 是否有类似于 Django 的信号概念的东西?基本上,我想在预保存或保存一些实体对象时触发一些功能。谢谢。

编辑:我只想要在 SQLAlchemy 中等效的 django-signals。

【问题讨论】:

  • 简单地覆盖save有什么问题?
  • @S.Lott 很抱歉复活了一个古老的线程,但是 SQLAlchemy 声明性模块没有 save 方法的相同概念,可以像 Django ORM 那样被覆盖。要将对象保存在 SA 中,请将它们添加到会话中,然后刷新会话。但是,您可以实现自己的声明性基类并达到相同的效果。
  • @JoeHolloway:虽然很有帮助,但它没有解释为什么标签上写着 Django 而标题上写着 SQLAlchemy。

标签: python django sqlalchemy


【解决方案1】:

我认为您正在寻找“ORM 事件”。 你可以在这里找到文档:

http://docs.sqlalchemy.org/en/latest/orm/events.html

【讨论】:

  • 顺便说一句,其他答案中的MapperExtensiondeprecated,因为SQLAlchemy 0.7。所以“ORM 事件”看起来是一个合适的解决方案。
【解决方案2】:

您没有明确说明,您是在集成 SQLAlchemy 和 Django,还是只是想要在 SQLAlchemy 中等效于 django-signals。

如果您想要等效的 Django 信号,例如 post_save、pre_save、pre_delete 等,我会向您推荐该页面,

sqlalchemy.orm.interfaces.MapperExtension

【讨论】:

  • 查看 S.C. 对“ORM 事件”的回答,了解当前的执行方式。来自 SQLAlchemy 文档:“0.7 版中的新功能:该事件取代了以前的“扩展”类系统。”
【解决方案3】:

你也可以考虑sqlalchemy.orm.SessionExtension

这是我拼凑的一些代码,用于在实例上设置所有者 ID 并设置在 pylons 应用程序中完成的工作的 update_date。 OrmExt 类是所有魔法发生的地方。 init_model 是你连接它的地方。

import logging
import sqlalchemy as sa
from sqlalchemy import orm

from pylons import session

import datetime

log = logging.getLogger(__name__)

class ORMSecurityException(Exception):
    '''
    thrown for security violations in orm layer
    '''
    pass

def _get_current_user():
    log.debug('getting current user from session...')
    log.debug(session)
    return session['user'] 

def _is_admin(user):
    return False  


def set_update_date(instance):

    if hasattr(instance,'update_date'):
    instance.update_date = datetime.datetime.now()

def set_owner(instance):
    '''
    if owner_id, run it through the rules
    '''
    log.info('set_owner')
    if hasattr(instance, 'owner_id'):
    log.info('instance.owner_id=%s' % instance.owner_id)
    u = _get_current_user()
    log.debug('user: %s' % u.email)
    if not u:
        #anonymous users can't save owned objects
        raise ORMSecurityException()
    if instance.owner_id==None:
        #must be new object thus, owned by current user
        log.info('setting owner on object %s for user: %s' % (instance.__class__.__name__,u.email))
        instance.owner_id = u.id
    elif instance.owner_id!=u.id and not _is_admin(u):
        #if owner_id does not match user_id and user is not admin VIOLATION
        raise ORMSecurityException()
    else:
        log.info('object is already owned by this user')
        return #good to go
else:
    log.info('%s is not an owned object' % instance.__class__.__name__)
    return

def instance_policy(instance):
    log.info('setting owner for %s' % instance.__class__.__name__)
    set_owner(instance)
    log.info('setting update_date for %s' % instance.__class__.__name__)
    set_update_date(instance)


class ORMExt(orm.SessionExtension):
    '''
    attempt at managing ownership logic on objects
    '''
    def __init__(self,policy):
        self._policy = policy

    def before_flush(self,sqlsess,flush_context,instances):
        '''
        check all instances for owner_id==user.id
        '''
        try:
            for instance in sqlsess.deleted:
                try:
                    log.info('running policy for deleted %s' % instance.__class__.__name__)
                    self._policy(instance)
                except Exception,ex:
                    log.error(ex)
                    raise ex

            for instance in sqlsess.new:
                try:
                    log.info('running policy for new %s' % instance.__class__.__name__)
                    self._policy(instance)
                except Exception,ex:
                    log.error(ex)
                    raise ex

            for instance in sqlsess.dirty:
                try:
                    if sqlsess.is_modified(instance,include_collections=False,passive=True):
                        log.info('running policy for updated %s' % instance.__class__.__name__)
                        self._policy(instance)
                except Exception, ex:
                    log.error(ex)
                    raise ex

        except Exception,ex:
            sqlsess.expunge_all()
            raise ex

def init_model(engine):
    """Call me before using any of the tables or classes in the model"""
    sm = orm.sessionmaker(autoflush=True, autocommit=True, bind=engine,extension=ORMExt(instance_policy))
    meta.engine = engine
    meta.Session = orm.scoped_session(sm)

【讨论】:

    【解决方案4】:

    这是我对这个问题的看法,它使用Louie 来调度信号:

    dispatch.py

    """
    Signals dispatching for SQLAlchemy mappers.
    """
    
    import louie
    from sqlalchemy.orm.interfaces import MapperExtension
    import signals
    
    
    class LouieDispatcherExtension(MapperExtension):
        """
        Dispatch signals using louie on insert, update and delete actions.
        """
    
        def after_insert(self, mapper, connection, instance):
            louie.send(signals.after_insert, instance.__class__,
                    instance=instance)
            return super(LouieDispatcherExtension, self).after_insert(mapper,
                    connection, instance)
    
        def after_delete(self, mapper, connection, instance):
            louie.send(signals.after_delete, instance.__class__,
                    instance=instance)
            return super(LouieDispatcherExtension, self).after_delete(mapper,
                    connection, instance)
    
        def after_update(self, mapper, connection, instance):
            louie.send(signals.after_update, instance.__class__,
                    instance=instance)
            return super(LouieDispatcherExtension, self).after_update(mapper,
                    connection, instance)
    
        def before_delete(self, mapper, connection, instance):
            louie.send(signals.before_delete, instance.__class__,
                    instance=instance)
            return super(LouieDispatcherExtension, self).before_delete(mapper,
                    connection, instance)
    
        def before_insert(self, mapper, connection, instance):
            louie.send(signals.before_insert, instance.__class__,
                    instance=instance)
            return super(LouieDispatcherExtension, self).before_insert(mapper,
                    connection, instance)
    
        def before_update(self, mapper, connection, instance):
            louie.send(signals.before_update, instance.__class__,
                    instance=instance)
            return super(LouieDispatcherExtension, self).before_update(mapper,
                    connection, instance)
    

    signals.py

    from louie import Signal
    
    
    class after_delete(Signal): pass
    class after_insert(Signal): pass
    class after_update(Signal): pass
    class before_delete(Signal): pass
    class before_insert(Signal): pass
    class before_update(Signal): pass
    

    示例用法:

    class MyModel(DeclarativeBase):
    
        __mapper_args__ = {"extension": LouieDispatcherExtension()}
    
        ID = Column(Integer, primary_key=True)
        name = Column(String(255))
    
    def on_insert(instance):
        print "inserted %s" % instance
    
    louie.connect(on_insert, signals.after_insert, MyModel)
    

    【讨论】:

    • 所有提到的事件都有一个问题,它们已经处于刷新过程的中间。因此,虽然修改实例上的列就足够了,但如果您想要进行更复杂的操作,例如从会话中添加/删除实例,这还不够。所有这些更改都将被丢弃。
    【解决方案5】:

    你可以使用内部MapperExtension类:

    class YourModel(db.Model):
    
        class BaseExtension(MapperExtension):
    
            def before_insert(self, mapper, connection, instance):
                # do something here
    
            def before_update(self, mapper, connection, instance):
                # do something here
    
        __mapper_args__ = { 'extension': BaseExtension() }
    
        # ....
    

    【讨论】:

    • 我可以用它来做一个触发器,在插入时,在另一个表中插入一行?类似于充当日志的表格。
    猜你喜欢
    • 2019-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-14
    • 1970-01-01
    • 2018-03-08
    • 2012-07-31
    • 2011-09-21
    相关资源
    最近更新 更多