Skip to content

Commit

Permalink
Add ability to customize the transaction manager that TransactionLoop…
Browse files Browse the repository at this point in the history
… uses.
  • Loading branch information
jamadden committed Jun 11, 2024
1 parent b2bd17f commit ec66036
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

- Add support for Python 3.12 and 3.13.
- Drop support for Python 2 and anything less than Python 3.8.
- Add the ability to specify a transaction manager for loops to use
other than the default.


4.3.0 (2023-05-05)
Expand Down
39 changes: 38 additions & 1 deletion src/nti/transactions/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,19 @@ def __init__(
retries=None, # type: int
sleep=None, # type: float
long_commit_duration=None, # type: float
transaction_manager=None,
):
"""
:keyword float sleep: Sets the :attr:`sleep`.
:keyword int retries: If given, the number of times a transaction will be
retried. Note that this is one less than the total number of
:attr:`attempts`
:keyword transaction_manager: If not None, this is what will be returned
from :meth:`get_transaction_manager_for_call` instead of the default
transaction manager.
.. versionchanged:: NEXT
Add the *transaction_manager* argument.
"""
self.handler = handler
if retries is not None:
Expand All @@ -259,6 +266,7 @@ def __init__(
if sleep is not None:
self.sleep = sleep
self.random = random.SystemRandom()
self._txm = transaction_manager

def __repr__(self):
return '<%s.%s at 0x%x attempts=%s long_commit_duration=%s sleep=%s handler=%r>' % (
Expand Down Expand Up @@ -473,13 +481,42 @@ def flush():

_null_stat_collector = _NullStatCollector()

# pylint:disable-next=unused-argument
def get_transaction_manager_for_call(self, *args, **kwargs):
"""
Return the transaction manager to use for a particular call.
By default, this is the global/default thread-local transaction manager.
The arguments are the arguments passed to ``__call__``.
Subclasses may override. The expected use case is for transactions that are
isolated and scoped to a particular well-defined location. Most
likely, you'll keep a reference to the transaction manager outside
the loop object (or, if the arguments don't matter, you can just call this method
without supplying them)
for later use. For example, when getting a connection from ZODB::
txm = TransactionManager()
loop = TransactionLoop(..., transaction_manager=txm)
db = ... # get ZODB DB
with db.open(loop.get_transaction_manager_for_call()) as conn:
...
.. versionadded:: NEXT
"""
# We use the thread-local global/default transaction manager.
# Accessing it directly is a bit faster than going through the wrapping
# layer. Applications should not be changing it.
return self._txm if self._txm is not None else transaction.manager.manager

def __call__(self, *args, **kwargs):
note = self.describe_transaction(*args, **kwargs)

# We use the thread-local global/default transaction manager.
# Accessing it directly is a bit faster than going through the wrapping
# layer. Applications should not be changing it.
txm = transaction.manager.manager
txm = self.get_transaction_manager_for_call(*args, **kwargs)
# We always operate in explicit mode. This makes finding
# errors such as committing or aborting multiple times much easier,
# and prevents us from having to worry about a mis-match between
Expand Down

0 comments on commit ec66036

Please sign in to comment.