How to check whether SQLAlchemy session is dirty or not

12,309

Solution 1

you're looking for a net count of actual flushes that have proceeded for the whole span of the session's transaction; while there are some clues to whether or not this has happened (called the "snapshot"), this structure is just to help with rollbacks and isn't strong referencing. The most direct route to this would be to track "after_flush" events, since this event only emits if flush were called and also that the flush found state to flush:

from sqlalchemy import event
import weakref
transactions_with_flushes = weakref.WeakSet()

@event.listens_for(Session, "after_flush")
def log_transaction(session, flush_context):
    for trans in session.transaction._iterate_parents():
        transactions_with_flushes.add(trans)

def session_has_pending_commit(session):
    return session.transaction in transactions_with_flushes

edit: here's an updated version that's a lot simpler:

from sqlalchemy import event

@event.listens_for(Session, "after_flush")
def log_transaction(session, flush_context):
    session.info['has_flushed'] = True

def session_has_pending_commit(session):
    return session.info.get('has_flushed', False)

Solution 2

Here is my solution based on @zzzeek's answer and updated comment. I've unit tested it and it seems to play well with rollbacks (a session is clean after issuing a rollback):

from sqlalchemy import event
from sqlalchemy.orm import Session


@event.listens_for(Session, "after_flush")
def log_flush(session, flush_context):
    session.info['flushed'] = True


@event.listens_for(Session, "after_commit")
@event.listens_for(Session, "after_rollback")
def reset_flushed(session):
    if 'flushed' in session.info:
        del session.info['flushed']


def has_uncommitted_changes(session):
    return any(session.new) or any(session.deleted) \
        or any([x for x in session.dirty if session.is_modified(x)]) \
        or session.info.get('flushed', False)

Solution 3

The session has a dirty attribute

session.dirty

persistent objects which currently have changes detected (this collection is now created on the fly each time the property is called)

sqlalchemy.orm.session.Session.dirty

Share:
12,309

Related videos on Youtube

Giovanni Mascellani
Author by

Giovanni Mascellani

Postdoc in Mathematics at Université Libre de Bruxelles, Belgium. Developer for the Debian Project. And a few other things...

Updated on September 15, 2022

Comments

  • Giovanni Mascellani
    Giovanni Mascellani over 1 year

    I have a SQLAlchemy Session object and would like to know whether it is dirty or not. The exact question what I would like to (metaphorically) ask the Session is: "If at this point I issue a commit() or a rollback(), the effect on the database is the same or not?".

    The rationale is this: I want to ask the user wether he wants or not to confirm the changes. But if there are no changes, I would like not to ask anything. Of course I may monitor myself all the operations that I perform on the Session and decide whether there were modifications or not, but because of the structure of my program this would require some quite involved changes. If SQLAlchemy already offered this opportunity, I'd be glad to take advantage of it.

    Thanks everybody.

  • Giovanni Mascellani
    Giovanni Mascellani about 11 years
    This doesn't solve my problem: there may be no dirty objects, but if some change was already flushed to the database, I would still want to know about it.
  • Giovanni Mascellani
    Giovanni Mascellani about 11 years
    Apart from the fact of being private, it apparently has the same problem as Yoriz's answer: I want to detect also changes there were already flushed, but not committed.
  • aman.gupta
    aman.gupta about 11 years
    Hmm, ok. I don't have any other suggestion except to post on the SQLAlchemy group (groups.google.com/forum/?fromgroups#!forum/sqlalchemy) and if you get an answer, post it here.
  • Giovanni Mascellani
    Giovanni Mascellani over 9 years
    I notice this answer after an year and a half... Thanks, it is just what I needed. I enhanced it by adding another check: transactions are actually added to transactions_with_flushes only if there is something in session.new, session.deleted or session.dirty. Moreover objects in session.dirty are first tested with session.is_modified. The final implementation is in this project of mine: github.com/giomasce/liturgy/blob/master/database.py#L19. It appears to work, although I did not perform maniacal testing.
  • Giovanni Mascellani
    Giovanni Mascellani over 7 years
    _iterate_parents has been removed from SQLAlchemy. Now parent iteration must be done by hand. See the same link above to see my implementation, which appear to work.
  • zzzeek
    zzzeek over 7 years
    This answer is strange anyway. Session has an .Info dictionary these days. Put "flushed=True" in it for after_flush and remove it in after_commit. Im not sure why I have it putting all the SessionTransaction objects in a dictionary.
  • rdrey
    rdrey almost 6 years
    @zzzeek does this play well with rollbacks or would we need to hook into after_ events for those, too? Could you update your answer with the info dict approach, please?
  • zzzeek
    zzzeek almost 6 years
    @rdrey the commit/rollback is independent of the flush(). the above recipe will also tell you if data has been flushed that would be impacted by a rollback() also.