]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed bug where the session attachment error "object is already
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 10 Mar 2015 21:51:35 +0000 (17:51 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 10 Mar 2015 21:51:35 +0000 (17:51 -0400)
attached to session X" would fail to prevent the object from
also being attached to the new session, in the case that execution
continued after the error raise occurred.
fixes #3301

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/orm/session.py
test/orm/test_session.py

index b54a43aaea8e82368c920578e7d5e3059b7f0e0f..474cec093917a19900d48f6055bad7fa73159336 100644 (file)
     series as well.  For changes that are specific to 1.0 with an emphasis
     on compatibility concerns, see :doc:`/changelog/migration_10`.
 
+    .. change::
+        :tags: bug, orm
+        :tickets: 3301
+
+        Fixed bug where the session attachment error "object is already
+        attached to session X" would fail to prevent the object from
+        also being attached to the new session, in the case that execution
+        continued after the error raise occurred.
+
     .. change::
         :tags: bug, ext
         :tickets: 3219, 3240
index f02d8b54efe1d7230e44fcaffced3437cfe7f645..bc9444040eeb39f391883dbad629cd9aaac1b1cc 100644 (file)
@@ -1830,7 +1830,7 @@ class Session(_SessionClassMethods):
                 "function to send this object back to the transient state." %
                 state_str(state)
             )
-        self._before_attach(state)
+        self._before_attach(state, check_identity_map=False)
         self._deleted.pop(state, None)
         if discard_existing:
             self.identity_map.replace(state)
@@ -1910,13 +1910,12 @@ class Session(_SessionClassMethods):
         self._attach(state, include_before=True)
         state._load_pending = True
 
-    def _before_attach(self, state):
+    def _before_attach(self, state, check_identity_map=True):
         if state.session_id != self.hash_key and \
                 self.dispatch.before_attach:
             self.dispatch.before_attach(self, state.obj())
 
-    def _attach(self, state, include_before=False):
-        if state.key and \
+        if check_identity_map and state.key and \
             state.key in self.identity_map and \
                 not self.identity_map.contains_state(state):
             raise sa_exc.InvalidRequestError(
@@ -1932,10 +1931,11 @@ class Session(_SessionClassMethods):
                 "(this is '%s')" % (state_str(state),
                                     state.session_id, self.hash_key))
 
+    def _attach(self, state, include_before=False):
+
         if state.session_id != self.hash_key:
-            if include_before and \
-                    self.dispatch.before_attach:
-                self.dispatch.before_attach(self, state.obj())
+            if include_before:
+                self._before_attach(state)
             state.session_id = self.hash_key
             if state.modified and state._strong_obj is None:
                 state._strong_obj = state.obj()
index 2aa0cd3eb942f33b11f905be13c590c5ddface90..58551d763ede152596fb57e3abc92749b583db4b 100644 (file)
@@ -505,6 +505,25 @@ class SessionStateTest(_fixtures.FixtureTest):
         assert user not in s
         assert s.query(User).count() == 0
 
+    def test_already_attached(self):
+        User = self.classes.User
+        users = self.tables.users
+        mapper(User, users)
+
+        s1 = Session()
+        s2 = Session()
+
+        u1 = User(id=1, name='u1')
+        make_transient_to_detached(u1)  # shorthand for actually persisting it
+        s1.add(u1)
+
+        assert_raises_message(
+            sa.exc.InvalidRequestError,
+            "Object '<User.*?>' is already attached to session",
+            s2.add, u1
+        )
+        assert u1 not in s2
+        assert not s2.identity_map.keys()
 
     @testing.uses_deprecated()
     def test_identity_conflict(self):
@@ -562,7 +581,7 @@ class SessionStateTest(_fixtures.FixtureTest):
         assert u2 is not None and u2 is not u1
         assert u2 in sess
 
-        assert_raises(Exception, lambda: sess.add(u1))
+        assert_raises(AssertionError, lambda: sess.add(u1))
 
         sess.expunge(u2)
         assert u2 not in sess