Giovanni Mascellani
Giovanni Mascellani

Reputation: 1278

How to check whether SQLAlchemy session is dirty or not

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.

Upvotes: 20

Views: 17482

Answers (4)

zzzeek
zzzeek

Reputation: 75167

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)

Upvotes: 14

rdrey
rdrey

Reputation: 9529

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)

Upvotes: 5

Yoriz
Yoriz

Reputation: 3625

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

Upvotes: 4

Kylotan
Kylotan

Reputation: 18449

Sessions have a private _is_clean() member which seems to return true if there is nothing to flush to the database. However, the fact that it is private may mean it's not suitable for external use. I'd stop short of personally recommending this, since any mistake here could obviously result in data loss for your users.

Upvotes: 0

Related Questions