Exemple #1
0
    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
Exemple #2
0
    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