Cascading after_update operations with sqlalchemy not working

255 views Asked by At

We have a parent/child table and want to be able to propagate updates up from the children/leaves to the parent root.

For example, given we have a structure like

{
  id: 1,
  state: "in-progress",
  children: [{
    id: 2,
    state: "in-progress",
    children: [{
      id: 3,
      state: "in-progress"
    }]
  }]
}

When child 3 state gets updated to completed we want to update the state of the parent 2 and its parent 1 to completed as well. We can do that when it's one level of separation, but for two levels the root node never gets updated. So node 2 will get updated to completed but 1 is stuck in-progress

Here's what we have

class OperationNode(BaseModel):
    __tablename__ = "operation_node"

    id = Column(Integer, primary_key=True)

    # child -> parent
    parent_id = Column(
        Integer,
        ForeignKey("operation_node.id", ondelete="CASCADE", onupdate="CASCADE"),
        index=True,
    )

    # Track progress
    state = Column(String(15), default=OperationNodeStates.in_progress.value)


    # List of child objects with eager loading and max depth
    children = relationship("OperationNode",
                            foreign_keys=[parent_id],
                            cascade="all, delete-orphan",
                            lazy="joined",
                            join_depth=2)


@event.listens_for(OperationNode, "after_update")
def after_update_trigger_parent_sync(mapper, connection, operation_node):
    insp = db.inspect(operation_node)
    state_history = insp.attrs["state"].load_history()

    # Update aggregated fields of parent when a child has changes
    if operation_node.parent_id \
            and state_history.has_changes():
        operation_node_table = OperationNode.__table__
        connection.execute(
            operation_node_table.update().
            where(operation_node_table.c.id == operation_node.parent_id).
            values(state="completed")
        )

so when we do

operation_node = OperationNode.query.get(3)
operation_node.state = "completed"
db.session.merge(operation_node)
db.session.commit()

only the direct parent 2 gets updated from the after_update and not the root 1. The after_update doesn't trigger the second time :(. Would be happy to share more code/context if needed.

Are we missing something? Or is there a good example of this same use case out there? Any help is appreciated as we are very stuck!

Using sqlalchemy 1.3.24 and Flask-SQLAlchemy 2.4.4

0

There are 0 answers