SQLAlchemy Mapper Events don't get triggered

Question:

I can’t get Mapper Events to trigger in SqlAlchemy 2.0

tl;dr: I am able to perform operations but events never trigger.

Below is the example they have in their docs:

def my_before_insert_listener(mapper, connection, target):
    # execute a stored procedure upon INSERT,
    # apply the value to the row to be inserted
    target.calculated_value = connection.execute(
        text("select my_special_function(%d)" % target.special_number)
    ).scalar()

# associate the listener function with SomeClass,
# to execute during the "before_insert" hook
event.listen(
    SomeClass, 'before_insert', my_before_insert_listener)

And here, is my attempt to implement basically the same thing:

# models.py

from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, String, DateTime, func, event, MetaData

metadata_obj = MetaData()
Base = declarative_base(metadata=metadata_obj)

class Opportunity(Base):
    __tablename__ = "opportunity"
    id = Column(String, primary_key=True, default=generate)
    created_at = Column(DateTime, server_default=func.now())
    updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
    name = Column(String, nullable=False)

opportunity_table = Opportunity.__table__

I also have the following listener

#models.py 

def opportunity_after_update(mapper, connection, target):
    print(f"Opportunity {target.id} was updated!")
event.listen(Opportunity, 'after_update', opportunity_after_update)

The following code successfully updates the table in my database, but the listener never gets called:

with Session() as session:
    session.query(Opportunity).filter_by(id=some_id).update({'name'='New Name'})
    session.commit()

Any clues that may be helpful?

Answers:

if you want to modify an existing record, there are (at least) two approaches that you could take.

Firstly, you could retrieve the record an modify it in the session.

with Session() as s:
    opportunity = s.get(Opportunity, some_id)
    opportunity.name = 'new_name'
    s.commit()

In this case, because the object involved is guaranteed to be in the session, mapper event listeners will pick up any changes and report them (or perform some other action that you define).

Secondly, you can use the "update with custom where clause method":

with Session() as s:
    s.query(Opportunity).filter_by(id=some_id).update({'name': 'new_name'})
    s.commit()

In this case, the session will not necessarily contain references to the updated rows, so mapper event listeners will not be notified of them.

The recommended solution for capturing such events is to use a do_orm_execute listener. Such a listener will intercept:

all top-level SQL statements invoked from the Session.execute() method, as well as related methods such as Session.scalars() and Session.scalar()

(quotation taken from the previous link; session.query and session.execute can be considered to be equivalent here)

Which update method you choose – and consequently which listeners you can use – depends on your use case. The first method is most common, but the second method is useful if you want to update an arbitrarily large collection of rows, for example "all the rows in the users table whose name column is equal to ‘John’".

Answered By: snakecharmerb
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.