I am trying to write a generic auditing extension, but I have some 
inconsistencies I'd like to iron out if possible.

The problem I have is that when an object is updated via a relationship 
rather than a Column attribute, the value returned from 
attributes.get_history is a SQLAlchemy object, and not the value that is in 
the database. This is more pronounced on a many-to-many relationship - the 
secondary table isn't marked as new/dirty, even though that is the table 
that will actually have data inserted into it. Instead the 2 tables joined 
by the secondary table are marked as dirty, although no data is changed in 
those tables. I understand this makes sense at the SQLAlchemy level - but 
is there a way to get the actual database changes? 

A code example to illustrate this:

from sqlalchemy.types import Integer
from sqlalchemy import Column, ForeignKey, create_engine
from sqlalchemy.orm import relationship, backref, attributes, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.interfaces import SessionExtension

BASE = declarative_base()

ENGINE = create_engine("sqlite://")

class AuditListener(SessionExtension):
    def after_flush(self, session, *args):
        for attr in ("new", "dirty", "deleted"):
            print attr
            for obj in getattr(session, attr):
                print "    %s" % obj.__class__.__name__
                for col in obj.__mapper__.iterate_properties:
                    added, unchanged, deleted = attributes.get_history(obj, 
col.key)
                    if added:
                        print "        %s: %s" % (col.key, str(added))

SESSION = sessionmaker(
    bind=ENGINE,
    extension=(AuditListener())
)

class FooBar(BASE):
    __tablename__ = 'foobar'
    foo_id = Column('foo_id',
                    ForeignKey('foo.foo_id'),
                    primary_key=True)

    bar_id = Column('bar_id',
                    ForeignKey('bar.bar_id'),
                    primary_key=True)

class Foo(BASE):
    __tablename__ = 'foo'
    foo_id = Column('foo_id',
                    Integer,
                    primary_key=True)

    bars = relationship('Bar',
                        secondary=FooBar.__table__,
                        backref=backref('foos'))

class Bar(BASE):
    __tablename__ = 'bar'
    bar_id = Column('bar_id',
                    Integer,
                    primary_key=True)


def example():
    BASE.metadata.create_all(ENGINE, checkfirst=False)

    session = SESSION()
    f = Foo(foo_id=1)
    b1 = Bar(bar_id=1)
    b2 = Bar(bar_id=2)
    session.add(f)
    session.add(b1)
    session.add(b2)
    session.commit()

    # add relationship between f and b1 & b2:
    f.bars = [b1, b2]
    session.commit()

if __name__ == '__main__':
    example()

If you run this code you will see this output:

new
    Bar
        bar_id: [2]
    Foo
        foo_id: [1]
    Bar
        bar_id: [1]
dirty
deleted
new
dirty
    Bar
    Foo
        bars: [<__main__.Bar object at 0x27b4910>, <__main__.Bar object at 
0x277b050>]
    Bar
deleted

As you can see, the output when the many-to-many relationship is committed 
is to denote an update to the bars relationship of Foo. There is nothing 
indicating there are 2 new FooBar entries created.

Any ideas?

Thanks,

Mark.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to