"""Plugin registry for the strict ResultTable API.""" from __future__ import annotations from dataclasses import dataclass from typing import Any, Callable, Dict, Iterable, List, Optional, Union from SYS.result_table_api import ColumnSpec, ProviderAdapter, ResultModel, ResultTable, ensure_result_model ColumnFactory = Callable[[List[ResultModel]], List[ColumnSpec]] SelectionFn = Callable[[ResultModel], List[str]] @dataclass class Plugin: name: str adapter: ProviderAdapter # columns can be a static list or a factory that derives columns from sample rows columns: Union[List[ColumnSpec], ColumnFactory] selection_fn: SelectionFn metadata: Optional[Dict[str, Any]] = None def get_columns(self, rows: Optional[Iterable[ResultModel]] = None) -> List[ColumnSpec]: if self.columns is None: raise ValueError(f"plugin '{self.name}' must define columns") if callable(self.columns): rows_list = list(rows) if rows is not None else [] cols = list(self.columns(rows_list)) else: cols = list(self.columns) if not cols: raise ValueError(f"plugin '{self.name}' produced no columns") return cols def selection_args(self, row: ResultModel) -> List[str]: if not callable(self.selection_fn): raise ValueError(f"plugin '{self.name}' must define a selection function") sel = list(self.selection_fn(ensure_result_model(row))) return sel def build_table(self, items: Iterable[Any]) -> ResultTable: """Materialize adapter output into a ResultTable (strict, no legacy types).""" try: rows = [ensure_result_model(r) for r in self.adapter(items)] except Exception as exc: raise RuntimeError(f"plugin '{self.name}' adapter failed") from exc cols = self.get_columns(rows) return ResultTable(plugin=self.name, rows=rows, columns=cols, meta=self.metadata or {}) def serialize_row(self, row: ResultModel) -> Dict[str, Any]: r = ensure_result_model(row) metadata = r.metadata or {} out: Dict[str, Any] = { "title": r.title, "path": r.path, "ext": r.ext, "size_bytes": r.size_bytes, "metadata": metadata, "source": r.source or self.name, "_selection_args": self.selection_args(r), } selection_action = metadata.get("_selection_action") or metadata.get("selection_action") if selection_action: out["_selection_action"] = [ str(x) for x in selection_action if x is not None ] return out def serialize_rows(self, rows: Iterable[ResultModel]) -> List[Dict[str, Any]]: return [self.serialize_row(r) for r in rows] _PLUGINS: Dict[str, Plugin] = {} def register_plugin( name: str, adapter: ProviderAdapter, *, columns: Union[List[ColumnSpec], ColumnFactory], selection_fn: SelectionFn, metadata: Optional[Dict[str, Any]] = None, ) -> Plugin: name = str(name or "").strip().lower() if not name: raise ValueError("plugin name required") if name in _PLUGINS: raise ValueError(f"plugin already registered: {name}") if columns is None: raise ValueError("plugin registration requires columns") if selection_fn is None: raise ValueError("plugin registration requires selection_fn") plugin = Plugin(name=name, adapter=adapter, columns=columns, selection_fn=selection_fn, metadata=metadata) _PLUGINS[name] = plugin return plugin def get_plugin(name: str) -> Plugin: normalized = str(name or "").lower() if normalized not in _PLUGINS: raise KeyError(f"plugin not registered: {name}") return _PLUGINS[normalized] def list_plugins() -> List[str]: return list(_PLUGINS.keys())