Reputation: 16900
I am trying to add an event listener to the before_commit
event of an SQLAlchemy Session inside of a Flask application. When doing the following
def before_commit(session):
for item in session:
if hasattr(item, 'on_save'):
item.on_save(session)
event.listen(db.session, 'before_commit', before_commit)
I get
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "app.py", line 60, in <module>
event.listen(db.session, 'before_commit', before_commit)
File "C:\Python27\lib\site-packages\sqlalchemy\event\api.py", line 49, in listen
_event_key(target, identifier, fn).listen(*args, **kw)
File "C:\Python27\lib\site-packages\sqlalchemy\event\api.py", line 22, in _event_key
tgt = evt_cls._accept_with(target)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\events.py", line 1142, in _accept_with
"Session event listen on a scoped_session "
sqlalchemy.exc.ArgumentError: Session event listen on a scoped_session requires that its creation callable is associated with the Session class.
I can't find the correct way to register the event listener. The documentation actually states that event.listen()
also accepts a scoped_session
, but it seems like it does not?!
http://docs.sqlalchemy.org/en/latest/orm/events.html#sqlalchemy.orm.events.SessionEvents
The listen() function will accept Session objects as well as the return result of sessionmaker() and scoped_session().
Additionally, it accepts the Session class which will apply listeners to all Session instances globally.
Upvotes: 8
Views: 4175
Reputation: 27033
To give another example, this codebase won't work: https://sourceforge.net/p/turbogears1/code/HEAD/tree/branches/1.5/turbogears/database.py
# bad
def create_session():
"""Create a session that uses the engine from thread-local metadata.
The session by default does not begin a transaction, and requires that
flush() be called explicitly in order to persist results to the database.
"""
if not metadata.is_bound():
bind_metadata()
return sqlalchemy.orm.create_session()
session = sqlalchemy.orm.scoped_session(create_session)
Instead it needs to be something like the following:
# good
class SessionMakerAndBind(sqlalchemy.orm.sessionmaker):
def __call__(self, **kw):
if not metadata.is_bound():
bind_metadata()
return super(SessionMakerAndBind, self).__call__(**kw)
sessionmaker = SessionMakerAndBind(autoflush=False,
autocommit=True, expire_on_commit=False)
session = sqlalchemy.orm.scoped_session(sessionmaker)
Upvotes: 0
Reputation: 75317
it means that the factory you've passed to scoped_session() must be a sessionmaker():
from sqlalchemy.orm import scoped_session, sessionmaker, sessionmaker
from sqlalchemy import event
# good
ss1 = scoped_session(sessionmaker())
@event.listens_for(ss1, "before_flush")
def evt(*arg, **kw):
pass
# bad
ss2 = scoped_session(lambda: Session)
@event.listens_for(ss2, "before_flush")
def evt(*arg, **kw):
pass
Upvotes: 5