Merge "Use ordered[set/dict] to retain ordering"

This commit is contained in:
Jenkins
2015-03-12 20:17:15 +00:00
committed by Gerrit Code Review
6 changed files with 206 additions and 143 deletions

View File

@@ -74,8 +74,8 @@ ignored during inference (as these names have special meaning/usage in python).
... def execute(self, *args, **kwargs):
... pass
...
>>> UniTask().requires
frozenset([])
>>> sorted(UniTask().requires)
[]
.. make vim sphinx highlighter* happy**
@@ -214,8 +214,8 @@ name of the value.
... def execute(self):
... return 42
...
>>> TheAnswerReturningTask(provides='the_answer').provides
frozenset(['the_answer'])
>>> sorted(TheAnswerReturningTask(provides='the_answer').provides)
['the_answer']
Returning a tuple
+++++++++++++++++

View File

@@ -16,14 +16,28 @@
# under the License.
import abc
import collections
import itertools
try:
from collections import OrderedDict # noqa
except ImportError:
from ordereddict import OrderedDict # noqa
from oslo_utils import reflection
import six
from six.moves import zip as compat_zip
from taskflow import exceptions
from taskflow.types import sets
from taskflow.utils import misc
# Helper types tuples...
_sequence_types = (list, tuple, collections.Sequence)
_set_types = (set, collections.Set)
def _save_as_to_mapping(save_as):
"""Convert save_as to mapping name => index.
@@ -33,25 +47,26 @@ def _save_as_to_mapping(save_as):
# outside of code so that it's more easily understandable, since what an
# atom returns is pretty crucial for other later operations.
if save_as is None:
return {}
return OrderedDict()
if isinstance(save_as, six.string_types):
# NOTE(harlowja): this means that your atom will only return one item
# instead of a dictionary-like object or a indexable object (like a
# list or tuple).
return {save_as: None}
elif isinstance(save_as, (tuple, list)):
return OrderedDict([(save_as, None)])
elif isinstance(save_as, _sequence_types):
# NOTE(harlowja): this means that your atom will return a indexable
# object, like a list or tuple and the results can be mapped by index
# to that tuple/list that is returned for others to use.
return dict((key, num) for num, key in enumerate(save_as))
elif isinstance(save_as, set):
return OrderedDict((key, num) for num, key in enumerate(save_as))
elif isinstance(save_as, _set_types):
# NOTE(harlowja): in the case where a set is given we will not be
# able to determine the numeric ordering in a reliable way (since it is
# a unordered set) so the only way for us to easily map the result of
# the atom will be via the key itself.
return dict((key, key) for key in save_as)
raise TypeError('Atom provides parameter '
'should be str, set or tuple/list, not %r' % save_as)
# able to determine the numeric ordering in a reliable way (since it
# may be an unordered set) so the only way for us to easily map the
# result of the atom will be via the key itself.
return OrderedDict((key, key) for key in save_as)
else:
raise TypeError('Atom provides parameter '
'should be str, set or tuple/list, not %r' % save_as)
def _build_rebind_dict(args, rebind_args):
@@ -62,9 +77,9 @@ def _build_rebind_dict(args, rebind_args):
new name onto the required name).
"""
if rebind_args is None:
return {}
return OrderedDict()
elif isinstance(rebind_args, (list, tuple)):
rebind = dict(zip(args, rebind_args))
rebind = OrderedDict(compat_zip(args, rebind_args))
if len(args) < len(rebind_args):
rebind.update((a, a) for a in rebind_args[len(args):])
return rebind
@@ -85,11 +100,11 @@ def _build_arg_mapping(atom_name, reqs, rebind_args, function, do_infer,
extra arguments (where applicable).
"""
# build a list of required arguments based on function signature
# Build a list of required arguments based on function signature.
req_args = reflection.get_callable_args(function, required_only=True)
all_args = reflection.get_callable_args(function, required_only=False)
# remove arguments that are part of ignore_list
# Remove arguments that are part of ignore list.
if ignore_list:
for arg in ignore_list:
if arg in req_args:
@@ -97,39 +112,45 @@ def _build_arg_mapping(atom_name, reqs, rebind_args, function, do_infer,
else:
ignore_list = []
required = {}
# add reqs to required mappings
# Build the required names.
required = OrderedDict()
# Add required arguments to required mappings if inference is enabled.
if do_infer:
required.update((a, a) for a in req_args)
# Add additional manually provided requirements to required mappings.
if reqs:
if isinstance(reqs, six.string_types):
required.update({reqs: reqs})
else:
required.update((a, a) for a in reqs)
# add req_args to required mappings if do_infer is set
if do_infer:
required.update((a, a) for a in req_args)
# update required mappings based on rebind_args
# Update required mappings values based on rebinding of arguments names.
required.update(_build_rebind_dict(req_args, rebind_args))
# Determine if there are optional arguments that we may or may not take.
if do_infer:
opt_args = set(all_args) - set(required) - set(ignore_list)
optional = dict((a, a) for a in opt_args)
opt_args = sets.OrderedSet(all_args)
opt_args = opt_args - set(itertools.chain(six.iterkeys(required),
iter(ignore_list)))
optional = OrderedDict((a, a) for a in opt_args)
else:
optional = {}
optional = OrderedDict()
# Check if we are given some extra arguments that we aren't able to accept.
if not reflection.accepts_kwargs(function):
extra_args = set(required) - set(all_args)
extra_args = sets.OrderedSet(six.iterkeys(required))
extra_args -= all_args
if extra_args:
extra_args_str = ', '.join(sorted(extra_args))
raise ValueError('Extra arguments given to atom %s: %s'
% (atom_name, extra_args_str))
% (atom_name, list(extra_args)))
# NOTE(imelnikov): don't use set to preserve order in error message
missing_args = [arg for arg in req_args if arg not in required]
if missing_args:
raise ValueError('Missing arguments for atom %s: %s'
% (atom_name, ' ,'.join(missing_args)))
% (atom_name, missing_args))
return required, optional
@@ -161,52 +182,53 @@ class Atom(object):
with this atom. It can be useful in resuming older versions
of atoms. Standard major, minor versioning concepts
should apply.
:ivar save_as: An *immutable* output ``resource`` name dictionary this atom
produces that other atoms may depend on this atom providing.
The format is output index (or key when a dictionary
is returned from the execute method) to stored argument
name.
:ivar rebind: An *immutable* input ``resource`` mapping dictionary that
can be used to alter the inputs given to this atom. It is
typically used for mapping a prior atoms output into
:ivar save_as: An *immutable* output ``resource`` name
:py:class:`.OrderedDict` this atom produces that other
atoms may depend on this atom providing. The format is
output index (or key when a dictionary is returned from
the execute method) to stored argument name.
:ivar rebind: An *immutable* input ``resource`` :py:class:`.OrderedDict`
that can be used to alter the inputs given to this atom. It
is typically used for mapping a prior atoms output into
the names that this atom expects (in a way this is like
remapping a namespace of another atom into the namespace
of this atom).
:ivar inject: See parameter ``inject``.
:ivar name: See parameter ``name``.
:ivar requires: An *immutable* set of inputs this atom requires to
function.
:ivar optional: An *immutable* set of inputs that are optional for this
atom to function.
:ivar provides: An *immutable* set of outputs this atom produces.
:ivar requires: A :py:class:`~taskflow.types.sets.OrderedSet` of inputs
this atom requires to function.
:ivar optional: A :py:class:`~taskflow.types.sets.OrderedSet` of inputs
that are optional for this atom to function.
:ivar provides: A :py:class:`~taskflow.types.sets.OrderedSet` of outputs
this atom produces.
"""
def __init__(self, name=None, provides=None, inject=None):
self.name = name
self.save_as = _save_as_to_mapping(provides)
self.version = (1, 0)
self.inject = inject
self.requires = frozenset()
self.optional = frozenset()
self.provides = frozenset(self.save_as)
self.rebind = {}
self.save_as = _save_as_to_mapping(provides)
self.requires = sets.OrderedSet()
self.optional = sets.OrderedSet()
self.provides = sets.OrderedSet(self.save_as)
self.rebind = OrderedDict()
def _build_arg_mapping(self, executor, requires=None, rebind=None,
auto_extract=True, ignore_list=None):
req_arg, opt_arg = _build_arg_mapping(self.name, requires, rebind,
executor, auto_extract,
ignore_list)
self.rebind.clear()
if opt_arg:
self.rebind.update(opt_arg)
if req_arg:
self.rebind.update(req_arg)
self.requires = frozenset(req_arg.values())
self.optional = frozenset(opt_arg.values())
required, optional = _build_arg_mapping(self.name, requires, rebind,
executor, auto_extract,
ignore_list=ignore_list)
rebind = OrderedDict()
for (arg_name, bound_name) in itertools.chain(six.iteritems(required),
six.iteritems(optional)):
rebind.setdefault(arg_name, bound_name)
self.rebind = rebind
self.requires = sets.OrderedSet(six.itervalues(required))
self.optional = sets.OrderedSet(six.itervalues(optional))
if self.inject:
inject_set = set(six.iterkeys(self.inject))
self.requires -= inject_set
self.optional -= inject_set
inject_keys = frozenset(six.iterkeys(self.inject))
self.requires -= inject_keys
self.optional -= inject_keys
out_of_order = self.provides.intersection(self.requires)
if out_of_order:
raise exceptions.DependencyFailure(

View File

@@ -157,13 +157,22 @@ class Linker(object):
" decomposed into an empty graph" % (v, u, u))
for u in u_g.nodes_iter():
for v in v_g.nodes_iter():
depends_on = u.provides & v.requires
# This is using the intersection() method vs the &
# operator since the latter doesn't work with frozen
# sets (when used in combination with ordered sets).
#
# If this is not done the following happens...
#
# TypeError: unsupported operand type(s)
# for &: 'frozenset' and 'OrderedSet'
depends_on = u.provides.intersection(v.requires)
if depends_on:
edge_attrs = {
_EDGE_REASONS: frozenset(depends_on),
}
_add_update_edges(graph,
[u], [v],
attr_dict={
_EDGE_REASONS: depends_on,
})
attr_dict=edge_attrs)
else:
# Connect nodes with no predecessors in v to nodes with no
# successors in the *first* non-empty predecessor of v (thus

