[Zodb-checkins] SVN: ZODB/trunk/src/ - Databases have a new method, transaction, that can be used with the

Jim Fulton jim at zope.com
Mon Apr 27 18:07:57 EDT 2009


Log message for revision 99543:
  - Databases have a new method, transaction, that can be used with the
    Python (2.5 and later) with statement::
  
  db = ZODB.DB(...)
       with db.transaction() as conn:
            # ... do stuff with conn
  
  This uses a private transaction manager for the connection.
    If control exists the block without an error, the transaction is
    committed, otherwise, it is aborted.
  
  - Connections now have a public ``opened`` attribute that is true when
    the connection is open, and false otherwise.  When true, it is the
    seconds since the epoch (time.time()) when the connection was
    opened. This is a renaming of the previous ``_opened`` private
    variable.
  

Changed:
  U   ZODB/trunk/src/CHANGES.txt
  U   ZODB/trunk/src/ZODB/Connection.py
  U   ZODB/trunk/src/ZODB/DB.py
  U   ZODB/trunk/src/ZODB/tests/testDB.py

-=-
Modified: ZODB/trunk/src/CHANGES.txt
===================================================================
--- ZODB/trunk/src/CHANGES.txt	2009-04-27 21:35:05 UTC (rev 99542)
+++ ZODB/trunk/src/CHANGES.txt	2009-04-27 22:07:57 UTC (rev 99543)
@@ -14,6 +14,17 @@
 New Features
 ------------
 
+- Databases have a new method, transaction, that can be used with the
+  Python (2.5 and later) with statement::
+
+     db = ZODB.DB(...)
+     with db.transaction() as conn:
+          # ... do stuff with conn
+
+  This uses a private transaction manager for the connection.
+  If control exists the block without an error, the transaction is
+  committed, otherwise, it is aborted.
+
 - Convenience methods ZODB.DB.open and ZEO.DB.open provide a
   convenient way to open a connection to a database.  They open a
   database and return a connection to it. When the connection is
@@ -48,6 +59,13 @@
   There are corresponding methods to read and set the new configuration
   parameters.
 
+- Connections now have a public ``opened`` attribute that is true when
+  the connection is open, and false otherwise.  When true, it is the
+  seconds since the epoch (time.time()) when the connection was
+  opened. This is a renaming of the previous ``_opened`` private
+  variable.
+
+
 Bugs Fixed
 ----------
 

Modified: ZODB/trunk/src/ZODB/Connection.py
===================================================================
--- ZODB/trunk/src/ZODB/Connection.py	2009-04-27 21:35:05 UTC (rev 99542)
+++ ZODB/trunk/src/ZODB/Connection.py	2009-04-27 22:07:57 UTC (rev 99543)
@@ -101,7 +101,7 @@
         # Do we need to join a txn manager?
         self._needs_to_join = True
         self.transaction_manager = None
-        self._opened = None # time.time() when DB.open() opened us
+        self.opened = None # time.time() when DB.open() opened us
 
         self._reset_counter = global_reset_counter
         self._load_count = 0   # Number of objects unghosted
@@ -196,7 +196,7 @@
 
     def add(self, obj):
         """Add a new object 'obj' to the database and assign it an oid."""
-        if self._opened is None:
+        if self.opened is None:
             raise ConnectionStateError("The database connection is closed")
 
         marker = object()
@@ -220,7 +220,7 @@
 
     def get(self, oid):
         """Return the persistent object with oid 'oid'."""
-        if self._opened is None:
+        if self.opened is None:
             raise ConnectionStateError("The database connection is closed")
 
         obj = self._cache.get(oid, None)
@@ -292,7 +292,7 @@
 
         self._debug_info = ()
 
-        if self._opened:
+        if self.opened:
             self.transaction_manager.unregisterSynch(self)
 
         if primary:
@@ -301,15 +301,15 @@
                     connection.close(False)
 
             # Return the connection to the pool.
-            if self._opened is not None:
+            if self.opened is not None:
                 self._db._returnToPool(self)
 
-                # _returnToPool() set self._opened to None.
+                # _returnToPool() set self.opened to None.
                 # However, we can't assert that here, because self may
                 # have been reused (by another thread) by the time we
                 # get back here.
         else:
-            self._opened = None
+            self.opened = None
 
     def db(self):
         """Returns a handle to the database this connection belongs to."""
@@ -317,7 +317,7 @@
 
     def isReadOnly(self):
         """Returns True if this connection is read only."""
