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.

Reply via email to