Commit d2a26bcc authored by Jim Fulton's avatar Jim Fulton

- 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.
parent 27c2fc38
......@@ -14,6 +14,17 @@ New Features
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 @@ New Features
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
----------
......
......@@ -101,7 +101,7 @@ class Connection(ExportImport, object):
# 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 @@ class Connection(ExportImport, object):
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 @@ class Connection(ExportImport, object):
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 @@ class Connection(ExportImport, object):
self._debug_info = ()
if self._opened:
if self.opened:
self.transaction_manager.unregisterSynch(self)
if primary:
......@@ -301,15 +301,15 @@ class Connection(ExportImport, object):
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 @@ class Connection(ExportImport, object):
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 @@ class Connection(ExportImport, object):
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 @@ class Connection(ExportImport, object):
register for afterCompletion() calls.
"""
self._opened = time.time()
self.opened = time.time()
if transaction_manager is None:
transaction_manager = transaction.manager
......
......@@ -515,7 +515,7 @@ class DB(object):
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 @@ class DB(object):
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,6 +920,28 @@ class DB(object):
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
......
......@@ -16,6 +16,7 @@ from ZODB.tests.MinPO import MinPO
from zope.testing import doctest
import datetime
import os
import sys
import time
import transaction
import unittest
......@@ -181,6 +182,62 @@ def open_convenience():
>>> 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(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment