update and cleanup repo
This commit is contained in:
+33
-111
@@ -23,6 +23,7 @@ from urllib.parse import urlparse
|
||||
from SYS.logger import log, debug
|
||||
|
||||
from PluginCore.base import Provider, SearchResult
|
||||
from PluginCore.inline_utils import collect_choice, resolve_filter
|
||||
|
||||
|
||||
_EXTERNAL_PLUGIN_ENV_VARS: tuple[str, ...] = ("MM_PLUGIN_PATH", "MEDEIA_PLUGIN_PATH")
|
||||
@@ -150,6 +151,14 @@ class PluginInfo:
|
||||
exposed = True
|
||||
return exposed and _class_supports_method(self.plugin_class, "upload", Provider.upload)
|
||||
|
||||
@property
|
||||
def supports_download(self) -> bool:
|
||||
return (
|
||||
_class_supports_method(self.plugin_class, "handle_url", Provider.handle_url)
|
||||
or _class_supports_method(self.plugin_class, "download_url", Provider.download_url)
|
||||
or _class_supports_method(self.plugin_class, "download", Provider.download)
|
||||
)
|
||||
|
||||
@property
|
||||
def is_multi_instance(self) -> bool:
|
||||
"""True if the plugin declares MULTI_INSTANCE = True."""
|
||||
@@ -542,6 +551,7 @@ def get_plugin_capabilities(
|
||||
"supported_cmdlets": [],
|
||||
"supports_search": False,
|
||||
"supports_upload": False,
|
||||
"supports_download": False,
|
||||
"supports_pipe_download": False,
|
||||
"supports_delete_file": False,
|
||||
"supports_url_association": False,
|
||||
@@ -582,6 +592,7 @@ def get_plugin_capabilities(
|
||||
"supported_cmdlets": supported_cmdlets,
|
||||
"supports_search": bool(info.supports_search),
|
||||
"supports_upload": bool(info.supports_upload),
|
||||
"supports_download": bool(info.supports_download),
|
||||
"supports_pipe_download": bool(supports_pipe_download),
|
||||
"supports_delete_file": bool(supports_delete_file),
|
||||
"supports_url_association": bool(supports_url_association),
|
||||
@@ -627,6 +638,14 @@ def _supports_upload(provider: Provider) -> bool:
|
||||
return exposed and _class_supports_method(provider.__class__, "upload", Provider.upload)
|
||||
|
||||
|
||||
def _supports_download(provider: Provider) -> bool:
|
||||
return (
|
||||
_class_supports_method(provider.__class__, "handle_url", Provider.handle_url)
|
||||
or _class_supports_method(provider.__class__, "download_url", Provider.download_url)
|
||||
or _class_supports_method(provider.__class__, "download", Provider.download)
|
||||
)
|
||||
|
||||
|
||||
def _supports_pipe_result_download(provider: Provider) -> bool:
|
||||
return _class_supports_method(
|
||||
provider.__class__,
|
||||
@@ -647,6 +666,8 @@ def _supports_capability(provider: Provider, capability: str) -> bool:
|
||||
return _supports_search(provider)
|
||||
if capability_key in {"upload", "file", "file-provider"}:
|
||||
return _supports_upload(provider)
|
||||
if capability_key in {"download", "download-file", "download_file"}:
|
||||
return _supports_download(provider)
|
||||
if capability_key in {"pipe-download", "pipe_result_download", "pipe-result-download"}:
|
||||
return _supports_pipe_result_download(provider)
|
||||
if capability_key in {"delete-file", "delete_file", "delete"}:
|
||||
@@ -672,6 +693,8 @@ def _info_supports_capability(info: PluginInfo, capability: str) -> bool:
|
||||
return bool(info.supports_search)
|
||||
if capability_key in {"upload", "file", "file-provider"}:
|
||||
return bool(info.supports_upload)
|
||||
if capability_key in {"download", "download-file", "download_file"}:
|
||||
return bool(info.supports_download)
|
||||
if capability_key in {"pipe-download", "pipe_result_download", "pipe-result-download"}:
|
||||
return _class_supports_method(
|
||||
info.plugin_class,
|
||||
@@ -697,62 +720,6 @@ def _info_supports_capability(info: PluginInfo, capability: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
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}
|
||||
return {"value": str(entry), "text": str(entry), "aliases": []}
|
||||
|
||||
|
||||
def _collect_inline_choice_mapping(provider: Provider) -> Dict[str, List[Dict[str, Any]]]:
|
||||
mapping: Dict[str, List[Dict[str, Any]]] = {}
|
||||
|
||||
base = getattr(provider, "QUERY_ARG_CHOICES", None)
|
||||
if not isinstance(base, dict):
|
||||
base = getattr(provider, "INLINE_QUERY_FIELD_CHOICES", None)
|
||||
|
||||
def _merge_from(obj: Any) -> None:
|
||||
if not isinstance(obj, dict):
|
||||
return
|
||||
for key, value in obj.items():
|
||||
normalized: List[Dict[str, Any]] = []
|
||||
seq = value
|
||||
try:
|
||||
if callable(seq):
|
||||
seq = seq()
|
||||
except Exception:
|
||||
seq = value
|
||||
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(key).strip().lower()] = normalized
|
||||
|
||||
_merge_from(base)
|
||||
|
||||
try:
|
||||
fn = getattr(provider, "inline_query_field_choices", None)
|
||||
if callable(fn):
|
||||
_merge_from(fn())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return mapping
|
||||
|
||||
|
||||
def get_plugin(name: str, config: Optional[Dict[str, Any]] = None) -> Optional[Provider]:
|
||||
info = REGISTRY.get(name)
|
||||
if info is None:
|
||||
@@ -838,12 +805,11 @@ def list_configured_plugin_names_with_capability(
|
||||
"""Return plugin names that support `capability` AND have configuration present.
|
||||
|
||||
For MULTI_INSTANCE plugins (e.g. hydrusnetwork, ftp) the plugin must have at
|
||||
least one configured instance. For single-instance plugins the plugin's section
|
||||
must exist under config["plugin"] or config["provider"].
|
||||
least one configured instance. For single-instance plugins the plugin's section
|
||||
must exist under config["plugin"].
|
||||
"""
|
||||
cfg = config or {}
|
||||
plugin_section: Dict[str, Any] = cfg.get("plugin") or {} # type: ignore[assignment]
|
||||
provider_section: Dict[str, Any] = cfg.get("provider") or {} # type: ignore[assignment]
|
||||
|
||||
result: List[str] = []
|
||||
for info in REGISTRY.iter_plugins():
|
||||
@@ -862,7 +828,7 @@ def list_configured_plugin_names_with_capability(
|
||||
pass
|
||||
else:
|
||||
pname = name.lower()
|
||||
if isinstance(plugin_section.get(pname), dict) or isinstance(provider_section.get(pname), dict):
|
||||
if isinstance(plugin_section.get(pname), dict):
|
||||
result.append(name)
|
||||
return sorted(result)
|
||||
|
||||
@@ -890,7 +856,7 @@ def list_plugin_names_for_cmdlet(
|
||||
fallback_capability = {
|
||||
"search-file": "search",
|
||||
"add-file": "upload",
|
||||
"download-file": "search",
|
||||
"download-file": "download",
|
||||
"delete-file": "delete-file",
|
||||
}.get(cmd)
|
||||
|
||||
@@ -905,12 +871,11 @@ def list_plugin_names_for_cmdlet(
|
||||
if fallback_capability:
|
||||
configured.update(list_configured_plugin_names_with_capability(fallback_capability, cfg))
|
||||
|
||||
# Keep cmdlet-declared plugins if they appear configured in plugin/provider sections.
|
||||
# Keep cmdlet-declared plugins if they appear configured in the plugin section.
|
||||
plugin_section: Dict[str, Any] = cfg.get("plugin") or {} # type: ignore[assignment]
|
||||
provider_section: Dict[str, Any] = cfg.get("provider") or {} # type: ignore[assignment]
|
||||
for name in supported:
|
||||
key = str(name or "").strip().lower()
|
||||
if isinstance(plugin_section.get(key), dict) or isinstance(provider_section.get(key), dict):
|
||||
if isinstance(plugin_section.get(key), dict):
|
||||
configured.add(name)
|
||||
|
||||
return sorted(configured)
|
||||
@@ -995,13 +960,13 @@ def plugin_inline_query_choices(
|
||||
mapping: Dict[str, List[Dict[str, Any]]] = {}
|
||||
info = REGISTRY.get(pname)
|
||||
if info is not None:
|
||||
mapping = _collect_inline_choice_mapping(info.plugin_class)
|
||||
mapping = collect_choice(info.plugin_class)
|
||||
|
||||
if not mapping:
|
||||
plugin = get_plugin(pname, config)
|
||||
if plugin is None:
|
||||
return []
|
||||
mapping = _collect_inline_choice_mapping(plugin)
|
||||
mapping = collect_choice(plugin)
|
||||
|
||||
if not mapping:
|
||||
return []
|
||||
@@ -1065,52 +1030,9 @@ def resolve_inline_filters(
|
||||
*,
|
||||
field_transforms: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, str]:
|
||||
"""Map inline query args to provider filter values using declared choices.
|
||||
"""Map inline query args to plugin filter values using the canonical helper."""
|
||||
|
||||
- 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_inline_choice_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
|
||||
return resolve_filter(provider, inline_args, field_transforms=field_transforms)
|
||||
|
||||
|
||||
def clear_plugin_cache() -> None:
|
||||
|
||||
Reference in New Issue
Block a user