Merge "Use ordered[set/dict] to retain ordering"
This commit is contained in:
@@ -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
|
||||
+++++++++++++++++
|
||||
|
||||
142
taskflow/atom.py
142
taskflow/atom.py
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user