[Checkins] SVN: transaction/branches/sphinx/ Move whole-file doctests into docs/.

Tres Seaver cvs-admin at zope.org
Mon Dec 17 20:28:49 UTC 2012


Log message for revision 128696:
  Move whole-file doctests into docs/.

Changed:
  _U  transaction/branches/sphinx/
  A   transaction/branches/sphinx/docs/convenience.rst
  A   transaction/branches/sphinx/docs/doom.rst
  U   transaction/branches/sphinx/docs/index.rst
  A   transaction/branches/sphinx/docs/savepoint.rst
  D   transaction/branches/sphinx/transaction/tests/convenience.txt
  D   transaction/branches/sphinx/transaction/tests/doom.txt
  D   transaction/branches/sphinx/transaction/tests/savepoint.txt
  U   transaction/branches/sphinx/transaction/tests/savepointsample.py
  U   transaction/branches/sphinx/transaction/tests/test_savepoint.py
  U   transaction/branches/sphinx/transaction/tests/test_transaction.py

-=-
Copied: transaction/branches/sphinx/docs/convenience.rst (from rev 128695, transaction/branches/sphinx/transaction/tests/convenience.txt)
===================================================================
--- transaction/branches/sphinx/docs/convenience.rst	                        (rev 0)
+++ transaction/branches/sphinx/docs/convenience.rst	2012-12-17 20:28:48 UTC (rev 128696)
@@ -0,0 +1,180 @@
+Transaction convenience support
+===============================
+
+(We *really* need to write proper documentation for the transaction
+ package, but I don't want to block the conveniences documented here
+ for that.)
+
+with support
+------------
+
+We can now use the with statement to define transaction boundaries.
+
+.. doctest::
+
+    >>> import transaction.tests.savepointsample
+    >>> dm = transaction.tests.savepointsample.SampleSavepointDataManager()
+    >>> list(dm.keys())
+    []
+
+We can use it with a manager:
+
+.. doctest::
+
+    >>> with transaction.manager as t:
+    ...     dm['z'] = 3
+    ...     t.note('test 3')
+
+    >>> dm['z']
+    3
+
+    >>> dm.last_note
+    'test 3'
+
+    >>> with transaction.manager: #doctest ELLIPSIS
+    ...     dm['z'] = 4
+    ...     xxx
+    Traceback (most recent call last):
+    ...
+    NameError: ... name 'xxx' is not defined
+
+    >>> dm['z']
+    3
+
+On Python 2, you can also abbreviate ``with transaction.manager:`` as ``with
+transaction:``.  This does not work on Python 3 (see see
+http://bugs.python.org/issue12022).
+
+Retries
+-------
+
+Commits can fail for transient reasons, especially conflicts.
+Applications will often retry transactions some number of times to
+overcome transient failures.  This typically looks something like:
+
+.. doctest::
+
+    for i in range(3):
+        try:
+           with transaction.manager:
+               ... some something ...
+        except SomeTransientException:
+           contine
+        else:
+           break
+
+This is rather ugly.
+
+Transaction managers provide a helper for this case. To show this,
+we'll use a contrived example:
+
+.. doctest::
+
+    >>> ntry = 0
+    >>> with transaction.manager:
+    ...      dm['ntry'] = 0
+
+    >>> import transaction.interfaces
+    >>> class Retry(transaction.interfaces.TransientError):
+    ...     pass
+
+    >>> for attempt in transaction.manager.attempts():
+    ...     with attempt as t:
+    ...         t.note('test')
+    ...         print("%s %s" % (dm['ntry'], ntry))
+    ...         ntry += 1
+    ...         dm['ntry'] = ntry
+    ...         if ntry % 3:
+    ...             raise Retry(ntry)
+    0 0
+    0 1
+    0 2
+
+The raising of a subclass of TransientError is critical here. It's
+what signals that the transaction should be retried.  It is generally
+up to the data manager to signal that a transaction should try again
+by raising a subclass of TransientError (or TransientError itself, of
+course).
+
+You shouldn't make any assumptions about the object returned by the
+iterator.  (It isn't a transaction or transaction manager, as far as
+you know. :)  If you use the ``as`` keyword in the ``with`` statement,
+a transaction object will be assigned to the variable named.
+
+By default, it tries 3 times. We can tell it how many times to try:
+
+.. doctest::
+
+    >>> for attempt in transaction.manager.attempts(2):
+    ...     with attempt:
+    ...         ntry += 1
+    ...         if ntry % 3:
+    ...             raise Retry(ntry)
+    Traceback (most recent call last):
+    ...
+    Retry: 5
+
+It it doesn't succeed in that many times, the exception will be
+propagated.
+
+Of course, other errors are propagated directly:
+
+.. doctest::
+
+    >>> ntry = 0
+    >>> for attempt in transaction.manager.attempts():
+    ...     with attempt:
+    ...         ntry += 1
+    ...         if ntry == 3:
+    ...             raise ValueError(ntry)
+    Traceback (most recent call last):
+    ...
+    ValueError: 3
+
+We can use the default transaction manager:
+
+.. doctest::
+
+    >>> for attempt in transaction.attempts():
+    ...     with attempt as t:
+    ...         t.note('test')
+    ...         print("%s %s" % (dm['ntry'], ntry))
+    ...         ntry += 1
+    ...         dm['ntry'] = ntry
+    ...         if ntry % 3:
+    ...             raise Retry(ntry)
+    3 3
+    3 4
+    3 5
+
+Sometimes, a data manager doesn't raise exceptions directly, but
+wraps other other systems that raise exceptions outside of it's
+control.  Data  managers can provide a should_retry method that takes
+an exception instance and returns True if the transaction should be
+attempted again.
+
+.. doctest::
+
+    >>> class DM(transaction.tests.savepointsample.SampleSavepointDataManager):
+    ...     def should_retry(self, e):
+    ...         if 'should retry' in str(e):
+    ...             return True
+
+    >>> ntry = 0
+    >>> dm2 = DM()
+    >>> with transaction.manager:
+    ...     dm2['ntry'] = 0
+    >>> for attempt in transaction.manager.attempts():
+    ...     with attempt:
+    ...         print("%s %s" % (dm['ntry'], ntry))
+    ...         ntry += 1
+    ...         dm['ntry'] = ntry
+    ...         dm2['ntry'] = ntry
+    ...         if ntry % 3:
+    ...             raise ValueError('we really should retry this')
+    6 0
+    6 1
+    6 2
+
+    >>> dm2['ntry']
+    3

Copied: transaction/branches/sphinx/docs/doom.rst (from rev 128695, transaction/branches/sphinx/transaction/tests/doom.txt)
===================================================================
--- transaction/branches/sphinx/docs/doom.rst	                        (rev 0)
+++ transaction/branches/sphinx/docs/doom.rst	2012-12-17 20:28:48 UTC (rev 128696)
@@ -0,0 +1,164 @@
+Dooming Transactions
+====================
+
+A doomed transaction behaves exactly the same way as an active transaction but
+raises an error on any attempt to commit it, thus forcing an abort.
+
+Doom is useful in places where abort is unsafe and an exception cannot be
+raised.  This occurs when the programmer wants the code following the doom to
+run but not commit. It is unsafe to abort in these circumstances as a following
+get() may implicitly open a new transaction.
+
+Any attempt to commit a doomed transaction will raise a DoomedTransaction
+exception.
+
+An example of such a use case can be found in
+zope/app/form/browser/editview.py.  Here a form validation failure must doom
+the transaction as committing the transaction may have side-effects. However,
+the form code must continue to calculate a form containing the error messages
+to return.
+
+For Zope in general, code running within a request should always doom
+transactions rather than aborting them. It is the responsibilty of the
+publication to either abort() or commit() the transaction. Application code can
+use savepoints and doom() safely.
+
+To see how it works we first need to create a stub data manager:
+
+.. doctest::
+
+    >>> from transaction.interfaces import IDataManager
+    >>> from zope.interface import implementer
+    >>> @implementer(IDataManager)
+    ... class DataManager:
+    ...     def __init__(self):
+    ...         self.attr_counter = {}
+    ...     def __getattr__(self, name):
+    ...         def f(transaction):
+    ...             self.attr_counter[name] = self.attr_counter.get(name, 0) + 1
+    ...         return f
+    ...     def total(self):
+    ...         count = 0
+    ...         for access_count in self.attr_counter.values():
+    ...             count += access_count
+    ...         return count
+    ...     def sortKey(self):
+    ...         return 1
+
+Start a new transaction:
+
+.. doctest::
+
+    >>> import transaction
+    >>> txn = transaction.begin()
+    >>> dm = DataManager()
+    >>> txn.join(dm)
+
+We can ask a transaction if it is doomed to avoid expensive operations. An
+example of a use case is an object-relational mapper where a pre-commit hook
+sends all outstanding SQL to a relational database for objects changed during
+the transaction. This expensive operation is not necessary if the transaction
+has been doomed. A non-doomed transaction should return False:
+
+.. doctest::
+
+    >>> txn.isDoomed()
+    False
+
+We can doom a transaction by calling .doom() on it:
+
+.. doctest::
+
+    >>> txn.doom()
+    >>> txn.isDoomed()
+    True
+
+We can doom it again if we like:
+
+.. doctest::
+
+    >>> txn.doom()
+
+The data manager is unchanged at this point:
+
+.. doctest::
+
+    >>> dm.total()
+    0
+
+Attempting to commit a doomed transaction any number of times raises a
+DoomedTransaction:
+
+.. doctest::
+
+    >>> txn.commit() # doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    DoomedTransaction: transaction doomed, cannot commit
+    >>> txn.commit() # doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    DoomedTransaction: transaction doomed, cannot commit
+
+But still leaves the data manager unchanged:
+
+.. doctest::
+
+    >>> dm.total()
+    0
+
+But the doomed transaction can be aborted:
+
+.. doctest::
+
+    >>> txn.abort()
+
+Which aborts the data manager:
+
+.. doctest::
+
+    >>> dm.total()
+    1
+    >>> dm.attr_counter['abort']
+    1
+
+Dooming the current transaction can also be done directly from the transaction
+module. We can also begin a new transaction directly after dooming the old one:
+
+.. doctest::
+
+    >>> txn = transaction.begin()
+    >>> transaction.isDoomed()
+    False
+    >>> transaction.doom()
+    >>> transaction.isDoomed()
+    True
+    >>> txn = transaction.begin()
+
+After committing a transaction we get an assertion error if we try to doom the
+transaction. This could be made more specific, but trying to doom a transaction
+after it's been committed is probably a programming error:
+
+.. doctest::
+
+    >>> txn = transaction.begin()
+    >>> txn.commit()
+    >>> txn.doom()
+    Traceback (most recent call last):
+        ...
+    AssertionError
+
+A doomed transaction should act the same as an active transaction, so we should
+be able to join it:
+
+.. doctest::
+
+    >>> txn = transaction.begin()
+    >>> txn.doom()
+    >>> dm2 = DataManager()
+    >>> txn.join(dm2)
+
+Clean up:
+
+.. doctest::
+
+    >>> txn = transaction.begin()
+    >>> txn.abort()

Modified: transaction/branches/sphinx/docs/index.rst
===================================================================
--- transaction/branches/sphinx/docs/index.rst	2012-12-17 17:28:39 UTC (rev 128695)
+++ transaction/branches/sphinx/docs/index.rst	2012-12-17 20:28:48 UTC (rev 128696)
@@ -6,6 +6,9 @@
 .. toctree::
    :maxdepth: 2
 
+   convenience
+   doom
+   savepoint
    api
 
 

Copied: transaction/branches/sphinx/docs/savepoint.rst (from rev 128695, transaction/branches/sphinx/transaction/tests/savepoint.txt)
===================================================================
--- transaction/branches/sphinx/docs/savepoint.rst	                        (rev 0)
+++ transaction/branches/sphinx/docs/savepoint.rst	2012-12-17 20:28:48 UTC (rev 128696)
@@ -0,0 +1,326 @@
+Savepoints
+==========
+
+Savepoints provide a way to save to disk intermediate work done during
+a transaction allowing:
+
+- partial transaction (subtransaction) rollback (abort)
+
+- state of saved objects to be freed, freeing on-line memory for other
+  uses
+
+Savepoints make it possible to write atomic subroutines that don't
+make top-level transaction commitments.
+
+
+Applications
+------------
+
+To demonstrate how savepoints work with transactions, we've provided a sample
+data manager implementation that provides savepoint support.  The primary
+purpose of this data manager is to provide code that can be read to understand
+how savepoints work.  The secondary purpose is to provide support for
+demonstrating the correct operation of savepoint support within the
+transaction system.  This data manager is very simple.  It provides flat
+storage of named immutable values, like strings and numbers.
+
+.. doctest::
+
+    >>> import transaction
+    >>> from transaction.tests import savepointsample
+    >>> dm = savepointsample.SampleSavepointDataManager()
+    >>> dm['name'] = 'bob'
+
+As with other data managers, we can commit changes:
+
+.. doctest::
+
+    >>> transaction.commit()
+    >>> dm['name']
+    'bob'
+
+and abort changes:
+
+.. doctest::
+
+    >>> dm['name'] = 'sally'
+    >>> dm['name']
+    'sally'
+    >>> transaction.abort()
+    >>> dm['name']
+    'bob'
+
+Now, let's look at an application that manages funds for people.  It allows
+deposits and debits to be entered for multiple people.  It accepts a sequence
+of entries and generates a sequence of status messages.  For each entry, it
+applies the change and then validates the user's account.  If the user's
+account is invalid, we roll back the change for that entry.  The success or
+failure of an entry is indicated in the output status.  First we'll initialize
+some accounts:
+
+.. doctest::
+
+    >>> dm['bob-balance'] = 0.0
+    >>> dm['bob-credit'] = 0.0
+    >>> dm['sally-balance'] = 0.0
+    >>> dm['sally-credit'] = 100.0
+    >>> transaction.commit()
+
+Now, we'll define a validation function to validate an account:
+
+.. doctest::
+
+    >>> def validate_account(name):
+    ...     if dm[name+'-balance'] + dm[name+'-credit'] < 0:
+    ...         raise ValueError('Overdrawn', name)
+
+And a function to apply entries.  If the function fails in some unexpected
+way, it rolls back all of its changes and prints the error:
+
+.. doctest::
+
+    >>> def apply_entries(entries):
+    ...     savepoint = transaction.savepoint()
+    ...     try:
+    ...         for name, amount in entries:
+    ...             entry_savepoint = transaction.savepoint()
+    ...             try:
+    ...                 dm[name+'-balance'] += amount
+    ...                 validate_account(name)
+    ...             except ValueError as error:
+    ...                 entry_savepoint.rollback()
+    ...                 print("%s %s" % ('Error', str(error)))
+    ...             else:
+    ...                 print("%s %s" % ('Updated', name))
+    ...     except Exception as error:
+    ...         savepoint.rollback()
+    ...         print("%s" % ('Unexpected exception'))
+
+Now let's try applying some entries:
+
+.. doctest::
+
+    >>> apply_entries([
+    ...     ('bob',   10.0),
+    ...     ('sally', 10.0),
+    ...     ('bob',   20.0),
+    ...     ('sally', 10.0),
+    ...     ('bob',   -100.0),
+    ...     ('sally', -100.0),
+    ...     ])
+    Updated bob
+    Updated sally
+    Updated bob
+    Updated sally
+    Error ('Overdrawn', 'bob')
+    Updated sally
+
+    >>> dm['bob-balance']
+    30.0
+
+    >>> dm['sally-balance']
+    -80.0
+
+If we provide entries that cause an unexpected error:
+
+.. doctest::
+
+    >>> apply_entries([
+    ...     ('bob',   10.0),
+    ...     ('sally', 10.0),
+    ...     ('bob',   '20.0'),
+    ...     ('sally', 10.0),
+    ...     ])
+    Updated bob
+    Updated sally
+    Unexpected exception
+
+Because the apply_entries used a savepoint for the entire function, it was
+able to rollback the partial changes without rolling back changes made in the
+previous call to ``apply_entries``:
+
+.. doctest::
+
+    >>> dm['bob-balance']
+    30.0
+
+    >>> dm['sally-balance']
+    -80.0
+
+If we now abort the outer transactions, the earlier changes will go
+away:
+
+.. doctest::
+
+    >>> transaction.abort()
+
+    >>> dm['bob-balance']
+    0.0
+
+    >>> dm['sally-balance']
+    0.0
+
+Savepoint invalidation
+----------------------
+
+A savepoint can be used any number of times:
+
+.. doctest::
+
+    >>> dm['bob-balance'] = 100.0
+    >>> dm['bob-balance']
+    100.0
+    >>> savepoint = transaction.savepoint()
+
+    >>> dm['bob-balance'] = 200.0
+    >>> dm['bob-balance']
+    200.0
+    >>> savepoint.rollback()
+    >>> dm['bob-balance']
+    100.0
+
+    >>> savepoint.rollback()  # redundant, but should be harmless
+    >>> dm['bob-balance']
+    100.0
+
+    >>> dm['bob-balance'] = 300.0
+    >>> dm['bob-balance']
+    300.0
+    >>> savepoint.rollback()
+    >>> dm['bob-balance']
+    100.0
+
+However, using a savepoint invalidates any savepoints that come after it:
+
+.. doctest::
+
+    >>> dm['bob-balance'] = 200.0
+    >>> dm['bob-balance']
+    200.0
+    >>> savepoint1 = transaction.savepoint()
+
+    >>> dm['bob-balance'] = 300.0
+    >>> dm['bob-balance']
+    300.0
+    >>> savepoint2 = transaction.savepoint()
+
+    >>> savepoint.rollback()
+    >>> dm['bob-balance']
+    100.0
+
+    >>> savepoint2.rollback() #doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    ...
+    InvalidSavepointRollbackError: invalidated by a later savepoint
+
+    >>> savepoint1.rollback() #doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    ...
+    InvalidSavepointRollbackError: invalidated by a later savepoint
+
+    >>> transaction.abort()
+
+
+Databases without savepoint support
+-----------------------------------
+
+Normally it's an error to use savepoints with databases that don't support
+savepoints:
+
+.. doctest::
+
+    >>> dm_no_sp = savepointsample.SampleDataManager()
+    >>> dm_no_sp['name'] = 'bob'
+    >>> transaction.commit()
+    >>> dm_no_sp['name'] = 'sally'
+    >>> transaction.savepoint() #doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    ...
+    TypeError: ('Savepoints unsupported', {'name': 'bob'})
+
+    >>> transaction.abort()
+
+However, a flag can be passed to the transaction savepoint method to indicate
+that databases without savepoint support should be tolerated until a savepoint
+is rolled back.  This allows transactions to proceed if there are no reasons
+to roll back:
+
+.. doctest::
+
+    >>> dm_no_sp['name'] = 'sally'
+    >>> savepoint = transaction.savepoint(1)
+    >>> dm_no_sp['name'] = 'sue'
+    >>> transaction.commit()
+    >>> dm_no_sp['name']
+    'sue'
+
+    >>> dm_no_sp['name'] = 'sam'
+    >>> savepoint = transaction.savepoint(1)
+    >>> savepoint.rollback() #doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last): 
+    ...
+    TypeError: ('Savepoints unsupported', {'name': 'sam'})
+
+
+Failures
+--------
+
+If a failure occurs when creating or rolling back a savepoint, the transaction
+state will be uncertain and the transaction will become uncommitable.  From
+that point on, most transaction operations, including commit, will fail until
+the transaction is aborted.
+
+In the previous example, we got an error when we tried to rollback the
+savepoint.  If we try to commit the transaction, the commit will fail:
+
+.. doctest::
+
+    >>> transaction.commit() #doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    ...
+    TransactionFailedError: An operation previously failed, with traceback:
+    ...
+    TypeError: ('Savepoints unsupported', {'name': 'sam'})
+    <BLANKLINE>
+
+We have to abort it to make any progress:
+
+.. doctest::
+
+    >>> transaction.abort()
+
+Similarly, in our earlier example, where we tried to take a savepoint with a
+data manager that didn't support savepoints:
+
+.. doctest::
+
+    >>> dm_no_sp['name'] = 'sally'
+    >>> dm['name'] = 'sally'
+    >>> savepoint = transaction.savepoint() # doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    ...
+    TypeError: ('Savepoints unsupported', {'name': 'sue'})
+
+    >>> transaction.commit() # doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    ...
+    TransactionFailedError: An operation previously failed, with traceback:
+    ...
+    TypeError: ('Savepoints unsupported', {'name': 'sue'})
+    <BLANKLINE>
+
+    >>> transaction.abort()
+
+After clearing the transaction with an abort, we can get on with new
+transactions:
+
+.. doctest::
+
+    >>> dm_no_sp['name'] = 'sally'
+    >>> dm['name'] = 'sally'
+    >>> transaction.commit()
+    >>> dm_no_sp['name']
+    'sally'
+    >>> dm['name']
+    'sally'
+

Deleted: transaction/branches/sphinx/transaction/tests/convenience.txt
===================================================================
--- transaction/branches/sphinx/transaction/tests/convenience.txt	2012-12-17 17:28:39 UTC (rev 128695)
+++ transaction/branches/sphinx/transaction/tests/convenience.txt	2012-12-17 20:28:48 UTC (rev 128696)
@@ -1,165 +0,0 @@
-Transaction convenience support
-===============================
-
-(We *really* need to write proper documentation for the transaction
- package, but I don't want to block the conveniences documented here
- for that.)
-
-with support
-------------
-
-We can now use the with statement to define transaction boundaries.
-
-    >>> import transaction.tests.savepointsample
-    >>> dm = transaction.tests.savepointsample.SampleSavepointDataManager()
-    >>> list(dm.keys())
-    []
-
-We can use it with a manager:
-
-    >>> with transaction.manager as t:
-    ...     dm['z'] = 3
-    ...     t.note('test 3')
-
-    >>> dm['z']
-    3
-
-    >>> dm.last_note
-    'test 3'
-
-    >>> with transaction.manager: #doctest ELLIPSIS
-    ...     dm['z'] = 4
-    ...     xxx
-    Traceback (most recent call last):
-    ...
-    NameError: ... name 'xxx' is not defined
-
-    >>> dm['z']
-    3
-
-On Python 2, you can also abbreviate ``with transaction.manager:`` as ``with
-transaction:``.  This does not work on Python 3 (see see
-http://bugs.python.org/issue12022).
-
-Retries
--------
-
-Commits can fail for transient reasons, especially conflicts.
-Applications will often retry transactions some number of times to
-overcome transient failures.  This typically looks something like::
-
-    for i in range(3):
-        try:
-           with transaction.manager:
-               ... some something ...
-        except SomeTransientException:
-           contine
-        else:
-           break
-
-This is rather ugly.
-
-Transaction managers provide a helper for this case. To show this,
-we'll use a contrived example:
-
-
-    >>> ntry = 0
-    >>> with transaction.manager:
-    ...      dm['ntry'] = 0
-
-    >>> import transaction.interfaces
-    >>> class Retry(transaction.interfaces.TransientError):
-    ...     pass
-
-    >>> for attempt in transaction.manager.attempts():
-    ...     with attempt as t:
-    ...         t.note('test')
-    ...         print("%s %s" % (dm['ntry'], ntry))
-    ...         ntry += 1
-    ...         dm['ntry'] = ntry
-    ...         if ntry % 3:
-    ...             raise Retry(ntry)
-    0 0
-    0 1
-    0 2
-
-The raising of a subclass of TransientError is critical here. It's
-what signals that the transaction should be retried.  It is generally
-up to the data manager to signal that a transaction should try again
-by raising a subclass of TransientError (or TransientError itself, of
-course).
-
-You shouldn't make any assumptions about the object returned by the
-iterator.  (It isn't a transaction or transaction manager, as far as
-you know. :)  If you use the ``as`` keyword in the ``with`` statement,
-a transaction object will be assigned to the variable named.
-
-By default, it tries 3 times. We can tell it how many times to try:
-
-    >>> for attempt in transaction.manager.attempts(2):
-    ...     with attempt:
-    ...         ntry += 1
-    ...         if ntry % 3:
-    ...             raise Retry(ntry)
-    Traceback (most recent call last):
-    ...
-    Retry: 5
-
-It it doesn't succeed in that many times, the exception will be
-propagated.
-
-Of course, other errors are propagated directly:
-
-    >>> ntry = 0
-    >>> for attempt in transaction.manager.attempts():
-    ...     with attempt:
-    ...         ntry += 1
-    ...         if ntry == 3:
-    ...             raise ValueError(ntry)
-    Traceback (most recent call last):
-    ...
-    ValueError: 3
-
-We can use the default transaction manager:
-
-    >>> for attempt in transaction.attempts():
-    ...     with attempt as t:
-    ...         t.note('test')
-    ...         print("%s %s" % (dm['ntry'], ntry))
-    ...         ntry += 1
-    ...         dm['ntry'] = ntry
-    ...         if ntry % 3:
-    ...             raise Retry(ntry)
-    3 3
-    3 4
-    3 5
-
-Sometimes, a data manager doesn't raise exceptions directly, but
-wraps other other systems that raise exceptions outside of it's
-control.  Data  managers can provide a should_retry method that takes
-an exception instance and returns True if the transaction should be
-attempted again.
-
-    >>> class DM(transaction.tests.savepointsample.SampleSavepointDataManager):
-    ...     def should_retry(self, e):
-    ...         if 'should retry' in str(e):
-    ...             return True
-
-    >>> ntry = 0
-    >>> dm2 = DM()
-    >>> with transaction.manager:
-    ...     dm2['ntry'] = 0
-    >>> for attempt in transaction.manager.attempts():
-    ...     with attempt:
-    ...         print("%s %s" % (dm['ntry'], ntry))
-    ...         ntry += 1
-    ...         dm['ntry'] = ntry
-    ...         dm2['ntry'] = ntry
-    ...         if ntry % 3:
-    ...             raise ValueError('we really should retry this')
-    6 0
-    6 1
-    6 2
-
-    >>> dm2['ntry']
-    3

Deleted: transaction/branches/sphinx/transaction/tests/doom.txt
===================================================================
--- transaction/branches/sphinx/transaction/tests/doom.txt	2012-12-17 17:28:39 UTC (rev 128695)
+++ transaction/branches/sphinx/transaction/tests/doom.txt	2012-12-17 20:28:48 UTC (rev 128696)
@@ -1,136 +0,0 @@
-Dooming Transactions
-====================
-
-A doomed transaction behaves exactly the same way as an active transaction but
-raises an error on any attempt to commit it, thus forcing an abort.
-
-Doom is useful in places where abort is unsafe and an exception cannot be
-raised.  This occurs when the programmer wants the code following the doom to
-run but not commit. It is unsafe to abort in these circumstances as a following
-get() may implicitly open a new transaction.
-
-Any attempt to commit a doomed transaction will raise a DoomedTransaction
-exception.
-
-An example of such a use case can be found in
-zope/app/form/browser/editview.py.  Here a form validation failure must doom
-the transaction as committing the transaction may have side-effects. However,
-the form code must continue to calculate a form containing the error messages
-to return.
-
-For Zope in general, code running within a request should always doom
-transactions rather than aborting them. It is the responsibilty of the
-publication to either abort() or commit() the transaction. Application code can
-use savepoints and doom() safely.
-
-To see how it works we first need to create a stub data manager:
-
-    >>> from transaction.interfaces import IDataManager
-    >>> from zope.interface import implementer
-    >>> @implementer(IDataManager)
-    ... class DataManager:
-    ...     def __init__(self):
-    ...         self.attr_counter = {}
-    ...     def __getattr__(self, name):
-    ...         def f(transaction):
-    ...             self.attr_counter[name] = self.attr_counter.get(name, 0) + 1
-    ...         return f
-    ...     def total(self):
-    ...         count = 0
-    ...         for access_count in self.attr_counter.values():
-    ...             count += access_count
-    ...         return count
-    ...     def sortKey(self):
-    ...         return 1
-
-Start a new transaction:
-
-    >>> import transaction
-    >>> txn = transaction.begin()
-    >>> dm = DataManager()
-    >>> txn.join(dm)
-
-We can ask a transaction if it is doomed to avoid expensive operations. An
-example of a use case is an object-relational mapper where a pre-commit hook
-sends all outstanding SQL to a relational database for objects changed during
-the transaction. This expensive operation is not necessary if the transaction
-has been doomed. A non-doomed transaction should return False:
-
-    >>> txn.isDoomed()
-    False
-
-We can doom a transaction by calling .doom() on it:
-
-    >>> txn.doom()
-    >>> txn.isDoomed()
-    True
-
-We can doom it again if we like:
-
-    >>> txn.doom()
-
-The data manager is unchanged at this point:
-
-    >>> dm.total()
-    0
-
-Attempting to commit a doomed transaction any number of times raises a
-DoomedTransaction:
-
-    >>> txn.commit() # doctest: +IGNORE_EXCEPTION_DETAIL
-    Traceback (most recent call last):
-    DoomedTransaction: transaction doomed, cannot commit
-    >>> txn.commit() # doctest: +IGNORE_EXCEPTION_DETAIL
-    Traceback (most recent call last):
-    DoomedTransaction: transaction doomed, cannot commit
-
-But still leaves the data manager unchanged:
-
-    >>> dm.total()
-    0
-
-But the doomed transaction can be aborted:
-
-    >>> txn.abort()
-
-Which aborts the data manager:
-
-    >>> dm.total()
-    1
-    >>> dm.attr_counter['abort']
-    1
-
-Dooming the current transaction can also be done directly from the transaction
-module. We can also begin a new transaction directly after dooming the old one:
-
-    >>> txn = transaction.begin()
-    >>> transaction.isDoomed()
-    False
-    >>> transaction.doom()
-    >>> transaction.isDoomed()
-    True
-    >>> txn = transaction.begin()
-
-After committing a transaction we get an assertion error if we try to doom the
-transaction. This could be made more specific, but trying to doom a transaction
-after it's been committed is probably a programming error:
-
-    >>> txn = transaction.begin()
-    >>> txn.commit()
-    >>> txn.doom()
-    Traceback (most recent call last):
-        ...
-    AssertionError
-
-A doomed transaction should act the same as an active transaction, so we should
-be able to join it:
-
-    >>> txn = transaction.begin()
-    >>> txn.doom()
-    >>> dm2 = DataManager()
-    >>> txn.join(dm2)
-
-Clean up:
-
-    >>> txn = transaction.begin()
-    >>> txn.abort()

Deleted: transaction/branches/sphinx/transaction/tests/savepoint.txt
===================================================================
--- transaction/branches/sphinx/transaction/tests/savepoint.txt	2012-12-17 17:28:39 UTC (rev 128695)
+++ transaction/branches/sphinx/transaction/tests/savepoint.txt	2012-12-17 20:28:48 UTC (rev 128696)
@@ -1,290 +0,0 @@
-Savepoints
-==========
-
-Savepoints provide a way to save to disk intermediate work done during
-a transaction allowing:
-
-- partial transaction (subtransaction) rollback (abort)
-
-- state of saved objects to be freed, freeing on-line memory for other
-  uses
-
-Savepoints make it possible to write atomic subroutines that don't
-make top-level transaction commitments.
-
-
-Applications
-------------
-
-To demonstrate how savepoints work with transactions, we've provided a sample
-data manager implementation that provides savepoint support.  The primary
-purpose of this data manager is to provide code that can be read to understand
-how savepoints work.  The secondary purpose is to provide support for
-demonstrating the correct operation of savepoint support within the
-transaction system.  This data manager is very simple.  It provides flat
-storage of named immutable values, like strings and numbers.
-
-    >>> import transaction
-    >>> from transaction.tests import savepointsample
-    >>> dm = savepointsample.SampleSavepointDataManager()
-    >>> dm['name'] = 'bob'
-
-As with other data managers, we can commit changes:
-
-    >>> transaction.commit()
-    >>> dm['name']
-    'bob'
-
-and abort changes:
-
-    >>> dm['name'] = 'sally'
-    >>> dm['name']
-    'sally'
-    >>> transaction.abort()
-    >>> dm['name']
-    'bob'
-
-Now, let's look at an application that manages funds for people.  It allows
-deposits and debits to be entered for multiple people.  It accepts a sequence
-of entries and generates a sequence of status messages.  For each entry, it
-applies the change and then validates the user's account.  If the user's
-account is invalid, we roll back the change for that entry.  The success or
-failure of an entry is indicated in the output status.  First we'll initialize
-some accounts:
-
-    >>> dm['bob-balance'] = 0.0
-    >>> dm['bob-credit'] = 0.0
-    >>> dm['sally-balance'] = 0.0
-    >>> dm['sally-credit'] = 100.0
-    >>> transaction.commit()
-
-Now, we'll define a validation function to validate an account:
-
-    >>> def validate_account(name):
-    ...     if dm[name+'-balance'] + dm[name+'-credit'] < 0:
-    ...         raise ValueError('Overdrawn', name)
-
-And a function to apply entries.  If the function fails in some unexpected
-way, it rolls back all of its changes and prints the error:
-
-    >>> def apply_entries(entries):
-    ...     savepoint = transaction.savepoint()
-    ...     try:
-    ...         for name, amount in entries:
-    ...             entry_savepoint = transaction.savepoint()
-    ...             try:
-    ...                 dm[name+'-balance'] += amount
-    ...                 validate_account(name)
-    ...             except ValueError as error:
-    ...                 entry_savepoint.rollback()
-    ...                 print("%s %s" % ('Error', str(error)))
-    ...             else:
-    ...                 print("%s %s" % ('Updated', name))
-    ...     except Exception as error:
-    ...         savepoint.rollback()
-    ...         print("%s" % ('Unexpected exception'))
-
-Now let's try applying some entries:
-
-    >>> apply_entries([
-    ...     ('bob',   10.0),
-    ...     ('sally', 10.0),
-    ...     ('bob',   20.0),
-    ...     ('sally', 10.0),
-    ...     ('bob',   -100.0),
-    ...     ('sally', -100.0),
-    ...     ])
-    Updated bob
-    Updated sally
-    Updated bob
-    Updated sally
-    Error ('Overdrawn', 'bob')
-    Updated sally
-
-    >>> dm['bob-balance']
-    30.0
-
-    >>> dm['sally-balance']
-    -80.0
-
-If we provide entries that cause an unexpected error:
-
-    >>> apply_entries([
-    ...     ('bob',   10.0),
-    ...     ('sally', 10.0),
-    ...     ('bob',   '20.0'),
-    ...     ('sally', 10.0),
-    ...     ])
-    Updated bob
-    Updated sally
-    Unexpected exception
-
-Because the apply_entries used a savepoint for the entire function, it was
-able to rollback the partial changes without rolling back changes made in the
-previous call to ``apply_entries``:
-
-    >>> dm['bob-balance']
-    30.0
-
-    >>> dm['sally-balance']
-    -80.0
-
-If we now abort the outer transactions, the earlier changes will go
-away:
-
-    >>> transaction.abort()
-
-    >>> dm['bob-balance']
-    0.0
-
-    >>> dm['sally-balance']
-    0.0
-
-Savepoint invalidation
-----------------------
-
-A savepoint can be used any number of times:
-
-    >>> dm['bob-balance'] = 100.0
-    >>> dm['bob-balance']
-    100.0
-    >>> savepoint = transaction.savepoint()
-
-    >>> dm['bob-balance'] = 200.0
-    >>> dm['bob-balance']
-    200.0
-    >>> savepoint.rollback()
-    >>> dm['bob-balance']
-    100.0
-
-    >>> savepoint.rollback()  # redundant, but should be harmless
-    >>> dm['bob-balance']
-    100.0
-
-    >>> dm['bob-balance'] = 300.0
-    >>> dm['bob-balance']
-    300.0
-    >>> savepoint.rollback()
-    >>> dm['bob-balance']
-    100.0
-
-However, using a savepoint invalidates any savepoints that come after it:
-
-    >>> dm['bob-balance'] = 200.0
-    >>> dm['bob-balance']
-    200.0
-    >>> savepoint1 = transaction.savepoint()
-
-    >>> dm['bob-balance'] = 300.0
-    >>> dm['bob-balance']
-    300.0
-    >>> savepoint2 = transaction.savepoint()
-
-    >>> savepoint.rollback()
-    >>> dm['bob-balance']
-    100.0
-
-    >>> savepoint2.rollback() #doctest: +IGNORE_EXCEPTION_DETAIL
-    Traceback (most recent call last):
-    ...
-    InvalidSavepointRollbackError: invalidated by a later savepoint
-
-    >>> savepoint1.rollback() #doctest: +IGNORE_EXCEPTION_DETAIL
-    Traceback (most recent call last):
-    ...
-    InvalidSavepointRollbackError: invalidated by a later savepoint
-
-    >>> transaction.abort()
-
-
-Databases without savepoint support
------------------------------------
-
-Normally it's an error to use savepoints with databases that don't support
-savepoints:
-
-    >>> dm_no_sp = savepointsample.SampleDataManager()
-    >>> dm_no_sp['name'] = 'bob'
-    >>> transaction.commit()
-    >>> dm_no_sp['name'] = 'sally'
-    >>> transaction.savepoint() #doctest: +IGNORE_EXCEPTION_DETAIL
-    Traceback (most recent call last):
-    ...
-    TypeError: ('Savepoints unsupported', {'name': 'bob'})
-
-    >>> transaction.abort()
-
-However, a flag can be passed to the transaction savepoint method to indicate
-that databases without savepoint support should be tolerated until a savepoint
-is rolled back.  This allows transactions to proceed if there are no reasons
-to roll back:
-
-    >>> dm_no_sp['name'] = 'sally'
-    >>> savepoint = transaction.savepoint(1)
-    >>> dm_no_sp['name'] = 'sue'
-    >>> transaction.commit()
-    >>> dm_no_sp['name']
-    'sue'
-
-    >>> dm_no_sp['name'] = 'sam'
-    >>> savepoint = transaction.savepoint(1)
-    >>> savepoint.rollback() #doctest: +IGNORE_EXCEPTION_DETAIL
-    Traceback (most recent call last): 
-    ...
-    TypeError: ('Savepoints unsupported', {'name': 'sam'})
-
-
-Failures
---------
-
-If a failure occurs when creating or rolling back a savepoint, the transaction
-state will be uncertain and the transaction will become uncommitable.  From
-that point on, most transaction operations, including commit, will fail until
-the transaction is aborted.
-
-In the previous example, we got an error when we tried to rollback the
-savepoint.  If we try to commit the transaction, the commit will fail:
-
-    >>> transaction.commit() #doctest: +IGNORE_EXCEPTION_DETAIL
-    Traceback (most recent call last):
-    ...
-    TransactionFailedError: An operation previously failed, with traceback:
-    ...
-    TypeError: ('Savepoints unsupported', {'name': 'sam'})
-    <BLANKLINE>
-
-We have to abort it to make any progress:
-
-    >>> transaction.abort()
-
-Similarly, in our earlier example, where we tried to take a savepoint with a
-data manager that didn't support savepoints:
-
-    >>> dm_no_sp['name'] = 'sally'
-    >>> dm['name'] = 'sally'
-    >>> savepoint = transaction.savepoint() # doctest: +IGNORE_EXCEPTION_DETAIL
-    Traceback (most recent call last):
-    ...
-    TypeError: ('Savepoints unsupported', {'name': 'sue'})
-
-    >>> transaction.commit() # doctest: +IGNORE_EXCEPTION_DETAIL
-    Traceback (most recent call last):
-    ...
-    TransactionFailedError: An operation previously failed, with traceback:
-    ...
-    TypeError: ('Savepoints unsupported', {'name': 'sue'})
-    <BLANKLINE>
-
-    >>> transaction.abort()
-
-After clearing the transaction with an abort, we can get on with new
-transactions:
-
-    >>> dm_no_sp['name'] = 'sally'
-    >>> dm['name'] = 'sally'
-    >>> transaction.commit()
-    >>> dm_no_sp['name']
-    'sally'
-    >>> dm['name']
-    'sally'
-

Modified: transaction/branches/sphinx/transaction/tests/savepointsample.py
===================================================================
--- transaction/branches/sphinx/transaction/tests/savepointsample.py	2012-12-17 17:28:39 UTC (rev 128695)
+++ transaction/branches/sphinx/transaction/tests/savepointsample.py	2012-12-17 20:28:48 UTC (rev 128696)
@@ -16,7 +16,7 @@
 Sample data manager implementation that illustrates how to implement
 savepoints.
 
-See savepoint.txt in the transaction package.
+Used by savepoint.rst in the Sphinx docs.
 """
 
 from zope.interface import implementer

Modified: transaction/branches/sphinx/transaction/tests/test_savepoint.py
===================================================================
--- transaction/branches/sphinx/transaction/tests/test_savepoint.py	2012-12-17 17:28:39 UTC (rev 128695)
+++ transaction/branches/sphinx/transaction/tests/test_savepoint.py	2012-12-17 20:28:48 UTC (rev 128696)
@@ -80,7 +80,6 @@
 
 def test_suite():
     return unittest.TestSuite((
-        doctest.DocFileSuite('savepoint.txt'),
         doctest.DocTestSuite(),
         ))
 

Modified: transaction/branches/sphinx/transaction/tests/test_transaction.py
===================================================================
--- transaction/branches/sphinx/transaction/tests/test_transaction.py	2012-12-17 17:28:39 UTC (rev 128695)
+++ transaction/branches/sphinx/transaction/tests/test_transaction.py	2012-12-17 20:28:48 UTC (rev 128696)
@@ -36,10 +36,8 @@
     add in tests for objects which are modified multiple times,
     for example an object that gets modified in multiple sub txns.
 """
-from doctest import DocTestSuite, DocFileSuite, IGNORE_EXCEPTION_DETAIL
-
+from doctest import DocTestSuite
 import struct
-import sys
 import unittest
 import transaction
 
@@ -763,14 +761,10 @@
 
 def test_suite():
     suite = unittest.TestSuite((
-        DocFileSuite('doom.txt'),
         DocTestSuite(),
         unittest.makeSuite(TransactionTests),
         unittest.makeSuite(Test_oid_repr),
         ))
-    if sys.version_info >= (2, 6):
-        suite.addTest(DocFileSuite('convenience.txt',
-                      optionflags=IGNORE_EXCEPTION_DETAIL))
 
     return suite
 



More information about the checkins mailing list