huge refactor of the entire codebase, with the goal of improving maintainability, readability, and extensibility. This commit includes changes to almost every file in the project, including:
This commit is contained in:
+126
-79
@@ -1505,9 +1505,9 @@ class PipelineExecutor:
|
||||
"table") else None
|
||||
)
|
||||
|
||||
# Prefer an explicit provider hint from table metadata when available.
|
||||
# Prefer an explicit plugin hint from table metadata when available.
|
||||
# This keeps @N selectors working even when row payloads don't carry a
|
||||
# provider key (or when they carry a table-type like tidal.album).
|
||||
# plugin key (or when they carry a table-type like tidal.album).
|
||||
try:
|
||||
meta = (
|
||||
current_table.get_table_metadata()
|
||||
@@ -1517,56 +1517,58 @@ class PipelineExecutor:
|
||||
except Exception:
|
||||
meta = None
|
||||
if isinstance(meta, dict):
|
||||
_add(meta.get("plugin"))
|
||||
_add(meta.get("provider"))
|
||||
except Exception:
|
||||
logger.exception("Failed to inspect current_table/table metadata in _maybe_run_class_selector")
|
||||
|
||||
for item in selected_items or []:
|
||||
if isinstance(item, dict):
|
||||
_add(item.get("plugin"))
|
||||
_add(item.get("provider"))
|
||||
_add(item.get("store"))
|
||||
_add(item.get("table"))
|
||||
else:
|
||||
_add(getattr(item, "plugin", None))
|
||||
_add(getattr(item, "provider", None))
|
||||
_add(getattr(item, "store", None))
|
||||
_add(getattr(item, "table", None))
|
||||
|
||||
try:
|
||||
from ProviderCore.registry import get_provider, is_known_provider_name
|
||||
from ProviderCore.registry import get_plugin, is_known_plugin_name
|
||||
except Exception:
|
||||
get_provider = None # type: ignore
|
||||
is_known_provider_name = None # type: ignore
|
||||
get_plugin = None # type: ignore
|
||||
is_known_plugin_name = None # type: ignore
|
||||
|
||||
# If we have a table-type like "tidal.album", also try its provider prefix ("tidal")
|
||||
# when that prefix is a registered provider name.
|
||||
if is_known_provider_name is not None:
|
||||
# If we have a table-type like "tidal.album", also try its plugin prefix ("tidal")
|
||||
# when that prefix is a registered plugin name.
|
||||
if is_known_plugin_name is not None:
|
||||
try:
|
||||
for key in list(candidates):
|
||||
if not isinstance(key, str):
|
||||
continue
|
||||
if "." not in key:
|
||||
continue
|
||||
if is_known_provider_name(key):
|
||||
if is_known_plugin_name(key):
|
||||
continue
|
||||
prefix = str(key).split(".", 1)[0].strip().lower()
|
||||
if prefix and is_known_provider_name(prefix):
|
||||
if prefix and is_known_plugin_name(prefix):
|
||||
_add(prefix)
|
||||
except Exception:
|
||||
logger.exception("Failed while computing provider prefix heuristics in _maybe_run_class_selector")
|
||||
logger.exception("Failed while computing plugin prefix heuristics in _maybe_run_class_selector")
|
||||
|
||||
if get_provider is not None:
|
||||
if get_plugin is not None:
|
||||
for key in candidates:
|
||||
try:
|
||||
if is_known_provider_name is not None and (
|
||||
not is_known_provider_name(key)):
|
||||
if is_known_plugin_name is not None and (
|
||||
not is_known_plugin_name(key)):
|
||||
continue
|
||||
except Exception:
|
||||
# If the predicate fails for any reason, fall back to legacy behavior.
|
||||
logger.exception("is_known_provider_name predicate failed for key %s; falling back", key)
|
||||
logger.exception("is_known_plugin_name predicate failed for key %s; falling back", key)
|
||||
try:
|
||||
provider = get_provider(key, config)
|
||||
provider = get_plugin(key, config)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to load provider '%s' during selector resolution: %s", key, exc)
|
||||
logger.exception("Failed to load plugin '%s' during selector resolution: %s", key, exc)
|
||||
continue
|
||||
selector = getattr(provider, "selector", None)
|
||||
if selector is None:
|
||||
@@ -1583,6 +1585,92 @@ class PipelineExecutor:
|
||||
if handled:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _maybe_expand_plugin_selection(
|
||||
selected_items: List[Any],
|
||||
*,
|
||||
ctx: Any,
|
||||
config: Dict[str, Any],
|
||||
stage_table: Any,
|
||||
) -> Optional[List[Any]]:
|
||||
candidates: list[str] = []
|
||||
|
||||
def _add(value: Any) -> None:
|
||||
text = str(value or "").strip().lower()
|
||||
if text and text not in candidates:
|
||||
candidates.append(text)
|
||||
|
||||
table_type = None
|
||||
try:
|
||||
table_type = stage_table.table if stage_table is not None and hasattr(stage_table, "table") else None
|
||||
except Exception:
|
||||
table_type = None
|
||||
_add(table_type)
|
||||
|
||||
try:
|
||||
meta = (
|
||||
stage_table.get_table_metadata()
|
||||
if stage_table is not None and hasattr(stage_table, "get_table_metadata")
|
||||
else getattr(stage_table, "table_metadata", None)
|
||||
)
|
||||
except Exception:
|
||||
meta = None
|
||||
if isinstance(meta, dict):
|
||||
_add(meta.get("plugin"))
|
||||
_add(meta.get("provider"))
|
||||
|
||||
for item in selected_items or []:
|
||||
if isinstance(item, dict):
|
||||
_add(item.get("plugin"))
|
||||
_add(item.get("provider"))
|
||||
_add(item.get("table"))
|
||||
_add(item.get("source"))
|
||||
else:
|
||||
_add(getattr(item, "plugin", None))
|
||||
_add(getattr(item, "provider", None))
|
||||
_add(getattr(item, "table", None))
|
||||
_add(getattr(item, "source", None))
|
||||
|
||||
try:
|
||||
from ProviderCore.registry import get_plugin, is_known_plugin_name
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
for key in list(candidates):
|
||||
if "." in key:
|
||||
prefix = str(key).split(".", 1)[0].strip().lower()
|
||||
if prefix and prefix not in candidates:
|
||||
candidates.append(prefix)
|
||||
|
||||
for key in candidates:
|
||||
try:
|
||||
if not is_known_plugin_name(key):
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
plugin = get_plugin(key, config)
|
||||
except Exception:
|
||||
continue
|
||||
if plugin is None:
|
||||
continue
|
||||
expand = getattr(plugin, "expand_selection", None)
|
||||
if not callable(expand):
|
||||
continue
|
||||
try:
|
||||
expanded = expand(
|
||||
selected_items,
|
||||
ctx=ctx,
|
||||
stage_is_last=False,
|
||||
table_type=str(table_type or ""),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("%s expand_selection failed", key)
|
||||
return None
|
||||
if expanded:
|
||||
return list(expanded)
|
||||
return None
|
||||
|
||||
store_keys: list[str] = []
|
||||
for item in selected_items or []:
|
||||
if isinstance(item, dict):
|
||||
@@ -1998,10 +2086,10 @@ class PipelineExecutor:
|
||||
# IMPORTANT: Put selected row args *before* source_args.
|
||||
# Rationale: The cmdlet argument parser treats the *first* unknown
|
||||
# token as a positional value (e.g., URL). If `source_args`
|
||||
# contain unknown flags (like -provider which download-file does
|
||||
# contain unknown flags (like a removed legacy flag that download-file does
|
||||
# not declare), they could be misinterpreted as the positional
|
||||
# URL argument and cause attempts to download strings like
|
||||
# "-provider" (which is invalid). By placing selection args
|
||||
# not accept). By placing selection args
|
||||
# first we ensure the intended URL/selection token is parsed
|
||||
# as the positional URL and avoid this class of parsing errors.
|
||||
expanded_stage: List[str] = cmd_list + selected_row_args + source_args
|
||||
@@ -2081,66 +2169,15 @@ class PipelineExecutor:
|
||||
print("No items matched selection in pipeline\n")
|
||||
return False, None
|
||||
|
||||
# Provider selection expansion (non-terminal): allow certain provider tables
|
||||
# (e.g. tidal.album) to expand to multiple downstream items when the user
|
||||
# pipes into another stage (e.g. @N | .mpv or @N | add-file).
|
||||
table_type_hint = None
|
||||
try:
|
||||
table_type_hint = (
|
||||
stage_table.table
|
||||
if stage_table is not None and hasattr(stage_table, "table")
|
||||
else None
|
||||
if stages:
|
||||
expanded = PipelineExecutor._maybe_expand_plugin_selection(
|
||||
filtered,
|
||||
ctx=ctx,
|
||||
config=config,
|
||||
stage_table=stage_table,
|
||||
)
|
||||
except Exception:
|
||||
table_type_hint = None
|
||||
|
||||
if stages and isinstance(table_type_hint, str) and table_type_hint.strip().lower() == "tidal.album":
|
||||
try:
|
||||
from ProviderCore.registry import get_provider
|
||||
|
||||
prov = get_provider("tidal", config)
|
||||
except Exception:
|
||||
prov = None
|
||||
|
||||
if prov is not None and hasattr(prov, "_extract_album_selection_context") and hasattr(prov, "_tracks_for_album"):
|
||||
try:
|
||||
album_contexts = prov._extract_album_selection_context(filtered) # type: ignore[attr-defined]
|
||||
except Exception:
|
||||
album_contexts = []
|
||||
|
||||
track_items: List[Any] = []
|
||||
seen_track_ids: set[int] = set()
|
||||
for album_id, album_title, artist_name in album_contexts or []:
|
||||
try:
|
||||
track_results = prov._tracks_for_album( # type: ignore[attr-defined]
|
||||
album_id=album_id,
|
||||
album_title=album_title,
|
||||
artist_name=artist_name,
|
||||
limit=500,
|
||||
)
|
||||
except Exception:
|
||||
track_results = []
|
||||
for tr in track_results or []:
|
||||
try:
|
||||
md = getattr(tr, "full_metadata", None)
|
||||
tid = None
|
||||
if isinstance(md, dict):
|
||||
raw_id = md.get("trackId") or md.get("id")
|
||||
try:
|
||||
tid = int(raw_id) if raw_id is not None else None
|
||||
except Exception:
|
||||
tid = None
|
||||
if tid is not None:
|
||||
if tid in seen_track_ids:
|
||||
continue
|
||||
seen_track_ids.add(tid)
|
||||
except Exception:
|
||||
logger.exception("Failed to extract/parse track metadata in album processing")
|
||||
track_items.append(tr)
|
||||
|
||||
if track_items:
|
||||
filtered = track_items
|
||||
table_type_hint = "tidal.track"
|
||||
if expanded:
|
||||
filtered = expanded
|
||||
|
||||
if PipelineExecutor._maybe_run_class_selector(
|
||||
ctx,
|
||||
@@ -2177,6 +2214,16 @@ class PipelineExecutor:
|
||||
except Exception:
|
||||
logger.exception("Failed to determine current_table for selection auto-insert; defaulting to None")
|
||||
current_table = None
|
||||
table_type_hint = None
|
||||
try:
|
||||
raw_table_type = (
|
||||
stage_table.table
|
||||
if stage_table is not None and hasattr(stage_table, "table") else None
|
||||
)
|
||||
if isinstance(raw_table_type, str) and raw_table_type.strip():
|
||||
table_type_hint = raw_table_type
|
||||
except Exception:
|
||||
table_type_hint = None
|
||||
table_type = None
|
||||
try:
|
||||
if isinstance(table_type_hint, str) and table_type_hint.strip():
|
||||
|
||||
Reference in New Issue
Block a user