Attribute history not tracked when using session.merge()

Issue #3522 closed
Sylwester Kardziejonek created an issue

I've noticed that the attribute history of my objects is not tracked. The object comes from session.merge() like it was just freshly retrieved from database.

shipping_id = int(request.matchdict.get('id'))

# Get model from db
db_shipping = db.session.query(m.Shipping).get(shipping_id)

# Create updated instance
updated_shipping = m.Shipping(id=shipping_id, price=999)

# Merge it with session
merged = db.session.merge(updated_shipping)
# I've noticed an actual UPDATE is emitted at this point

# Attributes values are correctly reflected, but the history is all 'unchanged'

# Simply iterates over history of each attribute
changeset(merged)

# History is all 'unchanged'

From the other hand, if I add one line, this works:

shipping_id = int(request.matchdict.get('id'))

# Get model from db
db_shipping = db.session.query(m.Shipping).get(shipping_id)

# When I add this, the history is tracked properly
changeset(db_shipping)

# Create updated instance
updated_shipping = m.Shipping(id=shipping_id, price=999)

# Merge it with session
merged = db.session.merge(updated_shipping)

# Attributes values are correctly reflected, history state is correct

# Simply iterates over history of each attribute
changeset(merged)
def changeset(obj):
    data = {}
    for prop in obj.__mapper__.iterate_properties:
        history = get_history(obj, prop.key)
        if history.has_changes():
            old_value = history.deleted[0] if history.deleted else None
            new_value = history.added[0] if history.added else None
            if new_value:
                data[prop.key] = [new_value, old_value]
    return data

Comments (5)

  1. Mike Bayer repo owner

    Hi there -

    there's not enough detail to show anything here, no data, no mappings. Here is a demo using your function:

    from sqlalchemy import *
    from sqlalchemy.orm import *
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm.attributes import get_history
    
    Base = declarative_base()
    
    
    def changeset(obj):
        data = {}
        for prop in obj.__mapper__.iterate_properties:
            history = get_history(obj, prop.key)
            if history.has_changes():
                old_value = history.deleted[0] if history.deleted else None
                new_value = history.added[0] if history.added else None
                if new_value:
                    data[prop.key] = [new_value, old_value]
        return data
    
    
    class A(Base):
        __tablename__ = 'a'
        id = Column(Integer, primary_key=True)
        x = Column('x', Integer)
        y = Column('y', Integer)
    
    
    e = create_engine("sqlite://", echo=True)
    Base.metadata.create_all(e)
    s = Session(e)
    s.add(A(x=1, y=2))
    s.commit()
    s.close()
    
    
    a1 = s.query(A).first()
    
    # When I add this, the history is tracked properly
    # changeset(db_shipping)
    
    # Create updated instance
    new_a = A(id=a1.id, x=45, y=4)
    
    # Merge it with session
    merged = s.merge(new_a)
    
    # Attributes values are correctly reflected, history state is correct
    
    # Simply iterates over history of each attribute
    print(changeset(merged))
    

    output:

    #!
    
    {u'y': [4, 2], u'x': [45, 1]}
    
  2. Mike Bayer repo owner

    please provide a complete example that I can run locally here, see http://sscce.org/ for guidelines, thanks!

  3. Mike Bayer repo owner

    hi there -

    I don't doubt that you might be hitting something erroneous but I'd need a complete test script to continue. please reopen if you're able to provide this thanks!

  4. Sylwester Kardziejonek reporter

    I'm using SqlAlchemy with Pyramid and transaction manager, so maybe there is something about the ecosystem. As soon as I'll get back to this issue I'll try to create better example.

  5. Log in to comment