]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- [bug] Continuing [ticket:2566] regarding extra
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 6 Oct 2012 16:46:02 +0000 (12:46 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 6 Oct 2012 16:46:02 +0000 (12:46 -0400)
    state post-flush due to event listeners;
    any states that are marked as "dirty" from an
    attribute perspective, usually via column-attribute
    set events within after_insert(), after_update(),
    etc., will get the "history" flag reset
    in all cases, instead of only those instances
    that were part of the flush.  This has the effect
    that this "dirty" state doesn't carry over
    after the flush and won't result in UPDATE
    statements.   A warning is emitted to this
    effect; the set_committed_state()
    method can be used to assign attributes on objects
    without producing history events. [ticket:2582]

CHANGES
lib/sqlalchemy/orm/session.py
test/orm/test_session.py

diff --git a/CHANGES b/CHANGES
index 67bcb40bd17721282039c6c296ad11ae0f1b9b36..33bf4107c9c2a76b8c88104cc03ac45494ed9415 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -246,6 +246,21 @@ underneath "0.7.xx".
     inside of the flush, but for now, results
     can't be guaranteed.
 
+  - [bug] Continuing [ticket:2566] regarding extra
+    state post-flush due to event listeners;
+    any states that are marked as "dirty" from an
+    attribute perspective, usually via column-attribute
+    set events within after_insert(), after_update(),
+    etc., will get the "history" flag reset
+    in all cases, instead of only those instances
+    that were part of the flush.  This has the effect
+    that this "dirty" state doesn't carry over
+    after the flush and won't result in UPDATE
+    statements.   A warning is emitted to this
+    effect; the set_committed_state()
+    method can be used to assign attributes on objects
+    without producing history events. [ticket:2582]
+
   - [feature] ORM entities can be passed
     to select() as well as the select_from(),
     correlate(), and correlate_except()
index dcbd6ba7e53cf85d4c1e86574a18b46b09dedfb5..70d3bc0bb12178db61bccc62a448be4c367f18ec 100644 (file)
@@ -1841,6 +1841,21 @@ class Session(_SessionClassMethods):
 
             flush_context.finalize_flush_changes()
 
+            if not objects and self.identity_map._modified:
+                len_ = len(self.identity_map._modified)
+
+                statelib.InstanceState._commit_all_states(
+                                [(state, state.dict) for state in
+                                        self.identity_map._modified],
+                                instance_dict=self.identity_map)
+                util.warn("Attribute history events accumulated on %d "
+                        "previously clean instances "
+                        "within inner-flush event handlers have been reset, "
+                        "and will not result in database updates. "
+                        "Consider using set_committed_value() within "
+                        "inner-flush event handlers to avoid this warning."
+                                    % len_)
+
             # useful assertions:
             #if not objects:
             #    assert not self.identity_map._modified
index db3d505c313ac39905122a91b61b6fead49bad6b..70b90d29b9f088df8822b3dd4e2b60ab8038175e 100644 (file)
@@ -740,6 +740,48 @@ class SessionStateTest(_fixtures.FixtureTest):
                 del u3
                 gc_collect()
 
+    def _test_extra_dirty_state(self):
+        users, User = self.tables.users, self.classes.User
+        m = mapper(User, users)
+
+        s = Session()
+
+        @event.listens_for(m, "after_update")
+        def e(mapper, conn, target):
+            sess = object_session(target)
+            for entry in sess.identity_map.values():
+                entry.name = "5"
+
+        a1, a2 = User(name="1"), User(name="2")
+
+        s.add_all([a1, a2])
+        s.commit()
+
+        a1.name = "3"
+        return s, a1, a2
+
+    def test_extra_dirty_state_post_flush_warning(self):
+        s, a1, a2 = self._test_extra_dirty_state()
+        assert_raises_message(
+            sa.exc.SAWarning,
+            "Attribute history events accumulated on 1 previously "
+            "clean instances",
+            s.commit
+        )
+
+    def test_extra_dirty_state_post_flush_state(self):
+        s, a1, a2 = self._test_extra_dirty_state()
+        canary = []
+        @event.listens_for(s, "after_flush_postexec")
+        def e(sess, ctx):
+            canary.append(bool(sess.identity_map._modified))
+
+        @testing.emits_warning("Attribute")
+        def go():
+            s.commit()
+        go()
+        eq_(canary, [False])
+
 class SessionStateWFixtureTest(_fixtures.FixtureTest):
 
     def test_autoflush_rollback(self):