Skip to content

Commit

Permalink
elementary implementation of TransactionManager.run (i.e. without `…
Browse files Browse the repository at this point in the history
…attempts`);

document that a transaction manager supports the "context manager" protocol;
improve formatting (thanks to Marius Gedminas)
  • Loading branch information
d-maurer committed May 6, 2019
1 parent d6620ff commit 2404dcd
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 9 deletions.
29 changes: 23 additions & 6 deletions transaction/_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"""
import sys
import threading
import itertools

from zope.interface import implementer

Expand Down Expand Up @@ -176,6 +177,9 @@ def run(self, func=None, tries=3):
tries = func
return lambda func: self.run(func, tries)

if tries <= 0:
raise ValueError("tries must be > 0")

# These are ordinarily native strings, but that's
# not required. A callable class could override them
# to anything, and a Python 2.7 file could have
Expand All @@ -192,12 +196,25 @@ def run(self, func=None, tries=3):
doc = name + u'\n\n' + doc
else:
doc = name

for attempt in self.attempts(tries):
with attempt:
if doc:
self.get().note(doc)
return func()

for try_no in itertools.count(1):
txn = self.begin()
if doc:
txn.note(doc)
try:
result = func()
self.commit()
return result
except BaseException as exc:
# Note: `abort` must not be called before `_retryable`
retry = (isinstance(exc, Exception)
and try_no < tries
and self._retryable(exc.__class__, exc))
self.abort()
if retry:
continue
else:
raise


@implementer(ITransactionManager)
Expand Down
11 changes: 8 additions & 3 deletions transaction/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ class ITransactionManager(Interface):
"""An object that manages a sequence of transactions.
Applications use transaction managers to establish transaction boundaries.
A transaction manager supports the "context manager" protocol:
Its `__enter__` begins a new transaction; its `__exit__` commits
the current transaction if no exception has occured; otherwise,
it aborts it.
"""

explicit = Attribute(
Expand Down Expand Up @@ -135,15 +140,15 @@ def registeredSynchs():
def attempts(number=3):
"""Generate up to *number* (transactional) context managers.
This method is typically used as follows:
This method is typically used as follows::
for attempt in transaction_manager.attempts():
with attempt:
*with block*
The `with attempt` starts a new transaction for the execution
of the *with block*. If the execution succeeds,
the transaction is commited and the `for` loop
the (then current) transaction is commited and the `for` loop
terminates. If the execution raised an exception,
then the transaction is aborted.
If the exception was some kind
Expand All @@ -160,7 +165,7 @@ def run(func=None, tries=3):
The call is tried up to *tries* times.
The call is performed in a new transaction. After
the call, the transaction is committed (no exception) or
the call, the (then current) transaction is committed (no exception) or
aborted (exception).
`run` supports the alternative signature `run(tries=3)`.
Expand Down
7 changes: 7 additions & 0 deletions transaction/tests/test__manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,13 @@ def run(self):
stopped.set()
runner.join(1)

# the preceeding test (maybe others as well) registers `self` as
# synchronizer; satisfy the `ISynchronizer` requirements
def newTransaction(self, transaction):
pass

beforeCompletion = afterCompletion = newTransaction


class TestThreadTransactionManager(unittest.TestCase):

Expand Down

0 comments on commit 2404dcd

Please sign in to comment.