after_update event is fired when I save a many-to-many relationship

97 views
Skip to first unread message

waters greg

unread,
Dec 23, 2022, 12:23:04 PM12/23/22
to sqlalchemy
I'm trying to update an audit log table called 'loggings', here is the definition of the corresponding model:

class Logging(Base):
__tablename__ = 'loggings' 

     id = Column(GUID(), primary_key=True, nullable=False, default=uuid4)
     target_id = Column(GUID(), doc="The ID of the altered object")
     version = Column('version', Integer, default=0) 
     created_at = Column(DateTime, default=datetime.now)
     modified_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)

and two other models and an association table:

membership_table = Table('membership', Base.metadata,
Column('user_id', GUID(), ForeignKey('user.id')),
Column('team_id', GUID(), ForeignKey('team.id')),
PrimaryKeyConstraint('user_id', 'team_id'))

class User(ActivityMixin, Base): # Product
__tablename__ = "user"

id = Column(GUID(), primary_key=True, nullable=False, default=uuid4)
name = Column(String)
password = Column(String)

def __repr__(self): # optional
return f"User {self.name}"

class Team(Base):
   __tablename__ = 'team'
   id = Column(GUID(), primary_key=True, nullable=False, default=uuid4)
   name = Column(String(64))
   users = relationship(lambda:User, secondary=membership_table, backref='teams')

I have an event listener/handler attached to the User class and another to the Team class:
@event.listens_for(User, 'after_update') 
def create_logs_for_user(mapper, connection, target):
       # logic to add a creation related  record to the loggings table

@event.listens_for(User, 'after_update') 
def update_logs_for_user(mapper, connection, target):
       # logic to add user-updates  records to the loggings table

@event.listens_for(Team, 'after_update') 
def update_logs_for_team(mapper, connection, target):
       # logic to add team-updates records to the loggings table

These are the operations that I perform:

[1]: from app import Logging, User, Team, session
[2]: user1 = User(name='qwerh') 
[3]: session.add(user1)
[4]: session.commit()  # after_insert gets fired here
[5]: team1 = Team(name="team1")
[6]: session.add(team1)
[7]: session.commit() # After this point, the users, team, and loggings tables have one record each, as expected.
[8]: user1.teams.append(team1)   
[10]: session.commit() # Two update listeners are getting fired here! After the append.

Question:
When I perform the operation on step [8] above, the loggings table gets filled in twice, due to the "after_update" event getting fired by both the User and the Team models.
How do I prevent this from happening, I only want to capture the one event from the Team model, sorry I'm very new to SQLAlchemy, thank you!
 

Michael Bayer

unread,
Jan 4, 2023, 7:24:20 PM1/4/23
to sqlalchemy
the net-change to "user1.teams" is likely sending it through the "after_update" event but no net SQL is being emitted if no actual columns have changed on user1.

you would need to adjust the logic in your application to more specifically check for those conditions in which you want to act upon an event.  for example, if you want the event only to occur when specific attributes were affected, you could try using the history interface to see if those attributes have net changes: https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.attributes.History  or you can use a method like session.is_modified(): https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session.is_modified
Reply all
Reply to author
Forward
0 new messages