Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get a SQLAlchemy ORM object's previous state after a db update?

The issue is that I can't figure out how to use SQLAlchemy to notify me when an object goes into a new state.

I'm using SQLAlchemy ORM (Declarative) to update an object:

class Customer(declarative_base()):

    __table_name__ = "customer"

    id = Column(Integer, primary_key=True)
    status = Column(String)

I want to know when an object enters a state. Particularly after an UPDATE has been issued and when the state changes. E.g. Customer.status == 'registered' and it previously had a different state.

I'm currently doing this with an 'set' attribute event:

from sqlalchemy import event
from model import Customer

def on_set_attribute(target, value, oldvalue, initiator):
    print target.status
    print value
    print oldvalue

event.listen(
        Customer.status,
        'set',
        on_set_attribute,
        propagate=True,
        active_history=True)

My code fires every time 'set' is called on that attribute, and I check if the value and the oldvalue are different. The problem is that the target parameter isn't fully formed so it doesn't have all the attribute values populated yet.

Is there a better way to do this? Thanks!

like image 881
Evan Hammer Avatar asked Mar 26 '13 16:03

Evan Hammer


People also ask

How does SQLAlchemy update data?

Update table elements in SQLAlchemy. Get the books to table from the Metadata object initialized while connecting to the database. Pass the update query to the execute() function and get all the results using fetchall() function. Use a for loop to iterate through the results.

What does SQLAlchemy query return?

It returns an instance based on the given primary key identifier providing direct access to the identity map of the owning Session. It creates a SQL JOIN against this Query object's criterion and apply generatively, returning the newly resulting Query. It returns exactly one result or raise an exception.

What is _sa_instance_state in SQLAlchemy?

_sa_instance_state is a non-database-persisted value used by SQLAlchemy internally (it refers to the InstanceState for the instance.


1 Answers

My solution was to use 'after_flush' SessionEvent instead of 'set' AttributeEvent.

Many thanks to agronholm who provided example SessionEvent code that specifically checked an object's value and oldvalue.

The solution below is a modification of his code:

def get_old_value(attribute_state):
    history = attribute_state.history
    return history.deleted[0] if history.deleted else None


def trigger_attribute_change_events(object_):
    for mapper_property in object_mapper(object_).iterate_properties:
        if isinstance(mapper_property, ColumnProperty):
            key = mapper_property.key
            attribute_state = inspect(object_).attrs.get(key)
            history = attribute_state.history

            if history.has_changes():
                value = attribute_state.value
                # old_value is None for new objects and old value for dirty objects
                old_value = get_old_value(attribute_state)
                handler = registry.get(mapper_property)
                if handler:
                    handler(object_, value, old_value)


def on_after_flush(session, flush_context):
    changed_objects = session.new.union(session.dirty)
    for o in changed_objects:
        trigger_attribute_change_events(o)

event.listen(session, "after_flush", on_after_flush)

The registry is a dictionary whose keys are MapperProperty's and whose values are event handlers. session, event, inspect, and object_mapper are all sqlalchemy classes and functions.

like image 170
Evan Hammer Avatar answered Oct 12 '22 11:10

Evan Hammer