Commits

Mike Bayer committed 9ad3597

- rearranged delete() so that the object is attached before
cascades fire off [ticket:5058]
- after_attach() only fires if the object was not previously attached

Comments (0)

Files changed (2)

lib/sqlalchemy/orm/session.py

         except exc.NO_STATE:
             raise exc.UnmappedInstanceError(instance)
 
-        # grab the full cascade list first, since lazyloads/autoflush
-        # may be triggered by this operation (delete cascade lazyloads by default)
+        if state.key is None:
+            raise sa_exc.InvalidRequestError(
+                "Instance '%s' is not persisted" %
+                mapperutil.state_str(state))
+
+        if state in self._deleted:
+            return
+            
+        # ensure object is attached to allow the 
+        # cascade operation to load deferred attributes
+        # and collections
+        self._attach(state)
+
+        # grab the cascades before adding the item to the deleted list
+        # so that autoflush does not delete the item
         cascade_states = list(_cascade_state_iterator('delete', state))
-        self._delete_impl(state)
+
+        self._deleted[state] = state.obj()
+        self.identity_map.add(state)
+
         for state, m, o in cascade_states:
-            self._delete_impl(state, ignore_transient=True)
+            self._delete_impl(state)
 
     def merge(self, instance, dont_load=False,
               _recursive=None):
         if (self.identity_map.contains_state(state) and
             state not in self._deleted):
             return
+            
         if state.key is None:
             raise sa_exc.InvalidRequestError(
                 "Instance '%s' is not persisted" %
                 mapperutil.state_str(state))
 
-        if (state.key in self.identity_map and
-            not self.identity_map.contains_state(state)):
-            raise sa_exc.InvalidRequestError(
-                "Could not update instance '%s', identity key %s; a different "
-                "instance with the same identity key already exists in this "
-                "session." % (mapperutil.state_str(state), state.key))
-
         self._attach(state)
         self._deleted.pop(state, None)
         self.identity_map.add(state)
         else:
             self._update_impl(state)
 
-    def _delete_impl(self, state, ignore_transient=False):
-        if self.identity_map.contains_state(state) and state in self._deleted:
+    def _delete_impl(self, state):
+        if state in self._deleted:
             return
 
         if state.key is None:
-            if ignore_transient:
-                return
-            else:
-                raise sa_exc.InvalidRequestError(
-                    "Instance '%s' is not persisted" %
-                    mapperutil.state_str(state))
-        if (state.key in self.identity_map and
-            not self.identity_map.contains_state(state)):
-            raise sa_exc.InvalidRequestError(
-                "Instance '%s' is with key %s already persisted with a "
-                "different identity" % (mapperutil.state_str(state),
-                                        state.key))
-
+            return
+                    
         self._attach(state)
         self._deleted[state] = state.obj()
         self.identity_map.add(state)
-
+    
     def _attach(self, state):
+        if state.key and \
+            state.key in self.identity_map and \
+            not self.identity_map.contains_state(state):
+            raise sa_exc.InvalidRequestError(
+                "Can't attach instance %s; another instance with key %s is already present in this session." % 
+                    (mapperutil.state_str(state), state.key)
+                )
+                
         if state.session_id and state.session_id is not self.hash_key:
             raise sa_exc.InvalidRequestError(
                 "Object '%s' is already attached to session '%s' "
                 "(this is '%s')" % (mapperutil.state_str(state),
                                     state.session_id, self.hash_key))
+                                    
         if state.session_id != self.hash_key:
             state.session_id = self.hash_key
-        for ext in self.extensions:
-            ext.after_attach(self, state.obj())
+            for ext in self.extensions:
+                ext.after_attach(self, state.obj())
 
     def __contains__(self, instance):
         """Return True if the instance is associated with this session.

test/orm/session.py

     def test_save_update_delete(self):
 
         s = create_session()
-        mapper(User, users)
+        mapper(User, users, properties={
+            'addresses':relation(Address, cascade="all, delete")
+        })
+        mapper(Address, addresses)
 
         user = User(name='u1')
 
         self.assertRaisesMessage(sa.exc.InvalidRequestError, "is already attached to session", s2.delete, user)
 
         u2 = s2.query(User).get(user.id)
-        self.assertRaisesMessage(sa.exc.InvalidRequestError, "already persisted with a different identity", s.delete, u2)
+        self.assertRaisesMessage(sa.exc.InvalidRequestError, "another instance with key", s.delete, u2)
 
+        s.expire(user)
         s.expunge(user)
         assert user not in s
         s.delete(user)