HTTP: prefer pip-system-certs/certifi_win32 bundle; use init-time verify in retries; add tests

This commit is contained in:
2026-01-05 13:09:24 -08:00
parent 1f765cffda
commit ac1d1d634f
12 changed files with 19424 additions and 2371 deletions

View File

@@ -0,0 +1,80 @@
"""Provider registry for ResultTable API (breaking, strict API).
Providers register themselves here with an adapter and optional column factory
and selection function. Consumers (cmdlets) can look up providers by name and
obtain the columns and selection behavior for building tables and for selection
args used by subsequent cmdlets.
"""
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
ColumnFactory = Callable[[Iterable[ResultModel]], List[ColumnSpec]]
SelectionFn = Callable[[ResultModel], List[str]]
@dataclass
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
metadata: Optional[Dict[str, Any]] = None
def get_columns(self, rows: Optional[Iterable[ResultModel]] = None) -> List[ColumnSpec]:
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)]
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)]
_PROVIDERS: Dict[str, Provider] = {}
def register_provider(
name: str,
adapter: ProviderAdapter,
*,
columns: Optional[Union[List[ColumnSpec], ColumnFactory]] = None,
selection_fn: Optional[SelectionFn] = None,
metadata: Optional[Dict[str, Any]] = None,
) -> Provider:
name = str(name or "").strip().lower()
if not name:
raise ValueError("provider name required")
if name in _PROVIDERS:
raise ValueError(f"provider already registered: {name}")
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()]
def list_providers() -> List[str]:
return list(_PROVIDERS.keys())

109
SYS/result_table_api.py Normal file
View File

@@ -0,0 +1,109 @@
"""ResultTable API types and small helpers (breaking: no legacy compatibility).
This module provides the canonical dataclasses and protocols that providers and
renderers must use. It intentionally refuses to accept legacy dicts/strings/objs
— adapters must produce `ResultModel` instances.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, Iterable, Optional, Protocol
@dataclass(frozen=True)
class ResultModel:
"""Canonical result model that providers must produce.
Fields:
- title: human-friendly title (required)
- path: optional filesystem path/URL
- ext: file extension (without dot), e.g. "pdf", "mp3"
- size_bytes: optional size in bytes
- media_kind: one of 'video','audio','image','doc', etc.
- metadata: arbitrary provider-specific metadata
- source: provider name string
"""
title: str
path: Optional[str] = None
ext: Optional[str] = None
size_bytes: Optional[int] = None
media_kind: Optional[str] = None
metadata: Dict[str, Any] = field(default_factory=dict)
source: Optional[str] = None
@dataclass(frozen=True)
class ColumnSpec:
"""Specification for a column that renderers will use.
extractor: callable that accepts a ResultModel and returns the cell value.
format_fn: optional callable used to format the extracted value to string.
"""
name: str
header: str
extractor: Callable[[ResultModel], Any]
format_fn: Optional[Callable[[Any], str]] = None
width: Optional[int] = None
sortable: bool = False
ProviderAdapter = Callable[[Iterable[Any]], Iterable[ResultModel]]
"""Type for provider adapters.
Adapters must accept provider-specific sequence/iterable and yield
`ResultModel` instances only. Anything else is an error (no implicit normalization).
"""
class Renderer(Protocol):
"""Renderer protocol.
Implementations should accept rows and columns and return a renderable
or side-effect (e.g., print to terminal). Keep implementations deterministic
and side-effect-free when possible (return a Rich renderable object).
"""
def render(self, rows: Iterable[ResultModel], columns: Iterable[ColumnSpec], meta: Optional[Dict[str, Any]] = None) -> Any: # pragma: no cover - interface
...
# Small helper enforcing strict API usage
def ensure_result_model(obj: Any) -> ResultModel:
"""Ensure `obj` is a `ResultModel` instance, else raise TypeError.
This makes the API intentionally strict: providers must construct ResultModel
objects.
"""
if isinstance(obj, ResultModel):
return obj
raise TypeError("ResultModel required; providers must yield ResultModel instances")
# Convenience column spec generators
def title_column() -> ColumnSpec:
return ColumnSpec("title", "Title", lambda r: r.title)
def ext_column() -> ColumnSpec:
return ColumnSpec("ext", "Ext", lambda r: r.ext or "")
# Helper to build a ColumnSpec that extracts a metadata key from ResultModel
def metadata_column(key: str, header: Optional[str] = None, format_fn: Optional[Callable[[Any], str]] = None) -> ColumnSpec:
hdr = header or str(key).replace("_", " ").title()
return ColumnSpec(name=key, header=hdr, extractor=lambda r: (r.metadata or {}).get(key), format_fn=format_fn)
__all__ = [
"ResultModel",
"ColumnSpec",
"ProviderAdapter",
"Renderer",
"ensure_result_model",
"title_column",
"ext_column",
]

View File

@@ -0,0 +1,67 @@
"""Renderers for the ResultTable API.
This module provides a Rich-based Renderer implementation that returns a
`rich.table.Table` renderable. The implementation is intentionally small and
focused on the command-line display use-case; keep logic side-effect-free where
possible and let callers decide whether to `Console.print()` or capture output.
"""
from __future__ import annotations
from typing import Any, Dict, Iterable, Optional
from SYS.result_table_api import ColumnSpec, ResultModel, Renderer
class RichRenderer(Renderer):
"""Rich renderer implementing the `Renderer` protocol.
Usage:
from rich.console import Console
table = RichRenderer().render(rows, columns, meta)
Console().print(table)
"""
def render(self, rows: Iterable[ResultModel], columns: Iterable[ColumnSpec], meta: Optional[Dict[str, Any]] = None) -> Any: # pragma: no cover - simple wrapper
try:
from rich.table import Table as RichTable
except Exception as exc:
raise RuntimeError("rich is required for RichRenderer") from exc
table = RichTable(show_header=True, header_style="bold")
cols = list(columns)
for col in cols:
table.add_column(col.header)
for r in rows:
cells = []
for col in cols:
try:
raw = col.extractor(r)
if col.format_fn:
try:
cell = col.format_fn(raw)
except Exception:
cell = str(raw or "")
else:
cell = str(raw or "")
except Exception:
cell = ""
cells.append(cell)
table.add_row(*cells)
return table
# Small convenience function
def render_to_console(rows: Iterable[ResultModel], columns: Iterable[ColumnSpec], meta: Optional[Dict[str, Any]] = None) -> None:
try:
from rich.console import Console
except Exception:
# If rich isn't present, fall back to simple text output
for r in rows:
print(" ".join(str((col.extractor(r) or "")) for col in columns))
return
table = RichRenderer().render(rows, columns, meta)
Console().print(table)