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

@@ -89,7 +89,6 @@ class ProviderRegistry:
replace: bool = False,
) -> ProviderInfo:
"""Register a provider class with canonical and alias names."""
candidates = self._candidate_names(provider_class, override_name)
if not candidates:
raise ValueError("provider name candidates are required")
@@ -397,6 +396,125 @@ def match_provider_name_for_url(url: str) -> Optional[str]:
return None
def provider_inline_query_choices(
provider_name: str,
field_name: str,
config: Optional[Dict[str, Any]] = None,
) -> List[str]:
"""Return provider-declared inline query choices for a field (e.g., system:GBA).
Providers can expose a mapping via ``QUERY_ARG_CHOICES`` (preferred) or
``INLINE_QUERY_FIELD_CHOICES`` / ``inline_query_field_choices()``. The helper
keeps completion logic simple and reusable.
This helper keeps completion logic simple and reusable.
"""
pname = str(provider_name or "").strip().lower()
field = str(field_name or "").strip().lower()
if not pname or not field:
return []
provider = get_search_provider(pname, config)
if provider is None:
provider = get_provider(pname, config)
if provider is None:
return []
def _normalize_choice_entry(entry: Any) -> Optional[Dict[str, Any]]:
if entry is None:
return None
if isinstance(entry, dict):
value = entry.get("value")
text = entry.get("text") or entry.get("label") or value
aliases = entry.get("alias") or entry.get("aliases") or []
value_str = str(value) if value is not None else (str(text) if text is not None else None)
text_str = str(text) if text is not None else value_str
if not value_str or not text_str:
return None
alias_list = [str(a) for a in aliases if a is not None]
return {"value": value_str, "text": text_str, "aliases": alias_list}
# string/other primitives
return {"value": str(entry), "text": str(entry), "aliases": []}
def _collect_mapping(p) -> Dict[str, List[Dict[str, Any]]]:
mapping: Dict[str, List[Dict[str, Any]]] = {}
base = getattr(p, "QUERY_ARG_CHOICES", None)
if not isinstance(base, dict):
base = getattr(p, "INLINE_QUERY_FIELD_CHOICES", None)
if isinstance(base, dict):
for k, v in base.items():
normalized: List[Dict[str, Any]] = []
seq = v
try:
if callable(seq):
seq = seq()
except Exception:
seq = v
if isinstance(seq, dict):
seq = seq.get("choices") or seq.get("values") or seq
if isinstance(seq, (list, tuple, set)):
for entry in seq:
n = _normalize_choice_entry(entry)
if n:
normalized.append(n)
if normalized:
mapping[str(k).strip().lower()] = normalized
try:
fn = getattr(p, "inline_query_field_choices", None)
if callable(fn):
extra = fn()
if isinstance(extra, dict):
for k, v in extra.items():
normalized: List[Dict[str, Any]] = []
seq = v
try:
if callable(seq):
seq = seq()
except Exception:
seq = v
if isinstance(seq, dict):
seq = seq.get("choices") or seq.get("values") or seq
if isinstance(seq, (list, tuple, set)):
for entry in seq:
n = _normalize_choice_entry(entry)
if n:
normalized.append(n)
if normalized:
mapping[str(k).strip().lower()] = normalized
except Exception:
pass
return mapping
try:
mapping = _collect_mapping(provider)
if not mapping:
return []
entries = mapping.get(field, [])
if not entries:
return []
seen: set[str] = set()
out: List[str] = []
for entry in entries:
text = entry.get("text") or entry.get("value")
if not text:
continue
text_str = str(text)
if text_str in seen:
continue
seen.add(text_str)
out.append(text_str)
for alias in entry.get("aliases", []):
alias_str = str(alias)
if alias_str and alias_str not in seen:
seen.add(alias_str)
out.append(alias_str)
return out
except Exception:
return []
def get_provider_for_url(url: str,
config: Optional[Dict[str, Any]] = None) -> Optional[Provider]:
name = match_provider_name_for_url(url)
@@ -405,6 +523,60 @@ def get_provider_for_url(url: str,
return get_provider(name, config)
def resolve_inline_filters(
provider: Provider,
inline_args: Dict[str, Any],
*,
field_transforms: Optional[Dict[str, Any]] = None,
) -> Dict[str, str]:
"""Map inline query args to provider filter values using declared choices.
- Uses provider's inline choice mapping (value/text/aliases) to resolve user text.
- Applies optional per-field transforms (e.g., str.upper).
- Returns normalized filters suitable for provider.search.
"""
filters: Dict[str, str] = {}
if not inline_args:
return filters
mapping = _collect_mapping(provider)
transforms = field_transforms or {}
for raw_key, raw_val in inline_args.items():
if raw_val is None:
continue
key = str(raw_key or "").strip().lower()
val_str = str(raw_val).strip()
if not key or not val_str:
continue
entries = mapping.get(key, [])
resolved: Optional[str] = None
val_lower = val_str.lower()
for entry in entries:
text = str(entry.get("text") or "").strip()
value = str(entry.get("value") or "").strip()
aliases = [str(a).strip() for a in entry.get("aliases", []) if a is not None]
if val_lower in {text.lower(), value.lower()} or val_lower in {a.lower() for a in aliases}:
resolved = value or text or val_str
break
if resolved is None:
resolved = val_str
transform = transforms.get(key)
if callable(transform):
try:
resolved = transform(resolved)
except Exception:
pass
if resolved:
filters[key] = str(resolved)
return filters
__all__ = [
"ProviderInfo",
"Provider",
@@ -423,4 +595,5 @@ __all__ = [
"get_provider_class",
"selection_auto_stage_for_table",
"download_soulseek_file",
"provider_inline_query_choices",
]