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
"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)
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(
"(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()
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):
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