]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- should close the session before rolling back the transaction
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 15 Jan 2014 20:14:24 +0000 (15:14 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 15 Jan 2014 20:14:24 +0000 (15:14 -0500)
- make section title actually say, "such as for test suites"
- add topic section detailing an evented approach to allowing ROLLBACK by using savepoint

doc/build/orm/session.rst

index c4d9b08742b3754c67e28ec22d307190d0f5b673..ea811aab4f3c396866d49922e6ede4f762714379 100644 (file)
@@ -1648,8 +1648,8 @@ proper context for the desired engine::
 
 .. _session_external_transaction:
 
-Joining a Session into an External Transaction
-===============================================
+Joining a Session into an External Transaction (such as for test suites)
+========================================================================
 
 If a :class:`.Connection` is being used which is already in a transactional
 state (i.e. has a :class:`.Transaction` established), a :class:`.Session` can
@@ -1686,11 +1686,12 @@ entire database interaction is rolled back::
             self.session.commit()
 
         def tearDown(self):
+            self.session.close()
+
             # rollback - everything that happened with the
             # Session above (including calls to commit())
             # is rolled back.
             self.trans.rollback()
-            self.session.close()
 
             # return connection to the Engine
             self.connection.close()
@@ -1702,6 +1703,46 @@ nested begin/commit-or-rollback pairs where only the outermost begin/commit
 pair actually commits the transaction, or if the outermost block rolls back,
 everything is rolled back.
 
+.. topic:: Supporting Tests with Rollbacks
+
+   The above recipe works well for any kind of database enabled test, except
+   for a test that needs to actually invoke :meth:`.Session.rollback` within
+   the scope of the test itself.   The above recipe can be expanded, such
+   that the :class:`.Session` always runs all operations within the scope
+   of a SAVEPOINT, which is established at the start of each transaction,
+   so that tests can also rollback the "transaction" as well while still
+   remaining in the scope of a larger "transaction" that's never committed,
+   using two extra events::
+
+      from sqlalchemy import event
+
+      class SomeTest(TestCase):
+          def setUp(self):
+              # connect to the database
+              self.connection = engine.connect()
+
+              # begin a non-ORM transaction
+              self.trans = connection.begin()
+
+              # bind an individual Session to the connection
+              self.session = Session(bind=self.connection)
+
+              # two events make sure a SAVEPOINT is always started
+              # for this session.  After the initial "begin"...
+              @event.listens_for(self.session, "after_begin")
+              def start_savepoint(session, transaction, connection):
+                  if not transaction.nested:
+                      session.begin_nested()
+
+              # ... and after the end of each "transaction", assuming
+              # the transaction ending was the "nested" transaction
+              @event.listens_for(self.session, "after_transaction_end")
+              def restart_savepoint(session, transaction):
+                  if transaction.nested and not transaction._parent.nested:
+                      session.begin_nested()
+
+          # ... the tearDown() method stays the same
+
 .. _unitofwork_contextual:
 
 Contextual/Thread-local Sessions