-        if self._opened is None:
+        if self.opened is None:
             raise ConnectionStateError("The database connection is closed")
         return self.before is not None or self._storage.isReadOnly()
 
@@ -798,7 +798,7 @@
         the database."""
         oid = obj._p_oid
 
-        if self._opened is None:
+        if self.opened is None:
             msg = ("Shouldn't load state for %s "
                    "when the connection is closed" % oid_repr(oid))
             self._log.error(msg)
@@ -1010,7 +1010,7 @@
         register for afterCompletion() calls.
         """
 
-        self._opened = time.time()
+        self.opened = time.time()
 
         if transaction_manager is None:
             transaction_manager = transaction.manager

Modified: ZODB/trunk/src/ZODB/DB.py
===================================================================
--- ZODB/trunk/src/ZODB/DB.py	2009-04-27 21:35:05 UTC (rev 99542)
+++ ZODB/trunk/src/ZODB/DB.py	2009-04-27 22:07:57 UTC (rev 99543)
@@ -515,7 +515,7 @@
         self._a()
         try:
             assert connection._db is self
-            connection._opened = None
+            connection.opened = None
 
             am = self._activity_monitor
             if am is not None:
@@ -783,7 +783,7 @@
 
         def get_info(c):
             # `result`, `time` and `before` are lexically inherited.
-            o = c._opened
+            o = c.opened
             d = c.getDebugInfo()
             if d:
                 if len(d) == 1:
@@ -920,7 +920,29 @@
             txn = transaction.get()
         txn.register(TransactionalUndo(self, id))
 
+    def transaction(self):
+        return ContextManager(self)
 
+
+class ContextManager:
+    """PEP 343 context manager
+    """
+
+    def __init__(self, db):
+        self.db = db
+
+    def __enter__(self):
+        self.tm = transaction.TransactionManager()
+        self.conn = self.db.open(self.tm)
+        return self.conn
+
+    def __exit__(self, t, v, tb):
+        if t is None:
+            self.tm.commit()
+        else:
+            self.tm.abort()
+        self.conn.close()
+
 resource_counter_lock = threading.Lock()
 resource_counter = 0
 

Modified: ZODB/trunk/src/ZODB/tests/testDB.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testDB.py	2009-04-27 21:35:05 UTC (rev 99542)
+++ ZODB/trunk/src/ZODB/tests/testDB.py	2009-04-27 22:07:57 UTC (rev 99543)
@@ -16,6 +16,7 @@
 from zope.testing import doctest
 import datetime
 import os
+import sys
 import time
 import transaction
 import unittest
@@ -181,6 +182,62 @@
     >>> db.close()
     """
 
+if sys.version_info >= (2, 6):
+    def db_with_transaction():
+        """Using databases with with
+
+        The transaction method returns a context manager that when entered
+        starts a transaction with a private transaction manager.  To
+        illustrate this, we start a trasnaction using a regular connection
+        and see that it isn't automatically committed or aborted as we use
+        the transaction context manager.
+
+        >>> db = ZODB.DB('data.fs')
+        >>> conn = db.open()
+        >>> conn.root()['x'] = conn.root().__class__()
+        >>> transaction.commit()
+        >>> conn.root()['x']['x'] = 1
+
+        >>> with db.transaction() as conn2:
+        ...     conn2.root()['y'] = 1
+
+        >>> conn2.opened
+
+    Now, we'll open a 3rd connection a verify that
+
+        >>> conn3 = db.open()
+        >>> conn3.root()['x']
+        {}
+        >>> conn3.root()['y']
+        1
+        >>> conn3.close()
+
+    Let's try again, but this time, we'll have an exception:
+
+        >>> with db.transaction() as conn2:
+        ...     conn2.root()['y'] = 2
+        ...     XXX
+        Traceback (most recent call last):
+        ...
+        NameError: name 'XXX' is not defined
+
+        >>> conn2.opened
+
+        >>> conn3 = db.open()
+        >>> conn3.root()['x']
+        {}
+        >>> conn3.root()['y']
+        1
+        >>> conn3.close()
+
+        >>> transaction.commit()
+
+        >>> conn3 = db.open()
+        >>> conn3.root()['x']
+        {'x': 1}
+
+        """
+
 def test_suite():
     s = unittest.makeSuite(DBTests)
     s.addTest(doctest.DocTestSuite(



More information about the Zodb-checkins mailing list