What exactly causes the session to flush?  I'm trying to track down a nasty 
bug in my versioning system.

Sorry for the long code dump.  I retooled 
examples/versioned_history/history_meta.py so it should look familiar.  The 
function that's breaking is "column_has_changed". I've added some logs as 
well.

# WHEN IT WORKS!

CRITICAL:root:BEFORE MAPPING NEW VALUES
CRITICAL:root:BEFORE SAVE
CRITICAL:root:BEFORE REVISE
CRITICAL:root:CHECK COLUMN CHANGES
CRITICAL:root:AFTER REVISE
CRITICAL:root:flush!
CRITICAL:root:AFTER SAVE
CRITICAL:root:flush!

# WHEN IT DOESN'T WORK!

CRITICAL:root:BEFORE MAPPING NEW VALUES
CRITICAL:root:BEFORE SAVE
CRITICAL:root:BEFORE REVISE
CRITICAL:root:flush!
CRITICAL:root:CHECK COLUMN CHANGES
CRITICAL:root:AFTER REVISE
CRITICAL:root:AFTER SAVE
CRITICAL:root:flush!

controller.py

for k, v in dict.items():
    setattr(model, k, v)
model.revise()
db.session.add(model)
db.session.commit()

model.py

class RevisionMixin:
    """Version control manager."""

    def revise(self):
        db.session.add(self)
        write_revision(self)

version.py

def write_revision(target):
    target_mapper = orm.object_mapper(target)
    revision_class = target.__versioned__['model']
    revision_mapper = revision_class.__mapper__

    object_changed = False
    state = {}

    for column in iter_mapper_columns(target_mapper, revision_mapper):
        state[column.key] = getattr(target, column.key)
        column_changed = column_has_changed(target, column.key)
        object_changed = object_changed or column_changed

    for relationship, changed in iter_relationships(target, target_mapper):
        if hasattr(revision_class, relationship.key):
            state[relationship.key] = getattr(target, relationship.key)
        object_changed = object_changed or changed

    if not isinstance(target.id, str) or object_changed:
        _write_revision(target, state)


def _write_revision(target, state):
    version = target.version or 0
    version = version + 1
    state['version'] = version
    state['updated_at'] = db.now()
    state['primary'] = target

    revision = target.__versioned__['model'](**state)
    db.session.add(revision)

    target.version = version
    target.updated_at = state['updated_at']


def iter_mapper_columns(primary, revision):
    mappers = zip(primary.iterate_to_root(), revision.iterate_to_root())
    for om, hm in mappers:
        if hm.single:
            continue
        for column in iter_shared_columns(om, hm):
            yield column


def iter_shared_columns(mapper, comparison_mapper):
    for comparison_mapper_column in comparison_mapper.local_table.c:
        if 'version_meta' in comparison_mapper_column.info:
            continue

        try:
            mapper_column = mapper.local_table.c[comparison_mapper_column.
key]
            yield mapper.get_property_by_column(mapper_column)
        except UnmappedColumnError:
            continue


def iter_relationships(target, mapper):
    for prop in mapper.iterate_properties:
        if isinstance(prop, RelationshipProperty):
            passive = attributes.PASSIVE_NO_INITIALIZE
            changed = attributes.get_history(
                target, prop.key, passive=passive).has_changes()
            yield prop, changed


def column_has_changed(target, column_name):
    # Sometimes the instance state history can't be properly
    # calculated?  No flushing during versioning.  Unsure why its not
    # working.
    added, _, deleted = attributes.get_history(target, column_name)
    return bool(added or deleted)


def relationship_has_changed(prop):
    for p in prop.local_columns:
        if p.foreign_keys:
            return True
    return False


-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 
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