使用映射程序事件时,SQLAlchemy推迟加载updated_at列

时间:2018-09-18 19:02:53

标签: python orm sqlalchemy

我定义了一个SQLAlchemy模型,其中包含一个updated_at属性:

updated_at = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now())

我在模型上也定义了几个映射器事件:

@listens_for(Drive, 'after_insert')
def insert_listener(mapper, connect, self):
    from api.drive import operations
    operations.after_create(self)


@listens_for(Drive, 'after_update')
def update_listener(mapper, connect, self):
    from api.drive import operations
    operations.after_update(self)

我将模型作为json上传到after_createafter_update例程中的文档存储中。

我注意到,除非在after_*例程中明确查询,否则SQLAlchemy不会加载update_at属性,因此,update_at时间戳永远不会进入文档存储。如果我在上载对象之前立即登录model.updated_at,它将正常工作。我尚未在模型定义中配置此属性的延迟加载,但是SQLAlchemy似乎是在后台进行的。

1 个答案:

答案 0 :(得分:0)

The after_insert and after_update events are there to run additional actions when the session flushes transient objects to the database. At this stage the data is flowing in one direction, from Python to the database. Any database-side triggers such as your defaults are not visible at this stage, unless the server explicitly supports returning such data and you configured SQLAlchemy to retrieve them at that time.

Just in case this isn't clear: func.now() is the SQLAlchemy spelling for the NOW() function in SQL, the function is executed as the database executes the INSERT or UPDATE statements, so the value only exists on the database side. They are Client-Invoked SQL Expressions.

You should use server_default and server_onupdate instead. If you are using a database that supports the RETURNING or OUTPUT inserted or similar features to return expression values from inserts or updates directly when executing (such as PostgreSQL, Oracle, and SQL Server) then such values will be available to SQL-Alchemy immediately. For MySQL or SQLLite or other databases without RETURNING support you are forced to refresh teh object See Fetching Server-Generated Defaults.

For a server that supports RETURNING I think adding eager_defaults should be enough:

class SomeModel(Base):
    updated_at = db.Column(
        db.DateTime,
        server_default=db.func.now(),
        server_onupdate=db.func.now()
    )

    __mapper_args__ = {"eager_defaults": True}

(The server_onupdate line really only means that SQLAlchemy will know to look for an updated value, and probably requires an addinional trigger definition to work. See this issue where server_onupdate is used with MySQL).

Even when the database doesn't support RETURNING, using eager_defaults should work as that'll automatically add an additional SELECT to the query. This does cost you in terms of performance. Consider using Python callables for your defaults instead (and forgo having the database take care of these values).