Hi, I'm confronted to an misunderstanding or unexpected behavior of session trigger flush. I want to manage revision of a document, like in this <http://docs.sqlalchemy.org/en/latest/_modules/examples/versioned_rows/versioned_rows.html> doc example. But the behavior is unexpected: I want to dot something like this:
1. create a document with it's first revision 2. flush -> creation of document + revision id database 3. modify revision of dicument 4. flush -> session *before_flush* <http://docs.sqlalchemy.org/en/latest/orm/events.html#sqlalchemy.orm.events.SessionEvents.before_flush> hook create a new revision instead update it But the following example code make a unexpected flush in point "3." Example test.py: from sqlalchemy import create_engine, Column, Integer, Text, ForeignKey, inspect, String from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, SessionExtension, relationship DeclarativeBase = declarative_base() class ContentRevision(DeclarativeBase): __tablename__ = 'content_revision' revision_id = Column(Integer, primary_key=True) content_id = Column(Integer, ForeignKey('content.id')) description = Column(Text()) title = Column(String(32)) node = relationship("Content", foreign_keys=[content_id], back_populates ="revisions") @classmethod def new_from(cls, revision): columns = (column.key for column in inspect(cls).attrs if column.key != 'revision_id') new_revision = cls() for column_name in columns: column_value = getattr(revision, column_name) setattr(new_revision, column_name, column_value) return new_revision class Content(DeclarativeBase): __tablename__ = 'content' id = Column(Integer, primary_key=True) revisions = relationship("ContentRevision", foreign_keys=[ContentRevision.content_id], back_populates="node") class VersionExtension(SessionExtension): def before_flush(self, session, flush_context, instances): print('EVENT: before_flush') for instance in session.dirty: if isinstance(instance, ContentRevision): print('EVENT: before_flush: ContentRevision found in dirty') if session.is_modified(instance, passive=True): print('EVENT: before_flush: ContentRevision has been modified') if inspect(instance).has_identity: print('EVENT: before_flush: revision updated, create new instead') previous_revision = instance new_revision = ContentRevision.new_from(instance) session.expunge(previous_revision) session.add(new_revision) else: print('EVENT: before_flush: revision is a new revision, no change') # Prepare database and session engine = create_engine('sqlite://', echo=False) DeclarativeBase.metadata.create_all(engine) session_maker = sessionmaker(engine, extension=[VersionExtension()]) session = session_maker() # Start example scenario print('SCENARIO: Create new content') content1 = Content(revisions=[ContentRevision(description='rev1', title= 'title1')]) print('SCENARIO: Add content to session') session.add(content1) print('SCENARIO: flush session') session.flush() assert session.query(ContentRevision.revision_id, ContentRevision.title, ContentRevision.description).order_by(ContentRevision. revision_id).all() == \ [(1, 'title1', 'rev1')] print('SCENARIO: update revision title') content1.revisions[0].title = 'title2' assert session.query(ContentRevision.revision_id, ContentRevision.title, ContentRevision.description).order_by(ContentRevision. revision_id).all() == \ [(1, 'title1', 'rev1'), (2, 'title2', 'rev1')] # Why session flushed here ? print('SCENARIO: update revision description') content1.revisions[0].description = 'rev2' assert session.query(ContentRevision.revision_id, ContentRevision.title, ContentRevision.description).order_by(ContentRevision. revision_id).all() == \ [(1, 'title1', 'rev1'), (2, 'title2', 'rev1')] # Why session don't flushed here ? print('SCENARIO: flush session') session.flush() assert session.query(ContentRevision.revision_id, ContentRevision.title, ContentRevision.description).order_by(ContentRevision. revision_id).all() == \ [(1, 'title1', 'rev1'), (2, 'title2', 'rev1')] # Why session don't flushed here ? print('SCENARIO: end') Output is (for python3.4 test.py): SCENARIO: Create new content SCENARIO: Add content to session SCENARIO: flush session EVENT: before_flush SCENARIO: update revision title EVENT: before_flush EVENT: before_flush: ContentRevision found in dirty EVENT: before_flush: ContentRevision has been modified EVENT: before_flush: revision updated, create new instead SCENARIO: update revision description SCENARIO: flush session SCENARIO: end Why session flush at *content1.revisions[0].title = 'title2'* line ? Why session *don't *flush at last session.flush() line ? Is it possible to use *before_flush* event to make my "new revision" when finish to edit it's values ? Thank's, Bastien Sevajol. -- 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 https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.