refactor(download): remove ProviderCore/download.py, move sanitize_filename to SYS.utils, replace callers to use API.HTTP.HTTPClient

This commit is contained in:
2026-01-06 01:38:59 -08:00
parent 3b363dd536
commit 41c11d39fd
38 changed files with 2640 additions and 526 deletions

View File

@@ -10,10 +10,10 @@ 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
from SYS.result_table_api import ColumnSpec, ProviderAdapter, ResultModel, ResultTable, ensure_result_model
ColumnFactory = Callable[[Iterable[ResultModel]], List[ColumnSpec]]
ColumnFactory = Callable[[List[ResultModel]], List[ColumnSpec]]
SelectionFn = Callable[[ResultModel], List[str]]
@@ -22,33 +22,57 @@ class Provider:
name: str
adapter: ProviderAdapter
# columns can be a static list or a factory that derives columns from sample rows
columns: Optional[Union[List[ColumnSpec], ColumnFactory]] = None
selection_fn: Optional[SelectionFn] = None
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"provider '{self.name}' must define columns")
if callable(self.columns):
try:
rows_list = list(rows) if rows is not None else []
return list(self.columns(rows_list))
except Exception:
# Fall back to a minimal Title column on errors
return [ColumnSpec("title", "Title", lambda r: r.title)]
if self.columns is not None:
return list(self.columns)
# Default minimal column set
return [ColumnSpec("title", "Title", lambda r: r.title)]
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"provider '{self.name}' produced no columns")
return cols
def selection_args(self, row: ResultModel) -> List[str]:
if callable(self.selection_fn):
try:
return list(self.selection_fn(row))
except Exception:
return []
# Default selector: prefer path flag, then title
if getattr(row, "path", None):
return ["-path", str(row.path)]
return ["-title", str(row.title)]
if not callable(self.selection_fn):
raise ValueError(f"provider '{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"provider '{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)
return {
"title": r.title,
"path": r.path,
"ext": r.ext,
"size_bytes": r.size_bytes,
"metadata": r.metadata or {},
"source": r.source or self.name,
"_selection_args": self.selection_args(r),
}
def serialize_rows(self, rows: Iterable[ResultModel]) -> List[Dict[str, Any]]:
return [self.serialize_row(r) for r in rows]
_PROVIDERS: Dict[str, Provider] = {}
@@ -58,8 +82,8 @@ def register_provider(
name: str,
adapter: ProviderAdapter,
*,
columns: Optional[Union[List[ColumnSpec], ColumnFactory]] = None,
selection_fn: Optional[SelectionFn] = None,
columns: Union[List[ColumnSpec], ColumnFactory],
selection_fn: SelectionFn,
metadata: Optional[Dict[str, Any]] = None,
) -> Provider:
name = str(name or "").strip().lower()
@@ -67,13 +91,20 @@ def register_provider(
raise ValueError("provider name required")
if name in _PROVIDERS:
raise ValueError(f"provider already registered: {name}")
if columns is None:
raise ValueError("provider registration requires columns")
if selection_fn is None:
raise ValueError("provider registration requires selection_fn")
p = Provider(name=name, adapter=adapter, columns=columns, selection_fn=selection_fn, metadata=metadata)
_PROVIDERS[name] = p
return p
def get_provider(name: str) -> Provider:
return _PROVIDERS[name.lower()]
normalized = str(name or "").lower()
if normalized not in _PROVIDERS:
raise KeyError(f"provider not registered: {name}")
return _PROVIDERS[normalized]
def list_providers() -> List[str]: