python - Modify other objects on update/insert -
i've got 2 mapped objects, parent , child.
class parent(base) __tablename__ = 'parent' id = ... name = ... date_modified = column(sa_datetime, default=datetime.now, onupdate=datetime.now, nullable=false) class child(base) __tablename__ = 'child' id = ... name = ... date_modified = column(sa_datetime, default=datetime.now, onupdate=datetime.now, nullable=false) parent = relationship(parent, backref='parent')
when child updated, want not child.date_modified
changed, child.parent.date_modified
.
i tried this:
@event.listens_for(child, 'after_update') def modified_listener(mapper, connection, target): if object_session(target).is_modified(target, include_collections=false): target.parent.date_modified = datetime.now()
but doesn't work, because i'm in flush , sawarning: attribute history events accumulated on 1 clean instance within inner-flush event handlers have been reset, , not result in database updates. consider using set_committed_value() within inner-flush event handlers avoid warning.
how can solve sqlalchemy?
basic update-parent-when-child-changes using sqlalchemy events has been covered on site before here , here, in case you're trying update parent during flush, possibly using update default value child, visible after update, or new value entirely. modifying parent in event handler not straightforward might first imagine:
warning
mapper-level flush events allow very limited operations, on attributes local row being operated upon only, allowing sql emitted on given
connection
. please read fully notes @ mapper-level events guidelines on using these methods; generally,sessionevents.before_flush()
method should preferred general on-flush changes.
as you've noticed, simple
target.parent.date_modified = datetime.now()
in event handler warns:
sawarning: attribute history events accumulated on 1 clean instances within inner-flush event handlers have been reset, , not result in database updates. consider using set_committed_value() within inner-flush event handlers avoid warning.
set_committed_value()
allows setting attributes no history events, if set value part of original loaded state.
you've noticed receiving target in after update event handler not guarantee update statement emitted:
this method called instances marked “dirty”, have no net changes column-based attributes, , no update statement has proceeded.
and
to detect if column-based attributes on object have net changes, , therefore resulted in update statement, use
object_session(instance).is_modified(instance, include_collections=false)
.
so solution use information held in event target emit update statement on parent table using given connection, , check if parent object present in session , set committed value of it:
from sqlalchemy import event sqlalchemy.orm.attributes import set_committed_value sqlalchemy.orm.session import object_session @event.listens_for(child, 'after_update') def receive_child_after_update(mapper, connection, target): session = object_session(target) if not session.is_modified(target, include_collections=false): return new_date_modified = target.date_modified # avoid touching target.parent relationship attribute , # copy date_modified value child parent. # warning: overwrite possible other updates parent's # date_modified. connection.execute( parent.__table__. update(). values(date_modified=new_date_modified). where(parent.id == target.parent_id)) parent_key = session.identity_key(parent, target.parent_id) try: the_parent = session.identity_map[parent_key] except keyerror: pass else: # if parent object present in session, update # date_modified attribute **in python only**, reflect # updated db state local transaction. set_committed_value( the_parent, 'date_modified', new_date_modified)
Comments
Post a Comment