From: Mike Bayer Date: Sat, 6 Oct 2012 16:46:02 +0000 (-0400) Subject: - [bug] Continuing [ticket:2566] regarding extra X-Git-Tag: rel_0_8_0b1~76 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ab59e3e1135e1c5b59d19a54114163119d5ab1a4;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - [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] --- diff --git a/CHANGES b/CHANGES index 67bcb40bd1..33bf4107c9 100644 --- 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() diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index dcbd6ba7e5..70d3bc0bb1 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -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 diff --git a/test/orm/test_session.py b/test/orm/test_session.py index db3d505c31..70b90d29b9 100644 --- a/test/orm/test_session.py +++ b/test/orm/test_session.py @@ -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):