Very strange how these reports are always in pairs. This recipe has been there for several years now, yet literally two days ago, someone submitted a pull request pointing out and repairing this issue for the first time. The pullreq has been merged and you can apply the changes it has here:
https://bitbucket.org/zzzeek/sqlalchemy/commits/d6c60cb2f3b1bf27f10aecf542fc0e3f3f903183 On Jun 19, 2013, at 5:53 PM, Seth P <spadow...@gmail.com> wrote: > The Versioned mixin described in > http://docs.sqlalchemy.org/en/rel_0_8/orm/examples.html#versioned-objects > (which I renamed VersionedMixin, but is otherwise the same) has what I would > consider an unintuitive and undesirable interaction with backref: if C > references A with a backref, adding a new C object referencing a particular A > object will cause the version number of the target A object to increment, > even though there are no changes to the A table. If the relation has no > backref (as in the relationship from C to B below), then the target object > version number is not incremented, as I would expect. It seems that the code > is effectively using session.is_modified(a) to determine whether to increment > the version number, whereas I would have thought session.is_modified(a, > include_collections=False) would be more appropriate. Is there some use case > I'm not considering that favors the current behavior? > > Thanks, > > Seth > > from sqlalchemy import Column, Integer, String, ForeignKey, create_engine > from sqlalchemy.ext.declarative.api import declarative_base > from sqlalchemy.orm import sessionmaker, scoped_session, relationship, backref > from history_meta import VersionedMixin, versioned_session > > Base = declarative_base(object) > metadata = Base.metadata > > class A(VersionedMixin, Base): > __tablename__ = 'a' > __table_args__ = {} > id = Column(Integer, primary_key=True) > name = Column(String(3)) > def __repr__(self): > return "A(id=%d,name='%s',version=%d,cs=%s)" % (self.id, self.name, > self.version, [c.name for c in self.cs]) > > class B(VersionedMixin, Base): > __tablename__ = 'b' > __table_args__ = {} > id = Column(Integer, primary_key=True) > name = Column(String(3)) > def __repr__(self): > return "B(id=%d,name='%s',version=%d)" % (self.id, self.name, > self.version) > > class C(VersionedMixin, Base): > __tablename__ = 'c' > __table_args__ = {} > id = Column(Integer, primary_key=True) > name = Column(String(3)) > a_id = Column(Integer, ForeignKey('a.id')) > a_re = relationship(A, backref='cs') > b_id = Column(Integer, ForeignKey('b.id')) > b_re = relationship(B) > > if __name__ == '__main__': > engine = create_engine('sqlite:///:memory:', echo=False) > metadata.create_all(bind=engine) > Session = scoped_session(sessionmaker(bind=engine)) > versioned_session(Session) > session = Session() > > # populate tables with a single entry in each table > a = A(name='a') > b = B(name='b') > c1 = C(name='c1', a_re=a, b_re=b) > session.add_all([a, b, c1]) > session.commit() > print '\nAfter initial commit' > print 'a=%s; is_modified(a)=%s; is_modified(a, > include_collections=False)=%s' % (a, session.is_modified(a), > session.is_modified(a, include_collections=False)) > print 'b=%s; is_modified(b)=%s; is_modified(b, > include_collections=False)=%s' % (b, session.is_modified(b), > session.is_modified(b, include_collections=False)) > # add another entry in b that points to a > c2 = C(name='c2', a_re=a, b_re=b) > session.add(c2) > print "\nAfter adding C(name='c2', a_re=a, b_re=b), but before > committing:" > print 'a=%s; is_modified(a)=%s; is_modified(a, > include_collections=False)=%s' % (a, session.is_modified(a), > session.is_modified(a, include_collections=False)) > print 'b=%s; is_modified(b)=%s; is_modified(b, > include_collections=False)=%s' % (b, session.is_modified(b), > session.is_modified(b, include_collections=False)) > session.commit() > print '\nAfter final commit:' > print 'a=%s; is_modified(a)=%s; is_modified(a, > include_collections=False)=%s' % (a, session.is_modified(a), > session.is_modified(a, include_collections=False)) > print 'b=%s; is_modified(b)=%s; is_modified(b, > include_collections=False)=%s' % (b, session.is_modified(b), > session.is_modified(b, include_collections=False)) > > > -- > 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. > > -- 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.