Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an ExecuteLastObjectDataManager that attempts to execute after ot… #11

Merged
merged 4 commits into from Apr 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGES.rst
Expand Up @@ -2,10 +2,11 @@
Changes
========

1.0.1 (unreleased)
1.1 (unreleased)
------------------

- Nothing changed yet.
- Add a new ObjectDataManager that will attempt to execute after
other ObjectDataManagers.


1.0.0 (2016-07-28)
Expand Down
25 changes: 23 additions & 2 deletions src/nti/transactions/tests/test_transaction.py
Expand Up @@ -12,18 +12,23 @@
#pylint: disable=W0212,R0904

import unittest

from hamcrest import assert_that
from hamcrest import is_
from hamcrest import calling
from hamcrest import raises
from hamcrest import contains

import fudge

from ..transactions import _do_commit
from ..transactions import TransactionLoop
from ..interfaces import CommitFailedError
from ..interfaces import AbortFailedError

from ..transactions import do
from ..transactions import do_near_end
from ..transactions import _do_commit
from ..transactions import TransactionLoop

import transaction
from transaction.interfaces import TransientError

Expand Down Expand Up @@ -218,3 +223,19 @@ def handler():

loop = Loop(handler)
assert_that(calling(loop), raises(AbortFailedError))

class TestDataManagers(unittest.TestCase):

def test_data_manager_sorting(self):
results = []
def test_call(x):
results.append(x)

# The managers will execute in order added (since identical),
# except for the one that requests to go last.
manager1 = do(call=test_call, args=(0,))
manager2 = do(call=test_call, args=(1,))
manager_post = do_near_end(call=test_call, args=(10,))
manager3 = do(call=test_call, args=(2,))
transaction.commit()
assert_that(results, contains(0, 1, 2, 10))
32 changes: 31 additions & 1 deletion src/nti/transactions/transactions.py
Expand Up @@ -196,6 +196,24 @@ def rollback(self):
# anyway.
pass

class OrderedNearEndObjectDataManager(ObjectDataManager):
"""
A special extension of :class:`ObjectDataManager` that attempts to execute
after all other data managers have executed. This is useful when an
operation relies on the execution of other data managers.

.. versionadded:: 1.1
"""

def sortKey(self):
"""
Sort prepended with z's in an attempt to execute after other data
managers.
"""
parent_key = super(OrderedNearEndObjectDataManager, self).sortKey()
sort_str = str(self.target) if self.target is not None else str(self.callable)
return 'zzz%s:%s' % (sort_str, parent_key)

class _QueuePutDataManager(ObjectDataManager):
"""
A data manager that checks if the queue is full before putting.
Expand Down Expand Up @@ -233,10 +251,22 @@ def do(*args, **kwargs):
Establishes a IDataManager in the current transaction.
See :class:`ObjectDataManager` for the possible arguments.
"""
result = ObjectDataManager(*args, **kwargs)
klass = kwargs.pop('datamanager_class', ObjectDataManager)
result = klass(*args, **kwargs)
transaction.get().join(result)
return result

def do_near_end(*args, **kwargs):
"""
Establishes a IDataManager in the current transaction that will attempt to
execute *after* all other DataManagers have had their say.
See :class:`ObjectDataManager` for the possible arguments.

.. versionadded:: 1.1
"""
kwargs['datamanager_class'] = OrderedNearEndObjectDataManager
return do(*args, **kwargs)

def _do_commit(tx, description, long_commit_duration):
exc_info = sys.exc_info()
try:
Expand Down