Files
Medios-Macina/SYS/result_table_adapters.py
T

113 lines
3.8 KiB
Python

"""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(provider=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())