Search code examples
pythontransactionszodb

How do you check whether you're in a transaction with ZODB in Python?


I was writing some tests involving ZODB and I got stuck for a long time because of the error I was getting in some unit test. Let's call it Test_B:

Failure/Error: 'NoneType' object has no attribute 'sortKey'
...
 File <<< my code somewhere >>>
     transaction.commit()
 File "/usr/local/lib/python2.7/site-packages/transaction/_manager.py", line 111, in commit
     return self.get().commit()
 File "/usr/local/lib/python2.7/site-packages/transaction/_transaction.py", line 280, in commit
     reraise(t, v, tb)
 File "/usr/local/lib/python2.7/site-packages/transaction/_transaction.py", line 271, in commit
     self._commitResources()
 File "/usr/local/lib/python2.7/site-packages/transaction/_transaction.py", line 386, in _commitResources
     L.sort(key=rm_key)
 File "/usr/local/lib/python2.7/site-packages/transaction/_transaction.py", line 555, in rm_key
     return func()
 File "/usr/local/lib/python2.7/site-packages/ZODB/Connection.py", line 813, in sortKey
     return "%s:%s" % (self._storage.sortKey(), id(self))

Thankfully I found out eventually that I forgot to call a transaction.commit() in a test that runs before Test_B called (unsurprisingly) Test_A. The sequence of events as recorded by the log looks thus:

<<< Test_A begins >>>
23:01:41 DEBUG    txn.140735119446400: new transaction
...
<<< no further mentions of txn.140735119446400 being committed or aborted >>>
<<< Test_A ends >>>
<<< Test_B begins >>>
23:01:41 DEBUG    txn.140735119446400: new transaction
23:01:41 DEBUG    my_spec: *** MANUALLY altered DB in Test_B body
...
<<< Test_B bails out due to error >>>

Note that Test A succeeds: this is wrong! I want Test A to fail and tell me I have outstanding changes in a transaction that I had forgotten to commit.

How can I do this with ZODB? I can't find anything in the documentation that I could use to find out whether I'm in a transaction with changes or not.

Obviously, with that check, then I can stick it into the AfterEach block of all my unit tests in the suite.


Solution

  • You'd normally close the connection to the ZODB between tests, to make sure the tests are isolated.

    Closing a ZODB connection with an active transaction will raise an exception:

    >>> from ZODB.FileStorage import FileStorage
    >>> from ZODB.DB import DB
    >>> storage = FileStorage('Data.fs')
    >>> db = DB(storage)
    >>> connection = db.open()
    >>> root = connection.root()
    >>> root['foo'] = 'bar'
    >>> connection.close()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Users/mj/Development/venvs/stackoverflow-2.7/lib/python2.7/site-packages/ZODB/Connection.py", line 286, in close
        raise ConnectionStateError("Cannot close a connection joined to "
    ZODB.POSException.ConnectionStateError: Cannot close a connection joined to a transaction