From: Mike Bayer Date: Tue, 30 Apr 2013 14:02:49 +0000 (-0400) Subject: - Fixed a regression from 0.7 where the contextmanager feature X-Git-Tag: rel_0_8_2~79^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0c4f0f891654a378913c060da38829e81327097a;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Fixed a regression from 0.7 where the contextmanager feature of :meth:`.Session.begin_nested` would fail to correctly roll back the transaction when a flush error occurred, instead raising its own exception while leaving the session still pending a rollback. [ticket:2718] --- diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index ed95398911..aa8f0f8786 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -6,6 +6,16 @@ .. changelog:: :version: 0.8.2 + .. change:: + :tags: bug, orm + :tickets: 2718 + + Fixed a regression from 0.7 where the contextmanager feature + of :meth:`.Session.begin_nested` would fail to correctly + roll back the transaction when a flush error occurred, instead + raising its own exception while leaving the session still + pending a rollback. + .. change:: :tags: bug, mysql diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index f7a5558f17..ffb8a4e039 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -169,6 +169,7 @@ class SessionTransaction(object): def _assert_active(self, prepared_ok=False, rollback_ok=False, + deactive_ok=False, closed_msg="This transaction is closed"): if self._state is COMMITTED: raise sa_exc.InvalidRequestError( @@ -182,7 +183,7 @@ class SessionTransaction(object): "SQL can be emitted within this transaction." ) elif self._state is DEACTIVE: - if not rollback_ok: + if not deactive_ok and not rollback_ok: if self._rollback_exception: raise sa_exc.InvalidRequestError( "This Session's transaction has been rolled back " @@ -192,7 +193,7 @@ class SessionTransaction(object): " Original exception was: %s" % self._rollback_exception ) - else: + elif not deactive_ok: raise sa_exc.InvalidRequestError( "This Session's transaction has been rolled back " "by a nested rollback() call. To begin a new " @@ -435,7 +436,7 @@ class SessionTransaction(object): return self def __exit__(self, type, value, traceback): - self._assert_active(prepared_ok=True) + self._assert_active(deactive_ok=True, prepared_ok=True) if self.session.transaction is None: return if type is None: diff --git a/test/orm/test_transaction.py b/test/orm/test_transaction.py index 64b05a131d..2866ab4ab6 100644 --- a/test/orm/test_transaction.py +++ b/test/orm/test_transaction.py @@ -1,4 +1,4 @@ - +from __future__ import with_statement from sqlalchemy.testing import eq_, assert_raises, \ assert_raises_message, assert_warnings from sqlalchemy import * @@ -83,6 +83,8 @@ class SessionTransactionTest(FixtureTest): conn.close() raise + + @testing.requires.savepoints def test_heavy_nesting(self): users = self.tables.users @@ -620,6 +622,66 @@ class CleanSavepointTest(FixtureTest): synchronize_session='fetch') self._run_test(update_fn) +class ContextManagerTest(FixtureTest): + run_inserts = None + + @testing.requires.savepoints + @engines.close_open_connections + def test_contextmanager_nested_rollback(self): + users, User = self.tables.users, self.classes.User + + mapper(User, users) + + sess = Session() + def go(): + with sess.begin_nested(): + sess.add(User()) # name can't be null + sess.flush() + + # and not InvalidRequestError + assert_raises( + sa_exc.DBAPIError, + go + ) + + with sess.begin_nested(): + sess.add(User(name='u1')) + + eq_(sess.query(User).count(), 1) + + def test_contextmanager_commit(self): + users, User = self.tables.users, self.classes.User + + mapper(User, users) + + sess = Session(autocommit=True) + with sess.begin(): + sess.add(User(name='u1')) + + sess.rollback() + eq_(sess.query(User).count(), 1) + + def test_contextmanager_rollback(self): + users, User = self.tables.users, self.classes.User + + mapper(User, users) + + sess = Session(autocommit=True) + def go(): + with sess.begin(): + sess.add(User()) # name can't be null + assert_raises( + sa_exc.DBAPIError, + go + ) + + eq_(sess.query(User).count(), 0) + + with sess.begin(): + sess.add(User(name='u1')) + eq_(sess.query(User).count(), 1) + + class AutoExpireTest(_LocalFixture): def test_expunge_pending_on_rollback(self):