"""Base node class for all flow-based programming nodes."""
from __future__ import annotations
from abc import ABC, abstractmethod
import logging
from typing import TYPE_CHECKING, Any, Dict, Optional, Type
if TYPE_CHECKING:
from ..materializer import ExecutableGraph
from ..core import DEFAULT_QUEUE_SIZE, Port, QueueFullStrategy, T
logger = logging.getLogger(__name__)
[docs]
class Node(ABC):
"""Base class for all FBP nodes."""
[docs]
def __init__(self, name: str):
self.name = name
self._input_ports: Dict[str, Port[Any]] = {}
self._output_ports: Dict[str, Port[Any]] = {}
self._completed = False # Track if node has completed processing
self._error: Optional[Exception] = None
self._graph: Optional[ExecutableGraph] = None # Reference to containing graph
self._setup_ports()
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.name})"
def __str__(self) -> str:
return self.name
@abstractmethod
def _setup_ports(self) -> None:
"""Setup input and output ports for this node."""
[docs]
@abstractmethod
async def process(self) -> None:
"""Process data. Must be implemented by subclasses."""
[docs]
def add_output_port(self, name: str, data_type: Type[T]) -> Port[T]:
"""Add an output port to this node."""
port: Port[T] = Port(name, False, data_type, self)
self._output_ports[name] = port
return port
[docs]
def get_output_port(self, name: str) -> Port[Any]:
"""Get an output port by name."""
return self._output_ports[name]
[docs]
async def initialize(self) -> None:
"""Initialize all ports and perform setup. Can be overridden."""
for port in self._input_ports.values():
await port.initialize()
await self.on_start()
[docs]
async def on_start(self) -> None:
"""Called when node starts. Override for custom initialization."""
[docs]
async def on_stop(self) -> None:
"""Called when node stops. Override for cleanup."""
[docs]
async def on_error(self, error: Exception) -> None:
"""Called when an error occurs. Override for custom error handling."""
self._error = error
logger.error(f"Error in node {self.name}: {error}")
[docs]
async def on_complete(self) -> None:
"""Called when node completes all processing. Override for custom completion logic."""
self._completed = True
[docs]
def signal_completion(self) -> None:
"""Signal that this node has completed and won't produce more data."""
self._completed = True