def add(self, *nodes, **kwargs): """Adds a given task/tasks/flow/flows to this flow. Note that if the addition of these nodes (and any edges) creates a `cyclic`_ graph then a :class:`~taskflow.exceptions.DependencyFailure` will be raised and the applied changes will be discarded. :param nodes: node(s) to add to the flow :param kwargs: keyword arguments, the two keyword arguments currently processed are: * ``resolve_requires`` a boolean that when true (the default) implies that when node(s) are added their symbol requirements will be matched to existing node(s) and links will be automatically made to those providers. If multiple possible providers exist then a :class:`~taskflow.exceptions.AmbiguousDependency` exception will be raised and the provided additions will be discarded. * ``resolve_existing``, a boolean that when true (the default) implies that on addition of a new node that existing node(s) will have their requirements scanned for symbols that this newly added node can provide. If a match is found a link is automatically created from the newly added node to the requiree. .. _cyclic: https://en.wikipedia.org/wiki/Cycle_graph """ # Let's try to avoid doing any work if we can; since the below code # after this filter can create more temporary graphs that aren't needed # if the nodes already exist... nodes = [i for i in nodes if not self._graph.has_node(i)] if not nodes: return self # This syntax will *hopefully* be better in future versions of python. # # See: http://legacy.python.org/dev/peps/pep-3102/ (python 3.0+) resolve_requires = bool(kwargs.get('resolve_requires', True)) resolve_existing = bool(kwargs.get('resolve_existing', True)) # Figure out what the existing nodes *still* require and what they # provide so we can do this lookup later when inferring. required = collections.defaultdict(list) provided = collections.defaultdict(list) retry_provides = set() if self._retry is not None: for value in self._retry.requires: required[value].append(self._retry) for value in self._retry.provides: retry_provides.add(value) provided[value].append(self._retry) for node in self._graph.nodes_iter(): for value in self._unsatisfied_requires(node, self._graph, retry_provides): required[value].append(node) for value in node.provides: provided[value].append(node) # NOTE(harlowja): Add node(s) and edge(s) to a temporary copy of the # underlying graph and only if that is successful added to do we then # swap with the underlying graph. tmp_graph = gr.DiGraph(self._graph) for node in nodes: tmp_graph.add_node(node) # Try to find a valid provider. if resolve_requires: for value in self._unsatisfied_requires( node, tmp_graph, retry_provides): if value in provided: providers = provided[value] if len(providers) > 1: provider_names = [n.name for n in providers] raise exc.AmbiguousDependency( "Resolution error detected when" " adding '%(node)s', multiple" " providers %(providers)s found for" " required symbol '%(value)s'" % dict(node=node.name, providers=sorted(provider_names), value=value)) else: self._link(providers[0], node, graph=tmp_graph, reason=value) else: required[value].append(node) for value in node.provides: provided[value].append(node) # See if what we provide fulfills any existing requiree. if resolve_existing: for value in node.provides: if value in required: for requiree in list(required[value]): if requiree is not node: self._link(node, requiree, graph=tmp_graph, reason=value) required[value].remove(requiree) self._swap(tmp_graph) return self
def add(self, *items, **kwargs): """Adds a given task/tasks/flow/flows to this flow. :param items: items to add to the flow :param kwargs: keyword arguments, the two keyword arguments currently processed are: * ``resolve_requires`` a boolean that when true (the default) implies that when items are added their symbol requirements will be matched to existing items and links will be automatically made to those providers. If multiple possible providers exist then a AmbiguousDependency exception will be raised. * ``resolve_existing``, a boolean that when true (the default) implies that on addition of a new item that existing items will have their requirements scanned for symbols that this newly added item can provide. If a match is found a link is automatically created from the newly added item to the requiree. """ items = [i for i in items if not self._graph.has_node(i)] if not items: return self # This syntax will *hopefully* be better in future versions of python. # # See: http://legacy.python.org/dev/peps/pep-3102/ (python 3.0+) resolve_requires = bool(kwargs.get('resolve_requires', True)) resolve_existing = bool(kwargs.get('resolve_existing', True)) # Figure out what the existing nodes *still* require and what they # provide so we can do this lookup later when inferring. required = collections.defaultdict(list) provided = collections.defaultdict(list) retry_provides = set() if self._retry is not None: for value in self._retry.requires: required[value].append(self._retry) for value in self._retry.provides: retry_provides.add(value) provided[value].append(self._retry) for item in self._graph.nodes_iter(): for value in _unsatisfied_requires(item, self._graph, retry_provides): required[value].append(item) for value in item.provides: provided[value].append(item) # NOTE(harlowja): Add items and edges to a temporary copy of the # underlying graph and only if that is successful added to do we then # swap with the underlying graph. tmp_graph = gr.DiGraph(self._graph) for item in items: tmp_graph.add_node(item) # Try to find a valid provider. if resolve_requires: for value in _unsatisfied_requires(item, tmp_graph, retry_provides): if value in provided: providers = provided[value] if len(providers) > 1: provider_names = [n.name for n in providers] raise exc.AmbiguousDependency( "Resolution error detected when" " adding %(item)s, multiple" " providers %(providers)s found for" " required symbol '%(value)s'" % dict(item=item.name, providers=sorted(provider_names), value=value)) else: self._link(providers[0], item, graph=tmp_graph, reason=value) else: required[value].append(item) for value in item.provides: provided[value].append(item) # See if what we provide fulfills any existing requiree. if resolve_existing: for value in item.provides: if value in required: for requiree in list(required[value]): if requiree is not item: self._link(item, requiree, graph=tmp_graph, reason=value) required[value].remove(requiree) self._swap(tmp_graph) return self