from __future__ import annotations from abc import ABC, abstractmethod from typing import Dict, Optional import polars as pl import xml.etree.ElementTree as ET from engine.graph import NodeDef from engine.context import RunContext class BaseTool(ABC): def __init__(self, node: NodeDef, ctx: RunContext): self.node = node self.ctx = ctx self.config: Optional[ET.Element] = node.config @abstractmethod def execute(self, inputs: Dict[str, pl.DataFrame]) -> Dict[str, pl.DataFrame]: """Execute the tool and return named output DataFrames.""" def _cfg(self, xpath: str, default: Optional[str] = None) -> Optional[str]: el = self.config.find(xpath) if self.config is not None else None return el.text if el is not None else default def _cfg_attr(self, xpath: str, attr: str, default: Optional[str] = None) -> Optional[str]: el = self.config.find(xpath) if self.config is not None else None return el.attrib.get(attr, default) if el is not None else default def _cfg_text(self, xpath: str, default: str = "") -> str: val = self._cfg(xpath, default) return val if val is not None else default