Search code examples
pythonsqlalchemyunit-of-work

Tracking model changes in SQLAlchemy


I want to log every action what will be done with some SQLAlchemy-Models.

So, I have a after_insert, after_delete and before_update hooks, where I will save previous and current representation of model,

def keep_logs(cls):
    @event.listens_for(cls, 'after_delete')
    def after_delete_trigger(mapper, connection, target):
        pass

    @event.listens_for(cls, 'after_insert')
    def after_insert_trigger(mapper, connection, target):
        pass

    @event.listens_for(cls, 'before_update')
    def before_update_trigger(mapper, connection, target):
        prev = cls.query.filter_by(id=target.id).one()
        # comparing previous and current model


MODELS_TO_LOGGING = (
    User,
)
for cls in MODELS_TO_LOGGING:
    keep_logs(cls)

But there is one problem: when I'm trying to find model in before_update hook, SQLA returns modified (dirty) version. How can I get previous version of model before updating it? Is there a different way to keep model changes?

Thanks!


Solution

  • SQLAlchemy tracks the changes to each attribute. You don't need to (and shouldn't) query the instance again in the event. Additionally, the event is triggered for any instance that has been modified, even if that modification will not change any data. Loop over each column, checking if it has been modified, and store any new values.

    @event.listens_for(cls, 'before_update')
    def before_update(mapper, connection, target):
        state = db.inspect(target)
        changes = {}
    
        for attr in state.attrs:
            hist = attr.load_history()
    
            if not hist.has_changes():
                continue
    
            # hist.deleted holds old value
            # hist.added holds new value
            changes[attr.key] = hist.added
    
        # now changes map keys to new values