zincware/ZnFlow

Restructuring: Store connections on the networkx graph only

Closed this issue · 1 comments

Currently we store connections in the networkx graph and inside the NodeConnector.
This could cause issues if only one of them is changed. It is rather unlikely because they are frozen but still possible.

My suggestion: use the networkx MultiDiGraph to store the connections there, and only there. Do not use a NodeConnector but a NodeConnection which reads from the graph to display how it is connected.

In this case the Node itself doesn't have to know anything about the graph it belongs to and the graph fulfills it's purpose of managing the connections instead of Connector.result()

First tests show that one could use:

class NodeBaseMixin:
    def __getattribute__(self, item):
        value = super().__getattribute__(item)
        if NodeBaseMixin._graph_ is not None:
            if item not in ["_graph_"] and not item.startswith("__"):
                connector = Connection(instance=self, attribute=item)
                return connector
        return value

    def __setattr__(self, item, value) -> None:
        if isinstance(value, Connection):
            assert id(self) in self._graph_
            assert id(value.instance) in self._graph_
            self._graph_.add_edge(
                id(value.instance), id(self), i_attr=value.attribute, j_attr=item
            )
        super().__setattr__(item, value)

with

@dataclasses.dataclass(frozen=True)
class Connection:
    """A Connector for Nodes.
    instance: either a Node or FucntionFuture
    attribute:
        Node.attribute
        or FunctionFuture.result
        or None if the class is passed and not an attribute
    """

    instance: any
    attribute: any

it is REALLY important to mention, that within the DiGraph something like the following does not work within the context manager. This affects also all calls inside the __init__ or generally everything that is not specifically excluded inside __getattribute__.

@dataclasses.dataclass()
class Node(NodeBaseMixin):
    input: int
    output: int = None
    
    def __post_init__(self):
        self.output = self.input * 2  # this can not work, because self.input is Connection and not int