class WorkflowExecutor:
    def __init__(self, graph):
        self.graph = graph
        self.cache = {}

    def execute_from_writer(self, writer_node):
        # Execute dependencies first
        inputs = {}

        for port_name, requirement in writer_node.inputs.items():
            src_node, src_port = self.graph.get_connection(writer_node, port_name)
            inputs[port_name] = self._execute_node(src_node)

        writer_node.run(**inputs)

    def _execute_node(self, node):
        if node in self.cache:
            return self.cache[node]

        # Reader
        if hasattr(node, "load_data"):
            result = node.load_data()
            self.cache[node] = result
            return result

        # Processor
        inputs = {}
        for port_name in node.inputs:
            src_node, _ = self.graph.get_connection(node, port_name)
            inputs[port_name] = self._execute_node(src_node)

        result = node.run(**inputs)
        self.cache[node] = result
        return result
