Python 为所有模型创建后的SQLAlchemy事件

Python 为所有模型创建后的SQLAlchemy事件,python,sqlalchemy,Python,Sqlalchemy,我正在做一个项目,需要对创建的每个模型进行通用定制。到目前为止,我完成大部分工作的方式是通过模型继承。下面是我的代码块,让您更好地了解: app.core.dba.mixins: class AuditExtension(MapperExtension): """ AuditExtension enforces the audit column values, and ensures any interaction with SQLAlchemy cannot overr

我正在做一个项目,需要对创建的每个模型进行通用定制。到目前为止,我完成大部分工作的方式是通过模型继承。下面是我的代码块,让您更好地了解:

app.core.dba.mixins:

class AuditExtension(MapperExtension):
    """
    AuditExtension enforces the audit column values, and ensures any interaction with
    SQLAlchemy cannot override the values
    """

    def before_insert(self, mapper, connection, instance):
        instance.created_dt = datetime.utcnow()
        instance.created_by = audit_session_user()

        instance.updated_dt = datetime.utcnow()
        instance.updated_by = audit_session_user()

    def before_update(self, mapper, connection, instance):
        # Never update the created columns
        instance.created_dt = instance.created_dt
        instance.created_by = instance.created_by

        instance.updated_dt = datetime.utcnow()
        instance.updated_by = audit_session_user()


class AuditColumns(object):

    """ Generate the column schema for simple table level auditing. """
    created_dt = Column(DateTime,
                        default=datetime.utcnow(),
                        nullable=False)
    created_by = Column(String(64),
                        #ForeignKey('operators.username', ondelete="RESTRICT"),
                        nullable=False)

    updated_dt = Column(DateTime,
                        default=datetime.utcnow(),
                        nullable=False,
                        onupdate=datetime.utcnow())

    updated_by = Column(String(64),
                        #ForeignKey('operators.username', ondelete="RESTRICT"),
                        nullable=False)

    __mapper_args__ = {
        'extension': AuditExtension()}
然后,我的模型将继承AuditColumns:

class ObjectTypes(Base, AuditColumns):
    __tablename__ = 'object_types'

    id = Column(BigInteger, primary_key=True)
    name = Column(String, nullable=False, unique=True)

    def __repr__(self):
        return self.name
我的问题是;只要操作包含在flask app和SQLAlchemy中,我的强制审计数据的解决方案就可以工作-这不会阻止任何具有数据库访问权限的人更新值

因此,我现在需要在继承AuditColumns的每个模型上实现一个触发器。我找到了这篇文章,它描述了一个用于before_insert/update的方法(这是我以前的工作),但不是用于“after_create”

现在,我已经将其添加到我的mixins文件代码中(直接在上面的审计代码之后:

trig_ddl = DDL("""
            CREATE TRIGGER tr_audit_columns BEFORE INSERT OR UPDATE
            ON test_table
            FOR EACH ROW EXECUTE PROCEDURE
            ss_test();
        """)

event.listen(AuditColumns, 'after_create', trig_ddl)
但是,当我运行测试用例时:

Base.metadata.drop_all(db.get_engine(app))
Base.metadata.create_all(db.get_engine(app))
我得到以下错误:

File "D:\Devel\flask-projects\sc2\app\core\dba\mixins.py", line 59, in <module>
    event.listen(AuditColumns, 'after_create', trig_ddl)
  File "D:\Devel\flask-projects\env\lib\site-packages\sqlalchemy\event.py", line 43, in listen
    (identifier, target))
sqlalchemy.exc.InvalidRequestError: No such event 'after_create' for target '<class 'app.core.dba.mixins.AuditColumns'>'
文件“D:\Devel\flask projects\sc2\app\core\dba\mixins.py”,第59行,在
监听(AuditColumns,“创建后”触发ddl)
文件“D:\Devel\flask projects\env\lib\site packages\sqlalchemy\event.py”,第43行,在listen中
(标识符、目标)
sqlalchemy.exc.InvalidRequestError:在为目标“”创建“”之后没有此类事件“”
我猜这是因为它还不是一张桌子;但是,如何为表create全局定义一个事件监听器来执行这种类型的命令呢

我知道我必须使trig_ddl动态化(我认为这不会太难,但我至少需要弄清楚其中的全局元素)

基本上,我不希望人们在每个模型中手动编写这个事件,因为它显然与这些审计列相关联


任何朝着正确方向的推动都会很好。

好吧,你需要把事件放在这里,这样你就可以在
表格中找到它了。

@event.listens_for(AuditColumns, "instrument_class", propagate=True)
def instrument_class(mapper, class_):
    if mapper.local_table is not None:
        trigger_for_table(mapper.local_table)

def trigger_for_table(table):
    trig_ddl = DDL("""
                CREATE TRIGGER tr_%s_audit_columns BEFORE INSERT OR UPDATE
                ON %s
                FOR EACH ROW EXECUTE PROCEDURE
                ss_test();
            """ % (table.name, table.name))

    event.listen(table, 'after_create', trig_ddl)

auditcolums
的任何子类都被映射,
mapper.local\u table
将已经存在(就像
class.\uu table\uuuu
一样),您将在该点应用DDL事件。

强制所有更改都必须通过orm是完全不可行的吗?试图在应用程序和数据库中保持业务逻辑似乎是未来令人头痛的问题。决定使用orm、将逻辑保留在应用层以及禁止(或非常小心)通过进程/访问控制直接访问数据库似乎是完全合理的。目前,我们在迁移、批量数据修复等方面大量使用直接访问数据库。。但可以想象的是,所有这些事情都可能在将来通过py/sqlalchemy编写的单元测试来实现……好吧,我已经阅读了文档,从中我可以得出结论,这是通过首先侦听任何试图继承AuditColumns的新模型来实现的——然后它使用映射器来找出继承扩展的表,然后调用trigger_for_table函数来生成DDL并生成特定模型的侦听器?如果您有关于instrument_类的更多信息,那就太好了,因为文档有点模糊(或者可能有点太具体了,我不太理解它)。。。顺便说一句,这很有效;但我真的理解它为什么会起作用。instrument_类是一个事件,“在将AuditColumns子类映射到表时立即执行此操作”。如果您创建了一个元类,该元类在
\uuuu init\uuuu()
上执行了某些操作,例如在创建一个新类时,这与此没有什么不同。