View File

@@ -27,11 +27,20 @@ def _unsatisfied_requires(node, graph, *additional_provided):
if not requires:
return requires
for provided in additional_provided:
requires = requires - provided
# This is using the difference() method vs the -
# operator since the latter doesn't work with frozen
# or regular sets (when used in combination with ordered
# sets).
#
# If this is not done the following happens...
#
# TypeError: unsupported operand type(s)
# for -: 'set' and 'OrderedSet'
requires = requires.difference(provided)
if not requires:
return requires
for pred in graph.bfs_predecessors_iter(node):
requires = requires - pred.provides
requires = requires.difference(pred.provides)
if not requires:
return requires
return requires

View File

@@ -27,40 +27,40 @@ class FlowDependenciesTest(test.TestCase):
def test_task_without_dependencies(self):
flow = utils.TaskNoRequiresNoReturns()
self.assertEqual(flow.requires, set())
self.assertEqual(flow.provides, set())
self.assertEqual(set(), flow.requires)
self.assertEqual(set(), flow.provides)
def test_task_requires_default_values(self):
flow = utils.TaskMultiArg()
self.assertEqual(flow.requires, set(['x', 'y', 'z']))
self.assertEqual(flow.provides, set())
self.assertEqual(set(['x', 'y', 'z']), flow.requires)
self.assertEqual(set(), flow.provides, )
def test_task_requires_rebinded_mapped(self):
flow = utils.TaskMultiArg(rebind={'x': 'a', 'y': 'b', 'z': 'c'})
self.assertEqual(flow.requires, set(['a', 'b', 'c']))
self.assertEqual(flow.provides, set())
self.assertEqual(set(['a', 'b', 'c']), flow.requires)
self.assertEqual(set(), flow.provides)
def test_task_requires_additional_values(self):
flow = utils.TaskMultiArg(requires=['a', 'b'])
self.assertEqual(flow.requires, set(['a', 'b', 'x', 'y', 'z']))
self.assertEqual(flow.provides, set())
self.assertEqual(set(['a', 'b', 'x', 'y', 'z']), flow.requires)
self.assertEqual(set(), flow.provides)
def test_task_provides_values(self):
flow = utils.TaskMultiReturn(provides=['a', 'b', 'c'])
self.assertEqual(flow.requires, set())
self.assertEqual(flow.provides, set(['a', 'b', 'c']))
self.assertEqual(set(), flow.requires)
self.assertEqual(set(['a', 'b', 'c']), flow.provides)
def test_task_provides_and_requires_values(self):
flow = utils.TaskMultiArgMultiReturn(provides=['a', 'b', 'c'])
self.assertEqual(flow.requires, set(['x', 'y', 'z']))
self.assertEqual(flow.provides, set(['a', 'b', 'c']))
self.assertEqual(set(['x', 'y', 'z']), flow.requires)
self.assertEqual(set(['a', 'b', 'c']), flow.provides)
def test_linear_flow_without_dependencies(self):
flow = lf.Flow('lf').add(
utils.TaskNoRequiresNoReturns('task1'),
utils.TaskNoRequiresNoReturns('task2'))
self.assertEqual(flow.requires, set())
self.assertEqual(flow.provides, set())
self.assertEqual(set(), flow.requires)
self.assertEqual(set(), flow.provides)
def test_linear_flow_requires_values(self):
flow = lf.Flow('lf').add(

View File

@@ -52,36 +52,36 @@ class TaskTest(test.TestCase):
def test_passed_name(self):
my_task = MyTask(name='my name')
self.assertEqual(my_task.name, 'my name')
self.assertEqual('my name', my_task.name)
def test_generated_name(self):
my_task = MyTask()
self.assertEqual(my_task.name,
'%s.%s' % (__name__, 'MyTask'))
self.assertEqual('%s.%s' % (__name__, 'MyTask'),
my_task.name)
def test_task_str(self):
my_task = MyTask(name='my')
self.assertEqual(str(my_task), 'my==1.0')
self.assertEqual('my==1.0', str(my_task))
def test_task_repr(self):
my_task = MyTask(name='my')
self.assertEqual(repr(my_task), '<%s.MyTask my==1.0>' % __name__)
self.assertEqual('<%s.MyTask my==1.0>' % __name__, repr(my_task))
def test_no_provides(self):
my_task = MyTask()
self.assertEqual(my_task.save_as, {})
self.assertEqual({}, my_task.save_as)
def test_provides(self):
my_task = MyTask(provides='food')
self.assertEqual(my_task.save_as, {'food': None})
self.assertEqual({'food': None}, my_task.save_as)
def test_multi_provides(self):
my_task = MyTask(provides=('food', 'water'))
self.assertEqual(my_task.save_as, {'food': 0, 'water': 1})
self.assertEqual({'food': 0, 'water': 1}, my_task.save_as)
def test_unpack(self):
my_task = MyTask(provides=('food',))
self.assertEqual(my_task.save_as, {'food': 0})
self.assertEqual({'food': 0}, my_task.save_as)
def test_bad_provides(self):
self.assertRaisesRegexp(TypeError, '^Atom provides',
@@ -89,28 +89,34 @@ class TaskTest(test.TestCase):
def test_requires_by_default(self):
my_task = MyTask()
self.assertEqual(my_task.rebind, {
expected = {
'spam': 'spam',
'eggs': 'eggs',
'context': 'context'
})
}
self.assertEqual(expected,
my_task.rebind)
self.assertEqual(set(['spam', 'eggs', 'context']),
my_task.requires)
def test_requires_amended(self):
my_task = MyTask(requires=('spam', 'eggs'))
self.assertEqual(my_task.rebind, {
expected = {
'spam': 'spam',
'eggs': 'eggs',
'context': 'context'
})
}
self.assertEqual(expected, my_task.rebind)
def test_requires_explicit(self):
my_task = MyTask(auto_extract=False,
requires=('spam', 'eggs', 'context'))
self.assertEqual(my_task.rebind, {
expected = {
'spam': 'spam',
'eggs': 'eggs',
'context': 'context'
})
}
self.assertEqual(expected, my_task.rebind)
def test_requires_explicit_not_enough(self):
self.assertRaisesRegexp(ValueError, '^Missing arguments',
@@ -119,36 +125,43 @@ class TaskTest(test.TestCase):
def test_requires_ignores_optional(self):
my_task = DefaultArgTask()
self.assertEqual(my_task.requires, set(['spam']))
self.assertEqual(my_task.optional, set(['eggs']))
self.assertEqual(set(['spam']), my_task.requires)
self.assertEqual(set(['eggs']), my_task.optional)
def test_requires_allows_optional(self):
my_task = DefaultArgTask(requires=('spam', 'eggs'))
self.assertEqual(my_task.requires, set(['spam', 'eggs']))
self.assertEqual(my_task.optional, set())
self.assertEqual(set(['spam', 'eggs']), my_task.requires)
self.assertEqual(set(), my_task.optional)
def test_rebind_includes_optional(self):
my_task = DefaultArgTask()
self.assertEqual(my_task.rebind, {
expected = {
'spam': 'spam',
'eggs': 'eggs',
})
}
self.assertEqual(expected, my_task.rebind)
def test_rebind_all_args(self):
my_task = MyTask(rebind={'spam': 'a', 'eggs': 'b', 'context': 'c'})
self.assertEqual(my_task.rebind, {
expected = {
'spam': 'a',
'eggs': 'b',
'context': 'c'
})
}
self.assertEqual(expected, my_task.rebind)
self.assertEqual(set(['a', 'b', 'c']),
my_task.requires)
def test_rebind_partial(self):
my_task = MyTask(rebind={'spam': 'a', 'eggs': 'b'})
self.assertEqual(my_task.rebind, {
expected = {
'spam': 'a',
'eggs': 'b',
'context': 'context'
})
}
self.assertEqual(expected, my_task.rebind)
self.assertEqual(set(['a', 'b', 'context']),
my_task.requires)
def test_rebind_unknown(self):
self.assertRaisesRegexp(ValueError, '^Extra arguments',
@@ -156,26 +169,33 @@ class TaskTest(test.TestCase):
def test_rebind_unknown_kwargs(self):
task = KwargsTask(rebind={'foo': 'bar'})
self.assertEqual(task.rebind, {
expected = {
'foo': 'bar',
'spam': 'spam'
})
}
self.assertEqual(expected, task.rebind)
def test_rebind_list_all(self):
my_task = MyTask(rebind=('a', 'b', 'c'))
self.assertEqual(my_task.rebind, {
expected = {
'context': 'a',
'spam': 'b',
'eggs': 'c'
})
}
self.assertEqual(expected, my_task.rebind)
self.assertEqual(set(['a', 'b', 'c']),
my_task.requires)
def test_rebind_list_partial(self):
my_task = MyTask(rebind=('a', 'b'))
self.assertEqual(my_task.rebind, {
expected = {
'context': 'a',
'spam': 'b',
'eggs': 'eggs'
})
}
self.assertEqual(expected, my_task.rebind)
self.assertEqual(set(['a', 'b', 'eggs']),
my_task.requires)
def test_rebind_list_more(self):
self.assertRaisesRegexp(ValueError, '^Extra arguments',
@@ -183,11 +203,14 @@ class TaskTest(test.TestCase):
def test_rebind_list_more_kwargs(self):
task = KwargsTask(rebind=('a', 'b', 'c'))
self.assertEqual(task.rebind, {
expected = {
'spam': 'a',
'b': 'b',
'c': 'c'
})
}
self.assertEqual(expected, task.rebind)
self.assertEqual(set(['a', 'b', 'c']),
task.requires)
def test_rebind_list_bad_value(self):
self.assertRaisesRegexp(TypeError, '^Invalid rebind value',
@@ -195,13 +218,13 @@ class TaskTest(test.TestCase):
def test_default_provides(self):
task = DefaultProvidesTask()
self.assertEqual(task.provides, set(['def']))
self.assertEqual(task.save_as, {'def': None})
self.assertEqual(set(['def']), task.provides)
self.assertEqual({'def': None}, task.save_as)
def test_default_provides_can_be_overridden(self):
task = DefaultProvidesTask(provides=('spam', 'eggs'))
self.assertEqual(task.provides, set(['spam', 'eggs']))
self.assertEqual(task.save_as, {'spam': 0, 'eggs': 1})
self.assertEqual(set(['spam', 'eggs']), task.provides)
self.assertEqual({'spam': 0, 'eggs': 1}, task.save_as)
def test_update_progress_within_bounds(self):
values = [0.0, 0.5, 1.0]
@@ -213,7 +236,7 @@ class TaskTest(test.TestCase):
a_task = ProgressTask()
a_task.notifier.register(task.EVENT_UPDATE_PROGRESS, progress_callback)
a_task.execute(values)
self.assertEqual(result, values)
self.assertEqual(values, result)
@mock.patch.object(task.LOG, 'warn')
def test_update_progress_lower_bound(self, mocked_warn):
@@ -225,8 +248,8 @@ class TaskTest(test.TestCase):
a_task = ProgressTask()
a_task.notifier.register(task.EVENT_UPDATE_PROGRESS, progress_callback)
a_task.execute([-1.0, -0.5, 0.0])
self.assertEqual(result, [0.0, 0.0, 0.0])
self.assertEqual(mocked_warn.call_count, 2)
self.assertEqual([0.0, 0.0, 0.0], result)
self.assertEqual(2, mocked_warn.call_count)
@mock.patch.object(task.LOG, 'warn')
def test_update_progress_upper_bound(self, mocked_warn):
@@ -238,8 +261,8 @@ class TaskTest(test.TestCase):
a_task = ProgressTask()
a_task.notifier.register(task.EVENT_UPDATE_PROGRESS, progress_callback)
a_task.execute([1.0, 1.5, 2.0])
self.assertEqual(result, [1.0, 1.0, 1.0])
self.assertEqual(mocked_warn.call_count, 2)
self.assertEqual([1.0, 1.0, 1.0], result)
self.assertEqual(2, mocked_warn.call_count)
@mock.patch.object(notifier.LOG, 'warn')
def test_update_progress_handler_failure(self, mocked_warn):
@@ -256,34 +279,34 @@ class TaskTest(test.TestCase):
a_task = MyTask()
self.assertRaises(ValueError, a_task.notifier.register,
task.EVENT_UPDATE_PROGRESS, None)
self.assertEqual(len(a_task.notifier), 0)
self.assertEqual(0, len(a_task.notifier))
def test_deregister_any_handler(self):
a_task = MyTask()
self.assertEqual(len(a_task.notifier), 0)
self.assertEqual(0, len(a_task.notifier))
a_task.notifier.register(task.EVENT_UPDATE_PROGRESS,
lambda event_type, details: None)
self.assertEqual(len(a_task.notifier), 1)
self.assertEqual(1, len(a_task.notifier))
a_task.notifier.deregister_event(task.EVENT_UPDATE_PROGRESS)
self.assertEqual(len(a_task.notifier), 0)
self.assertEqual(0, len(a_task.notifier))
def test_deregister_any_handler_empty_listeners(self):
a_task = MyTask()
self.assertEqual(len(a_task.notifier), 0)
self.assertEqual(0, len(a_task.notifier))
self.assertFalse(a_task.notifier.deregister_event(
task.EVENT_UPDATE_PROGRESS))
self.assertEqual(len(a_task.notifier), 0)
self.assertEqual(0, len(a_task.notifier))
def test_deregister_non_existent_listener(self):
handler1 = lambda event_type, details: None
handler2 = lambda event_type, details: None
a_task = MyTask()
a_task.notifier.register(task.EVENT_UPDATE_PROGRESS, handler1)
self.assertEqual(len(list(a_task.notifier.listeners_iter())), 1)
self.assertEqual(1, len(list(a_task.notifier.listeners_iter())))
a_task.notifier.deregister(task.EVENT_UPDATE_PROGRESS, handler2)
self.assertEqual(len(list(a_task.notifier.listeners_iter())), 1)
self.assertEqual(1, len(list(a_task.notifier.listeners_iter())))
a_task.notifier.deregister(task.EVENT_UPDATE_PROGRESS, handler1)
self.assertEqual(len(list(a_task.notifier.listeners_iter())), 0)
self.assertEqual(0, len(list(a_task.notifier.listeners_iter())))
def test_bind_not_callable(self):
a_task = MyTask()
@@ -295,8 +318,8 @@ class TaskTest(test.TestCase):
a_task = MyTask()
a_task.notifier.register(task.EVENT_UPDATE_PROGRESS, handler1)
b_task = a_task.copy(retain_listeners=False)
self.assertEqual(len(a_task.notifier), 1)
self.assertEqual(len(b_task.notifier), 0)
self.assertEqual(1, len(a_task.notifier))
self.assertEqual(0, len(b_task.notifier))
def test_copy_listeners(self):
handler1 = lambda event_type, details: None
@@ -304,15 +327,15 @@ class TaskTest(test.TestCase):
a_task = MyTask()
a_task.notifier.register(task.EVENT_UPDATE_PROGRESS, handler1)
b_task = a_task.copy()
self.assertEqual(len(b_task.notifier), 1)
self.assertEqual(1, len(b_task.notifier))
self.assertTrue(a_task.notifier.deregister_event(
task.EVENT_UPDATE_PROGRESS))
self.assertEqual(len(a_task.notifier), 0)
self.assertEqual(len(b_task.notifier), 1)
self.assertEqual(0, len(a_task.notifier))
self.assertEqual(1, len(b_task.notifier))
b_task.notifier.register(task.EVENT_UPDATE_PROGRESS, handler2)
listeners = dict(list(b_task.notifier.listeners_iter()))
self.assertEqual(len(listeners[task.EVENT_UPDATE_PROGRESS]), 2)
self.assertEqual(len(a_task.notifier), 0)
self.assertEqual(2, len(listeners[task.EVENT_UPDATE_PROGRESS]))
self.assertEqual(0, len(a_task.notifier))
class FunctorTaskTest(test.TestCase):
@@ -320,7 +343,7 @@ class FunctorTaskTest(test.TestCase):
def test_creation_with_version(self):
version = (2, 0)
f_task = task.FunctorTask(lambda: None, version=version)
self.assertEqual(f_task.version, version)
self.assertEqual(version, f_task.version)
def test_execute_not_callable(self):
self.assertRaises(ValueError, task.FunctorTask, 2)