h
This commit is contained in:
@@ -4,6 +4,10 @@ import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
from SYS.logger import log, debug
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple
|
||||
|
||||
@@ -651,7 +655,7 @@ def write_tags(
|
||||
)
|
||||
sidecar = media_path.parent / f"{fallback_base}.tag"
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to determine fallback .tag sidecar base for %s", media_path)
|
||||
|
||||
# Write via consolidated function
|
||||
try:
|
||||
@@ -1258,15 +1262,16 @@ def embed_metadata_in_file(
|
||||
stderr_text = result.stderr.decode("utf-8", errors="replace")[:200]
|
||||
debug(f"FFmpeg stderr: {stderr_text}", file=sys.stderr)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to decode FFmpeg stderr for %s", file_path)
|
||||
return False
|
||||
except Exception as exc:
|
||||
if temp_file.exists():
|
||||
try:
|
||||
temp_file.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to remove FFmpeg temp file %s after error", temp_file)
|
||||
debug(f"❌ Error embedding metadata: {exc}", file=sys.stderr)
|
||||
logger.exception("Error embedding metadata into %s", file_path)
|
||||
return False
|
||||
|
||||
|
||||
@@ -2236,7 +2241,7 @@ def enrich_playlist_entries(entries: list, extractor: str) -> list:
|
||||
enriched.append(full_info)
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to fetch full metadata for entry URL: %s", entry_url)
|
||||
|
||||
# Fallback to original entry if fetch failed
|
||||
enriched.append(entry)
|
||||
@@ -2306,7 +2311,7 @@ def extract_title_from_tags(tags_list: List[str]) -> Optional[str]:
|
||||
if extracted:
|
||||
return extracted
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("extract_title failed while extracting title from tags")
|
||||
|
||||
for t in tags_list:
|
||||
if isinstance(t, str) and t.lower().startswith("title:"):
|
||||
@@ -2563,9 +2568,9 @@ def scrape_url_metadata(
|
||||
}
|
||||
)
|
||||
except json_module.JSONDecodeError:
|
||||
pass
|
||||
logger.debug("Failed to decode flat playlist line %d as JSON: %r", idx, line[:200])
|
||||
except Exception:
|
||||
pass # Silently ignore if we can't get playlist entries
|
||||
logger.exception("yt-dlp flat-playlist extraction failed for URL: %s", url)
|
||||
|
||||
# Fallback: if still no tags detected, get from first item
|
||||
if not tags:
|
||||
@@ -2751,6 +2756,7 @@ def apply_mutagen_metadata(path: Path, metadata: dict[str, str], fmt: str) -> No
|
||||
audio[target_key] = [value]
|
||||
changed = True
|
||||
except Exception: # pragma: no cover - best effort only
|
||||
logger.exception("mutagen: failed to set field %s for %s", target_key, path)
|
||||
continue
|
||||
if not changed:
|
||||
return
|
||||
@@ -2758,6 +2764,7 @@ def apply_mutagen_metadata(path: Path, metadata: dict[str, str], fmt: str) -> No
|
||||
audio.save()
|
||||
except Exception as exc: # pragma: no cover - best effort only
|
||||
log(f"mutagen save failed: {exc}", file=sys.stderr)
|
||||
logger.exception("mutagen save failed for %s", path)
|
||||
|
||||
|
||||
def build_ffmpeg_command(
|
||||
|
||||
@@ -8,7 +8,10 @@ import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
from threading import RLock
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, List, Optional, Protocol, TextIO
|
||||
@@ -474,7 +477,7 @@ class ProgressBar:
|
||||
total=int(total)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to update pipeline UI transfer in ProgressBar._ensure_started")
|
||||
return
|
||||
|
||||
if self._progress is not None and self._task_id is not None:
|
||||
@@ -506,8 +509,8 @@ class ProgressBar:
|
||||
else:
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.exception("Failed to initialize pipeline Live UI integration in ProgressBar._ensure_started")
|
||||
|
||||
stream = file if file is not None else sys.stderr
|
||||
# Use shared stderr console when rendering to stderr (cooperates with PipelineLiveProgress).
|
||||
if stream is sys.stderr:
|
||||
@@ -516,6 +519,7 @@ class ProgressBar:
|
||||
|
||||
console = stderr_console()
|
||||
except Exception:
|
||||
logger.exception("Failed to acquire shared stderr Console from SYS.rich_display; using fallback Console")
|
||||
console = Console(file=stream)
|
||||
else:
|
||||
console = Console(file=stream)
|
||||
@@ -558,7 +562,7 @@ class ProgressBar:
|
||||
int) and total > 0 else None,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to update pipeline UI transfer in ProgressBar.update")
|
||||
return
|
||||
|
||||
if self._progress is None or self._task_id is None:
|
||||
@@ -582,7 +586,7 @@ class ProgressBar:
|
||||
try:
|
||||
self._pipeline_ui.finish_transfer(label=self._pipeline_label)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to finish pipeline UI transfer in ProgressBar.finish")
|
||||
finally:
|
||||
self._pipeline_ui = None
|
||||
self._pipeline_label = None
|
||||
@@ -681,7 +685,7 @@ class ProgressFileReader:
|
||||
# EOF
|
||||
self._finish()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Error while reading and updating ProgressFileReader")
|
||||
return chunk
|
||||
|
||||
def seek(self, offset: int, whence: int = 0) -> Any:
|
||||
@@ -695,7 +699,7 @@ class ProgressFileReader:
|
||||
else:
|
||||
self._read = pos
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to determine file position in ProgressFileReader.seek")
|
||||
return out
|
||||
|
||||
def tell(self) -> Any:
|
||||
@@ -705,7 +709,7 @@ class ProgressFileReader:
|
||||
try:
|
||||
self._finish()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to finish ProgressFileReader progress in close")
|
||||
return self._f.close()
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
@@ -825,6 +829,7 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
value = str(text or "").strip()
|
||||
except Exception:
|
||||
logger.exception("Failed to compute active subtask text in PipelineLiveProgress.set_active_subtask_text")
|
||||
value = ""
|
||||
self._active_subtask_text = value or None
|
||||
|
||||
@@ -1011,10 +1016,11 @@ class PipelineLiveProgress:
|
||||
else:
|
||||
stop_fn()
|
||||
except Exception:
|
||||
logger.exception("Failed to stop Live with clear parameter; retrying without clear")
|
||||
try:
|
||||
stop_fn()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to stop Live on retry")
|
||||
|
||||
self._live = None
|
||||
self._console = None
|
||||
@@ -1043,9 +1049,9 @@ class PipelineLiveProgress:
|
||||
subtasks.stop_task(sub_id)
|
||||
subtasks.update(sub_id, visible=False)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to stop or hide subtask %s in PipelineLiveProgress._hide_pipe_subtasks", sub_id)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to hide pipe subtasks for index %s", pipe_index)
|
||||
|
||||
def set_pipe_status_text(self, pipe_index: int, text: str) -> None:
|
||||
"""Set a status line under the pipe bars for the given pipe."""
|
||||
@@ -1071,20 +1077,21 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
self._hide_pipe_subtasks(pidx)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to hide pipe subtasks while setting status text for pipe %s", pidx)
|
||||
|
||||
task_id = self._status_tasks.get(pidx)
|
||||
if task_id is None:
|
||||
try:
|
||||
task_id = prog.add_task(msg)
|
||||
except Exception:
|
||||
logger.exception("Failed to add status task for pipe %s in set_pipe_status_text", pidx)
|
||||
return
|
||||
self._status_tasks[pidx] = task_id
|
||||
|
||||
try:
|
||||
prog.update(task_id, description=msg, refresh=True)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to update status task %s in set_pipe_status_text", task_id)
|
||||
|
||||
def clear_pipe_status_text(self, pipe_index: int) -> None:
|
||||
if not self._enabled:
|
||||
@@ -1104,7 +1111,7 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
prog.remove_task(task_id)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to remove pipe status task %s in clear_pipe_status_text", task_id)
|
||||
|
||||
def set_pipe_percent(self, pipe_index: int, percent: int) -> None:
|
||||
"""Update the pipe bar as a percent (only when single-item mode is enabled)."""
|
||||
@@ -1127,7 +1134,7 @@ class PipelineLiveProgress:
|
||||
pipe_progress.update(pipe_task, completed=pct, total=100, refresh=True)
|
||||
self._update_overall()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set pipe percent for pipe %s in set_pipe_percent", pipe_index)
|
||||
|
||||
def _update_overall(self) -> None:
|
||||
"""Update the overall pipeline progress task."""
|
||||
@@ -1142,6 +1149,7 @@ class PipelineLiveProgress:
|
||||
if self._pipe_done[i] >= max(1, self._pipe_totals[i])
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to compute completed pipes in _update_overall")
|
||||
completed = 0
|
||||
|
||||
try:
|
||||
@@ -1151,7 +1159,7 @@ class PipelineLiveProgress:
|
||||
description=f"Pipeline: {completed}/{len(self._pipe_labels)} pipes completed",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to update overall pipeline task in _update_overall")
|
||||
|
||||
# Auto-stop Live rendering once all pipes are complete so the progress
|
||||
# UI clears itself even if callers forget to stop it explicitly.
|
||||
@@ -1161,7 +1169,7 @@ class PipelineLiveProgress:
|
||||
if total_pipes > 0 and completed >= total_pipes:
|
||||
self.stop()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to auto-stop Live UI after all pipes completed")
|
||||
|
||||
def begin_pipe_steps(self, pipe_index: int, *, total_steps: int) -> None:
|
||||
"""Initialize step tracking for a pipe.
|
||||
@@ -1187,11 +1195,11 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
self.clear_pipe_status_text(pidx)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to clear pipe status text in begin_pipe_steps for %s", pidx)
|
||||
try:
|
||||
self.set_pipe_percent(pidx, 0)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set initial pipe percent in begin_pipe_steps for %s", pidx)
|
||||
|
||||
def advance_pipe_step(self, pipe_index: int, text: str) -> None:
|
||||
"""Advance the pipe's step counter by one.
|
||||
@@ -1226,14 +1234,14 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
self.set_pipe_status_text(pidx, line)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set pipe status text in advance_pipe_step for pipe %s", pidx)
|
||||
|
||||
# Percent mapping only applies when the pipe is in percent mode (single-item).
|
||||
try:
|
||||
pct = 100 if done >= total else int(round((done / max(1, total)) * 100.0))
|
||||
self.set_pipe_percent(pidx, pct)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set pipe percent in advance_pipe_step for pipe %s", pidx)
|
||||
|
||||
def begin_transfer(self, *, label: str, total: Optional[int] = None) -> None:
|
||||
if not self._enabled:
|
||||
@@ -1247,14 +1255,14 @@ class PipelineLiveProgress:
|
||||
if total is not None and total > 0:
|
||||
self._transfers.update(self._transfer_tasks[key], total=int(total))
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to update existing transfer task total for %s in begin_transfer", key)
|
||||
return
|
||||
task_total = int(total) if isinstance(total, int) and total > 0 else None
|
||||
try:
|
||||
task_id = self._transfers.add_task(key, total=task_total)
|
||||
self._transfer_tasks[key] = task_id
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to add transfer task %s in begin_transfer", key)
|
||||
|
||||
def update_transfer(
|
||||
self,
|
||||
@@ -1282,7 +1290,7 @@ class PipelineLiveProgress:
|
||||
kwargs["total"] = int(total)
|
||||
self._transfers.update(task_id, refresh=True, **kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to update transfer '%s'", key)
|
||||
|
||||
def finish_transfer(self, *, label: str) -> None:
|
||||
if self._transfers is None:
|
||||
@@ -1294,7 +1302,7 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
self._transfers.remove_task(task_id)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to remove transfer task '%s' in finish_transfer", key)
|
||||
|
||||
def _ensure_pipe(self, pipe_index: int) -> bool:
|
||||
if not self._enabled:
|
||||
@@ -1330,12 +1338,12 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
self.clear_pipe_status_text(pipe_index)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to clear pipe status text during begin_pipe for %s", pipe_index)
|
||||
try:
|
||||
self._pipe_step_total.pop(pipe_index, None)
|
||||
self._pipe_step_done.pop(pipe_index, None)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to reset pipe step totals during begin_pipe for %s", pipe_index)
|
||||
|
||||
# If this pipe will process exactly one item, allow percent-based updates.
|
||||
percent_mode = bool(int(total_items) == 1)
|
||||
@@ -1351,7 +1359,7 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
pipe_progress.start_task(pipe_task)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to start pipe task timer in begin_pipe for %s", pipe_index)
|
||||
|
||||
self._update_overall()
|
||||
|
||||
@@ -1386,6 +1394,7 @@ class PipelineLiveProgress:
|
||||
"description",
|
||||
"") or "").strip() or None
|
||||
except Exception:
|
||||
logger.exception("Failed to set active subtask text for first subtask %s in begin_pipe", first)
|
||||
self._active_subtask_text = None
|
||||
|
||||
def on_emit(self, pipe_index: int, emitted: Any) -> None:
|
||||
@@ -1429,7 +1438,7 @@ class PipelineLiveProgress:
|
||||
f"{self._pipe_labels[pipe_index]}: {_pipeline_progress_item_label(emitted)}",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to update subtask description for current %s in on_emit", current)
|
||||
subtasks.stop_task(current)
|
||||
subtasks.update(current, visible=False)
|
||||
|
||||
@@ -1448,12 +1457,12 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
self.clear_pipe_status_text(pipe_index)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to clear pipe status text after emit for %s", pipe_index)
|
||||
try:
|
||||
self._pipe_step_total.pop(pipe_index, None)
|
||||
self._pipe_step_done.pop(pipe_index, None)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to pop pipe step totals after emit for %s", pipe_index)
|
||||
|
||||
# Start next subtask spinner.
|
||||
next_index = active + 1
|
||||
@@ -1468,6 +1477,7 @@ class PipelineLiveProgress:
|
||||
"description",
|
||||
"") or "").strip() or None
|
||||
except Exception:
|
||||
logger.exception("Failed to set active subtask text for next subtask %s in on_emit", nxt)
|
||||
self._active_subtask_text = None
|
||||
else:
|
||||
self._active_subtask_text = None
|
||||
@@ -1504,7 +1514,7 @@ class PipelineLiveProgress:
|
||||
subtasks.stop_task(sub_id)
|
||||
subtasks.update(sub_id, visible=False)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to stop or hide subtask %s during finish_pipe for pipe %s", sub_id, pipe_index)
|
||||
|
||||
# If we just finished the active pipe, clear the title context.
|
||||
self._active_subtask_text = None
|
||||
@@ -1513,19 +1523,19 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
self.clear_pipe_status_text(pipe_index)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to clear pipe status text during finish_pipe for %s", pipe_index)
|
||||
try:
|
||||
self._pipe_step_total.pop(pipe_index, None)
|
||||
self._pipe_step_done.pop(pipe_index, None)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to pop pipe step totals during finish_pipe for %s", pipe_index)
|
||||
|
||||
# Stop the per-pipe timer once the pipe is finished.
|
||||
try:
|
||||
pipe_task = self._pipe_tasks[pipe_index]
|
||||
pipe_progress.stop_task(pipe_task)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to stop pipe task %s during finish_pipe", pipe_index)
|
||||
|
||||
self._update_overall()
|
||||
|
||||
@@ -1537,7 +1547,7 @@ class PipelineLiveProgress:
|
||||
try:
|
||||
self.finish_pipe(idx)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to finish pipe %s in complete_all_pipes", idx)
|
||||
|
||||
|
||||
class PipelineStageContext:
|
||||
@@ -1568,7 +1578,7 @@ class PipelineStageContext:
|
||||
try:
|
||||
cb(obj)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Error in PipelineStageContext.emit callback")
|
||||
|
||||
def get_current_command_text(self) -> str:
|
||||
"""Get the current command text (for backward compatibility)."""
|
||||
|
||||
226
SYS/pipeline.py
226
SYS/pipeline.py
@@ -10,6 +10,8 @@ from contextvars import ContextVar
|
||||
from typing import Any, Dict, List, Optional, Sequence, Callable
|
||||
from SYS.models import PipelineStageContext
|
||||
from SYS.logger import log, debug, is_debug_enabled
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
from SYS.worker import WorkerManagerRegistry, WorkerStages
|
||||
from SYS.cli_parsing import SelectionSyntax, SelectionFilterSyntax
|
||||
from SYS.rich_display import stdout_console
|
||||
@@ -62,7 +64,7 @@ def suspend_live_progress():
|
||||
try:
|
||||
ui.resume()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to resume live progress UI after suspend")
|
||||
|
||||
|
||||
def _is_selectable_table(table: Any) -> bool:
|
||||
@@ -237,7 +239,7 @@ def print_if_visible(*args: Any, file=None, **kwargs: Any) -> None:
|
||||
if should_print:
|
||||
log(*args, **kwargs) if file is None else log(*args, file=file, **kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Error in print_if_visible")
|
||||
|
||||
|
||||
def store_value(key: str, value: Any) -> None:
|
||||
@@ -253,7 +255,7 @@ def store_value(key: str, value: Any) -> None:
|
||||
state = _get_pipeline_state()
|
||||
state.pipeline_values[text] = value
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to store pipeline value '%s'", key)
|
||||
|
||||
|
||||
def load_value(key: str, default: Any = None) -> Any:
|
||||
@@ -330,7 +332,7 @@ def set_pending_pipeline_tail(
|
||||
state.pending_pipeline_source = clean_source if clean_source else None
|
||||
except Exception:
|
||||
# Keep existing pending tail on failure
|
||||
pass
|
||||
logger.exception("Failed to set pending pipeline tail; keeping existing pending tail")
|
||||
|
||||
|
||||
def get_pending_pipeline_tail() -> List[List[str]]:
|
||||
@@ -627,24 +629,9 @@ def set_last_result_table(
|
||||
if result_table.rows and len(sorted_items) == len(result_table.rows):
|
||||
state.last_result_items = sorted_items
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to sort result_table and reorder items")
|
||||
|
||||
|
||||
def set_last_result_table_overlay(
|
||||
result_table: Optional[Any],
|
||||
items: Optional[List[Any]] = None,
|
||||
subject: Optional[Any] = None
|
||||
) -> None:
|
||||
"""
|
||||
Set a result table as an overlay (display only, no history).
|
||||
"""
|
||||
state = _get_pipeline_state()
|
||||
|
||||
state.display_table = result_table
|
||||
state.display_items = items or []
|
||||
state.display_subject = subject
|
||||
|
||||
# Sort table by Title/Name column alphabetically if available
|
||||
if (
|
||||
result_table is not None
|
||||
and hasattr(result_table, "sort_by_title")
|
||||
@@ -662,23 +649,7 @@ def set_last_result_table_overlay(
|
||||
if len(sorted_items) == len(result_table.rows):
|
||||
state.display_items = sorted_items
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def set_last_result_table_preserve_history(
|
||||
result_table: Optional[Any],
|
||||
items: Optional[List[Any]] = None,
|
||||
subject: Optional[Any] = None
|
||||
) -> None:
|
||||
"""
|
||||
Update the last result table WITHOUT adding to history.
|
||||
"""
|
||||
state = _get_pipeline_state()
|
||||
|
||||
# Update current table WITHOUT pushing to history
|
||||
state.last_result_table = result_table
|
||||
state.last_result_items = items or []
|
||||
state.last_result_subject = subject
|
||||
logger.exception("Failed to sort overlay result_table and reorder items")
|
||||
|
||||
|
||||
|
||||
@@ -747,7 +718,7 @@ def restore_previous_result_table() -> bool:
|
||||
try:
|
||||
debug_table_state("restore_previous_result_table")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to debug_table_state during restore_previous_result_table")
|
||||
|
||||
return True
|
||||
|
||||
@@ -805,7 +776,7 @@ def restore_next_result_table() -> bool:
|
||||
try:
|
||||
debug_table_state("restore_next_result_table")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to debug_table_state during restore_next_result_table")
|
||||
|
||||
return True
|
||||
|
||||
@@ -926,7 +897,7 @@ def debug_table_state(label: str = "") -> None:
|
||||
f"history={len(state.result_table_history or [])} forward={len(state.result_table_forward or [])} last_selection={list(state.last_selection or [])}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to debug_table_state buffers summary")
|
||||
|
||||
|
||||
def get_last_selectable_result_items() -> List[Any]:
|
||||
@@ -1133,7 +1104,7 @@ class PipelineExecutor:
|
||||
if self._config_loader is not None:
|
||||
return self._config_loader.load()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to use config_loader.load(); falling back to SYS.config.load_config")
|
||||
try:
|
||||
from SYS.config import load_config
|
||||
|
||||
@@ -1209,7 +1180,7 @@ class PipelineExecutor:
|
||||
if hasattr(ctx, "clear_pipeline_stop"):
|
||||
ctx.clear_pipeline_stop()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to clear pipeline stop via ctx.clear_pipeline_stop")
|
||||
|
||||
@staticmethod
|
||||
def _maybe_seed_current_stage_table(ctx: Any) -> None:
|
||||
@@ -1231,7 +1202,7 @@ class PipelineExecutor:
|
||||
if last_table:
|
||||
ctx.set_current_stage_table(last_table)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to seed current_stage_table from display or last table")
|
||||
|
||||
@staticmethod
|
||||
def _maybe_apply_pending_pipeline_tail(ctx: Any,
|
||||
@@ -1290,13 +1261,13 @@ class PipelineExecutor:
|
||||
if hasattr(ctx, "clear_pending_pipeline_tail"):
|
||||
ctx.clear_pending_pipeline_tail()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to clear pending pipeline tail after appending pending tail")
|
||||
else:
|
||||
try:
|
||||
if hasattr(ctx, "clear_pending_pipeline_tail"):
|
||||
ctx.clear_pending_pipeline_tail()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to clear pending pipeline tail (source mismatch branch)")
|
||||
return stages
|
||||
|
||||
def _apply_quiet_background_flag(self, config: Any) -> Any:
|
||||
@@ -1410,7 +1381,7 @@ class PipelineExecutor:
|
||||
if isinstance(meta, dict):
|
||||
_add(meta.get("provider"))
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to inspect current_table/table metadata in _maybe_run_class_selector")
|
||||
|
||||
for item in selected_items or []:
|
||||
if isinstance(item, dict):
|
||||
@@ -1443,7 +1414,7 @@ class PipelineExecutor:
|
||||
if prefix and is_known_provider_name(prefix):
|
||||
_add(prefix)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed while computing provider prefix heuristics in _maybe_run_class_selector")
|
||||
|
||||
if get_provider is not None:
|
||||
for key in candidates:
|
||||
@@ -1453,7 +1424,7 @@ class PipelineExecutor:
|
||||
continue
|
||||
except Exception:
|
||||
# If the predicate fails for any reason, fall back to legacy behavior.
|
||||
pass
|
||||
logger.exception("is_known_provider_name predicate failed for key %s; falling back", key)
|
||||
try:
|
||||
provider = get_provider(key, config)
|
||||
except Exception:
|
||||
@@ -1511,7 +1482,7 @@ class PipelineExecutor:
|
||||
if handled:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed while running store-based selector logic in _maybe_run_class_selector")
|
||||
|
||||
return False
|
||||
|
||||
@@ -1544,7 +1515,7 @@ class PipelineExecutor:
|
||||
try:
|
||||
worker_manager.append_stdout(worker_id, text + "\n", channel="log")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to append pipeline event to worker stdout for %s", worker_id)
|
||||
|
||||
@staticmethod
|
||||
def _maybe_open_url_selection(
|
||||
@@ -1632,7 +1603,7 @@ class PipelineExecutor:
|
||||
kwargs["output"] = output_fn
|
||||
ensure_background_notifier(worker_manager, **kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to enable background notifier for session_worker_ids=%r", session_worker_ids)
|
||||
|
||||
@staticmethod
|
||||
def _get_raw_stage_texts(ctx: Any) -> List[str]:
|
||||
@@ -1691,7 +1662,7 @@ class PipelineExecutor:
|
||||
if last_table is not None:
|
||||
ctx.set_current_stage_table(last_table)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to sync current_stage_table from display/last table in _maybe_apply_initial_selection")
|
||||
|
||||
source_cmd = None
|
||||
source_args_raw = None
|
||||
@@ -1836,7 +1807,7 @@ class PipelineExecutor:
|
||||
f"@N expansion: {source_cmd} + selected_args={selected_row_args} + source_args={source_args}",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to record pipeline log step for @N expansion (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
|
||||
|
||||
stage_table = None
|
||||
try:
|
||||
@@ -1939,7 +1910,7 @@ class PipelineExecutor:
|
||||
continue
|
||||
seen_track_ids.add(tid)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to extract/parse track metadata in album processing")
|
||||
track_items.append(tr)
|
||||
|
||||
if track_items:
|
||||
@@ -1969,7 +1940,7 @@ class PipelineExecutor:
|
||||
f"Applied @N selection {' | '.join(selection_parts)}",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to record Applied @N selection log step (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
|
||||
|
||||
# Auto-insert downloader stages for provider tables.
|
||||
try:
|
||||
@@ -1979,6 +1950,7 @@ class PipelineExecutor:
|
||||
if current_table is None:
|
||||
current_table = ctx.get_last_result_table()
|
||||
except Exception:
|
||||
logger.exception("Failed to determine current_table for selection auto-insert; defaulting to None")
|
||||
current_table = None
|
||||
table_type = None
|
||||
try:
|
||||
@@ -1990,6 +1962,7 @@ class PipelineExecutor:
|
||||
if current_table and hasattr(current_table, "table") else None
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to compute table_type from current_table; using fallback attribute access")
|
||||
table_type = (
|
||||
current_table.table
|
||||
if current_table and hasattr(current_table, "table") else None
|
||||
@@ -2100,7 +2073,7 @@ class PipelineExecutor:
|
||||
try:
|
||||
print(f"Auto-running selection via {auto_stage[0]}")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to print auto-run selection message for %s", auto_stage[0])
|
||||
# Append the auto stage now. If the user also provided a selection
|
||||
# (e.g., @1 | add-file ...), we want to attach the row selection
|
||||
# args *to the auto-inserted stage* so the download command receives
|
||||
@@ -2137,44 +2110,54 @@ class PipelineExecutor:
|
||||
tail = [str(x) for x in inserted[1:]]
|
||||
stages[-1] = [cmd] + [str(x) for x in row_args] + tail
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to attach selection args to auto-inserted stage")
|
||||
|
||||
# If no auto stage inserted and there are selection-action tokens available
|
||||
# for the single selected row, apply it as the pipeline stage so a bare
|
||||
# `@N` runs the intended action (e.g., get-file for hash-backed rows).
|
||||
if not stages and selection_indices and len(selection_indices) == 1:
|
||||
try:
|
||||
idx = selection_indices[0]
|
||||
debug(f"@N initial selection idx={idx} last_items={len(ctx.get_last_result_items() or [])}")
|
||||
row_action = None
|
||||
try:
|
||||
row_action = ctx.get_current_stage_table_row_selection_action(idx)
|
||||
except Exception:
|
||||
row_action = None
|
||||
|
||||
if not row_action:
|
||||
# If no auto stage inserted and there are selection-action tokens available
|
||||
# for the single selected row, apply it as the pipeline stage so a bare
|
||||
# `@N` runs the intended action (e.g., get-file for hash-backed rows).
|
||||
if not stages and selection_indices and len(selection_indices) == 1:
|
||||
try:
|
||||
items = ctx.get_last_result_items() or []
|
||||
if 0 <= idx < len(items):
|
||||
maybe = items[idx]
|
||||
# Provide explicit debug output about the payload selected
|
||||
try:
|
||||
if isinstance(maybe, dict):
|
||||
debug(f"@N payload: hash={maybe.get('hash')} store={maybe.get('store')} _selection_args={maybe.get('_selection_args')} _selection_action={maybe.get('_selection_action')}")
|
||||
else:
|
||||
debug(f"@N payload object type: {type(maybe).__name__}")
|
||||
except Exception:
|
||||
pass
|
||||
if isinstance(maybe, dict):
|
||||
candidate = maybe.get("_selection_action")
|
||||
if isinstance(candidate, (list, tuple)):
|
||||
row_action = [str(x) for x in candidate if x is not None]
|
||||
debug(f"@N restored row_action from payload: {row_action}")
|
||||
except Exception:
|
||||
idx = selection_indices[0]
|
||||
debug(f"@N initial selection idx={idx} last_items={len(ctx.get_last_result_items() or [])}")
|
||||
row_action = None
|
||||
try:
|
||||
row_action = ctx.get_current_stage_table_row_selection_action(idx)
|
||||
except Exception:
|
||||
logger.exception("Failed to get current_stage_table row selection action for idx %s", idx)
|
||||
row_action = None
|
||||
|
||||
if row_action:
|
||||
debug(f"@N applying row action -> {row_action}")
|
||||
if not row_action:
|
||||
try:
|
||||
items = ctx.get_last_result_items() or []
|
||||
if 0 <= idx < len(items):
|
||||
maybe = items[idx]
|
||||
try:
|
||||
if isinstance(maybe, dict):
|
||||
debug(f"@N payload: hash={maybe.get('hash')} store={maybe.get('store')} _selection_args={maybe.get('_selection_args')} _selection_action={maybe.get('_selection_action')}")
|
||||
else:
|
||||
debug(f"@N payload object type: {type(maybe).__name__}")
|
||||
except Exception:
|
||||
logger.exception("Failed to debug selection payload for index %s", idx)
|
||||
if isinstance(maybe, dict):
|
||||
candidate = maybe.get("_selection_action")
|
||||
if isinstance(candidate, (list, tuple)):
|
||||
row_action = [str(x) for x in candidate if x is not None]
|
||||
except Exception:
|
||||
row_action = None
|
||||
|
||||
if row_action:
|
||||
debug(f"@N applying row action -> {row_action}")
|
||||
stages.append(row_action)
|
||||
if pipeline_session and worker_manager:
|
||||
try:
|
||||
worker_manager.log_step(
|
||||
pipeline_session.worker_id,
|
||||
f"@N applied row action -> {' '.join(row_action)}",
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to record pipeline log step for applied row action (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
|
||||
except Exception:
|
||||
logger.exception("Failed to apply single-row selection action")
|
||||
stages.append(row_action)
|
||||
if pipeline_session and worker_manager:
|
||||
try:
|
||||
@@ -2183,9 +2166,7 @@ class PipelineExecutor:
|
||||
f"@N applied row action -> {' '.join(row_action)}",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to record pipeline log step for applied row action (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
|
||||
else:
|
||||
first_cmd = stages[0][0] if stages and stages[0] else None
|
||||
if isinstance(table_type, str) and table_type.startswith("metadata.") and first_cmd not in (
|
||||
@@ -2234,7 +2215,7 @@ class PipelineExecutor:
|
||||
tail = [str(x) for x in inserted[1:]]
|
||||
stages[0] = [cmd] + [str(x) for x in row_args] + tail
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to attach selection args to inserted auto stage (alternate branch)")
|
||||
|
||||
# After inserting/appending an auto-stage, continue processing so later
|
||||
# selection-expansion logic can still run (e.g., for example selectors).
|
||||
@@ -2304,7 +2285,7 @@ class PipelineExecutor:
|
||||
continue
|
||||
i += 1
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to inspect add-file stage tokens for potential directory; skipping Live progress")
|
||||
if not name:
|
||||
continue
|
||||
# Display-only: avoid Live progress for relationship viewing.
|
||||
@@ -2342,7 +2323,7 @@ class PipelineExecutor:
|
||||
if hasattr(_pipeline_ctx, "set_live_progress"):
|
||||
_pipeline_ctx.set_live_progress(progress_ui)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to register PipelineLiveProgress with pipeline context")
|
||||
pipe_index_by_stage = {
|
||||
stage_idx: pipe_idx
|
||||
for pipe_idx, stage_idx in enumerate(pipe_stage_indices)
|
||||
@@ -2366,7 +2347,7 @@ class PipelineExecutor:
|
||||
if hasattr(ctx, "set_current_stage_table"):
|
||||
ctx.set_current_stage_table(None)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to clear current_stage_table in execute_tokens")
|
||||
|
||||
# Preflight (URL-duplicate prompts, etc.) should be cached within a single
|
||||
# pipeline run, not across independent pipelines.
|
||||
@@ -2374,7 +2355,7 @@ class PipelineExecutor:
|
||||
ctx.store_value("preflight",
|
||||
{})
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set preflight cache in execute_tokens")
|
||||
|
||||
stages = self._split_stages(tokens)
|
||||
if not stages:
|
||||
@@ -2482,7 +2463,7 @@ class PipelineExecutor:
|
||||
try:
|
||||
ctx.set_last_items(pipe_items)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set last items after @ selection")
|
||||
if pipeline_session and worker_manager:
|
||||
try:
|
||||
worker_manager.log_step(
|
||||
@@ -2490,7 +2471,7 @@ class PipelineExecutor:
|
||||
"@ used last result items"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to record pipeline log step for '@ used last result items' (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
|
||||
continue
|
||||
|
||||
subject = ctx.get_last_result_subject()
|
||||
@@ -2505,7 +2486,7 @@ class PipelineExecutor:
|
||||
list) else [subject]
|
||||
ctx.set_last_items(subject_items)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set last_items from subject during @ handling")
|
||||
if pipeline_session and worker_manager:
|
||||
try:
|
||||
worker_manager.log_step(
|
||||
@@ -2513,7 +2494,7 @@ class PipelineExecutor:
|
||||
"@ used current table subject"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to record pipeline log step for '@ used current table subject' (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
|
||||
continue
|
||||
|
||||
if cmd_name.startswith("@"): # selection stage
|
||||
@@ -2550,7 +2531,7 @@ class PipelineExecutor:
|
||||
ctx.set_current_stage_table(display_table)
|
||||
stage_table = display_table
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set current_stage_table from display table during selection processing")
|
||||
|
||||
if not stage_table and display_table is not None:
|
||||
stage_table = display_table
|
||||
@@ -2561,7 +2542,7 @@ class PipelineExecutor:
|
||||
if hasattr(ctx, "debug_table_state"):
|
||||
ctx.debug_table_state(f"selection {selection_token}")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to debug_table_state during selection %s", selection_token)
|
||||
|
||||
if display_table is not None and stage_table is display_table:
|
||||
items_list = ctx.get_last_result_items() or []
|
||||
@@ -2600,9 +2581,9 @@ class PipelineExecutor:
|
||||
try:
|
||||
debug(f"Selection sample object: provider={getattr(sample, 'provider', None)} store={getattr(sample, 'store', None)}")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to debug selection sample object")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to produce selection debug sample for token %s", selection_token)
|
||||
|
||||
if not filtered:
|
||||
print("No items matched selection\n")
|
||||
@@ -2628,14 +2609,14 @@ class PipelineExecutor:
|
||||
if base_table is not None and getattr(base_table, "table", None):
|
||||
new_table.set_table(str(getattr(base_table, "table")))
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set table on new_table for filter overlay")
|
||||
|
||||
try:
|
||||
# Attach a one-line header so users see the active filter.
|
||||
safe = str(selection_token)[1:].strip()
|
||||
new_table.set_header_line(f'filter: "{safe}"')
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set header line for filter overlay for token %s", selection_token)
|
||||
|
||||
for item in filtered:
|
||||
new_table.add_result(item)
|
||||
@@ -2643,15 +2624,15 @@ class PipelineExecutor:
|
||||
try:
|
||||
ctx.set_last_result_table_overlay(new_table, items=list(filtered), subject=ctx.get_last_result_subject())
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to set last_result_table_overlay for filter selection")
|
||||
|
||||
try:
|
||||
stdout_console().print()
|
||||
stdout_console().print(new_table)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to render filter overlay to stdout_console")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed while rendering filter overlay for selection %s", selection_token)
|
||||
continue
|
||||
|
||||
# UX: selecting a single URL row from get-url tables should open it.
|
||||
@@ -2667,7 +2648,7 @@ class PipelineExecutor:
|
||||
stage_is_last=(stage_index + 1 >= len(stages)),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to open URL selection for table %s", getattr(current_table, 'table', None))
|
||||
|
||||
if PipelineExecutor._maybe_run_class_selector(
|
||||
ctx,
|
||||
@@ -2685,6 +2666,7 @@ class PipelineExecutor:
|
||||
).replace("_",
|
||||
"-").lower()
|
||||
except Exception:
|
||||
logger.exception("Failed to determine next_cmd during selection expansion for stage_index %s", stage_index)
|
||||
next_cmd = None
|
||||
|
||||
def _is_tag_row(obj: Any) -> bool:
|
||||
@@ -2696,12 +2678,12 @@ class PipelineExecutor:
|
||||
"tag_name")):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to inspect TagItem object while checking _is_tag_row")
|
||||
try:
|
||||
if isinstance(obj, dict) and obj.get("tag_name"):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to inspect dict tag_name while checking _is_tag_row")
|
||||
return False
|
||||
|
||||
if (next_cmd in {"delete-tag",
|
||||
@@ -2788,7 +2770,7 @@ class PipelineExecutor:
|
||||
try:
|
||||
print(f"Auto-running selection via {auto_stage[0]}")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to print auto-run selection message for %s", auto_stage[0])
|
||||
stages.append(list(auto_stage))
|
||||
else:
|
||||
if auto_stage:
|
||||
@@ -2885,12 +2867,12 @@ class PipelineExecutor:
|
||||
stdout_console().print()
|
||||
stdout_console().print(overlay_table)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to render overlay_table to stdout_console")
|
||||
if session:
|
||||
try:
|
||||
session.close()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to close pipeline stage session")
|
||||
|
||||
except Exception as exc:
|
||||
pipeline_status = "failed"
|
||||
@@ -2907,26 +2889,26 @@ class PipelineExecutor:
|
||||
try:
|
||||
progress_ui.complete_all_pipes()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to complete all pipe UI tasks in progress_ui.complete_all_pipes")
|
||||
try:
|
||||
progress_ui.stop()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to stop progress_ui")
|
||||
try:
|
||||
from SYS import pipeline as _pipeline_ctx
|
||||
if hasattr(_pipeline_ctx, "set_live_progress"):
|
||||
_pipeline_ctx.set_live_progress(None)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to clear live_progress on pipeline context")
|
||||
# Close pipeline session and log final status
|
||||
try:
|
||||
if pipeline_session and worker_manager:
|
||||
pipeline_session.close(status=pipeline_status, error_msg=pipeline_error)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to close pipeline session during finalization")
|
||||
try:
|
||||
if pipeline_session and worker_manager:
|
||||
self._log_pipeline_event(worker_manager, pipeline_session.worker_id,
|
||||
f"Pipeline {pipeline_status}: {pipeline_error or ''}")
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to log final pipeline status (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
|
||||
|
||||
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Iterator, Optional, Sequence, Tuple
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PipelineProgress:
|
||||
@@ -31,6 +33,7 @@ class PipelineProgress:
|
||||
) if hasattr(self._ctx,
|
||||
"get_live_progress") else None
|
||||
except Exception:
|
||||
logger.exception("Failed to get live progress UI from pipeline context")
|
||||
ui = None
|
||||
|
||||
pipe_idx: int = 0
|
||||
@@ -48,6 +51,7 @@ class PipelineProgress:
|
||||
if isinstance(maybe_idx, int):
|
||||
pipe_idx = int(maybe_idx)
|
||||
except Exception:
|
||||
logger.exception("Failed to determine pipe index from stage context")
|
||||
pipe_idx = 0
|
||||
|
||||
return ui, pipe_idx
|
||||
@@ -61,6 +65,7 @@ class PipelineProgress:
|
||||
if callable(begin):
|
||||
begin(int(pipe_idx), total_steps=int(total_steps))
|
||||
except Exception:
|
||||
logger.exception("Failed to call begin_pipe_steps on UI")
|
||||
return
|
||||
|
||||
def step(self, text: str) -> None:
|
||||
@@ -72,6 +77,7 @@ class PipelineProgress:
|
||||
if callable(adv):
|
||||
adv(int(pipe_idx), str(text))
|
||||
except Exception:
|
||||
logger.exception("Failed to advance pipe step on UI")
|
||||
return
|
||||
|
||||
def set_percent(self, percent: int) -> None:
|
||||
@@ -83,6 +89,7 @@ class PipelineProgress:
|
||||
if callable(set_pct):
|
||||
set_pct(int(pipe_idx), int(percent))
|
||||
except Exception:
|
||||
logger.exception("Failed to set pipe percent on UI")
|
||||
return
|
||||
|
||||
def set_status(self, text: str) -> None:
|
||||
@@ -94,6 +101,7 @@ class PipelineProgress:
|
||||
if callable(setter):
|
||||
setter(int(pipe_idx), str(text))
|
||||
except Exception:
|
||||
logger.exception("Failed to set pipe status text on UI")
|
||||
return
|
||||
|
||||
def clear_status(self) -> None:
|
||||
@@ -105,6 +113,7 @@ class PipelineProgress:
|
||||
if callable(clr):
|
||||
clr(int(pipe_idx))
|
||||
except Exception:
|
||||
logger.exception("Failed to clear pipe status text on UI")
|
||||
return
|
||||
|
||||
def begin_transfer(self, *, label: str, total: Optional[int] = None) -> None:
|
||||
@@ -116,6 +125,7 @@ class PipelineProgress:
|
||||
if callable(fn):
|
||||
fn(label=str(label or "transfer"), total=total)
|
||||
except Exception:
|
||||
logger.exception("Failed to begin transfer on UI")
|
||||
return
|
||||
|
||||
def update_transfer(
|
||||
@@ -133,6 +143,7 @@ class PipelineProgress:
|
||||
if callable(fn):
|
||||
fn(label=str(label or "transfer"), completed=completed, total=total)
|
||||
except Exception:
|
||||
logger.exception("Failed to update transfer on UI")
|
||||
return
|
||||
|
||||
def finish_transfer(self, *, label: str) -> None:
|
||||
@@ -144,6 +155,7 @@ class PipelineProgress:
|
||||
if callable(fn):
|
||||
fn(label=str(label or "transfer"))
|
||||
except Exception:
|
||||
logger.exception("Failed to finish transfer on UI")
|
||||
return
|
||||
|
||||
def begin_pipe(
|
||||
@@ -164,6 +176,7 @@ class PipelineProgress:
|
||||
items_preview=list(items_preview or []),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to begin pipe on UI")
|
||||
return
|
||||
|
||||
def on_emit(self, emitted: Any) -> None:
|
||||
@@ -178,6 +191,7 @@ class PipelineProgress:
|
||||
try:
|
||||
self._local_ui.on_emit(0, emitted)
|
||||
except Exception:
|
||||
logger.exception("Failed to call local UI on_emit")
|
||||
return
|
||||
|
||||
def ensure_local_ui(
|
||||
@@ -196,6 +210,7 @@ class PipelineProgress:
|
||||
"get_live_progress") else None
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to check existing live progress from pipeline context")
|
||||
existing = None
|
||||
|
||||
if existing is not None:
|
||||
@@ -213,6 +228,7 @@ class PipelineProgress:
|
||||
self._ctx.set_live_progress(ui)
|
||||
self._local_attached = True
|
||||
except Exception:
|
||||
logger.exception("Failed to attach local UI to pipeline context")
|
||||
self._local_attached = False
|
||||
|
||||
try:
|
||||
@@ -223,11 +239,12 @@ class PipelineProgress:
|
||||
items_preview=list(items_preview or [])
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to begin_pipe on local UI")
|
||||
|
||||
self._local_ui = ui
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception("Failed to create local PipelineLiveProgress UI")
|
||||
self._local_ui = None
|
||||
self._local_attached = False
|
||||
return False
|
||||
@@ -239,18 +256,18 @@ class PipelineProgress:
|
||||
try:
|
||||
self._local_ui.finish_pipe(0, force_complete=bool(force_complete))
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to finish local UI pipe")
|
||||
try:
|
||||
self._local_ui.stop()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to stop local UI")
|
||||
finally:
|
||||
self._local_ui = None
|
||||
try:
|
||||
if self._local_attached and hasattr(self._ctx, "set_live_progress"):
|
||||
self._ctx.set_live_progress(None)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to detach local progress from pipeline context")
|
||||
self._local_attached = False
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -25,6 +25,9 @@ from ProviderCore.base import SearchResult
|
||||
from SYS.html_table import extract_records
|
||||
import lxml.html as lxml_html
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TableProviderMixin:
|
||||
"""Mixin to simplify providers that scrape table/list results from HTML.
|
||||
@@ -56,15 +59,18 @@ class TableProviderMixin:
|
||||
resp = client.get(url)
|
||||
content = resp.content
|
||||
except Exception:
|
||||
logger.exception("Failed to fetch URL %s for provider %s", url, getattr(self, 'name', '<provider>'))
|
||||
return []
|
||||
|
||||
# Ensure we pass an lxml document or string (httpx returns bytes)
|
||||
try:
|
||||
doc = lxml_html.fromstring(content)
|
||||
except Exception:
|
||||
logger.debug("Failed to parse content with lxml; attempting to decode as utf-8", exc_info=True)
|
||||
try:
|
||||
doc = content.decode("utf-8")
|
||||
except Exception:
|
||||
logger.debug("Failed to decode content as utf-8; falling back to str()", exc_info=True)
|
||||
doc = str(content)
|
||||
|
||||
records, chosen = extract_records(doc, base_url=url, xpaths=xpaths or self.DEFAULT_XPATHS)
|
||||
|
||||
@@ -43,6 +43,9 @@ else:
|
||||
# Reuse the existing format_bytes helper under a clearer alias
|
||||
from SYS.utils import format_bytes as format_mb
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _sanitize_cell_text(value: Any) -> str:
|
||||
"""Coerce to a single-line, tab-free string suitable for terminal display."""
|
||||
@@ -82,6 +85,7 @@ def _format_duration_hms(duration: Any) -> str:
|
||||
else:
|
||||
seconds = float(duration)
|
||||
except Exception:
|
||||
logger.debug("Failed to format duration '%s' to hms", duration, exc_info=True)
|
||||
return ""
|
||||
|
||||
if seconds < 0:
|
||||
@@ -118,6 +122,7 @@ class TableColumn:
|
||||
try:
|
||||
return self.extractor(item)
|
||||
except Exception:
|
||||
logger.exception("TableColumn.extract failed for key '%s'", self.key)
|
||||
return None
|
||||
|
||||
|
||||
@@ -137,6 +142,7 @@ def _as_dict(item: Any) -> Optional[Dict[str, Any]]:
|
||||
if hasattr(item, "__dict__"):
|
||||
return dict(getattr(item, "__dict__"))
|
||||
except Exception:
|
||||
logger.exception("Failed to convert %s to dict in _as_dict", type(item))
|
||||
return None
|
||||
return None
|
||||
|
||||
@@ -201,6 +207,7 @@ def extract_ext_value(item: Any) -> str:
|
||||
if suf:
|
||||
ext = suf.lstrip(".")
|
||||
except Exception:
|
||||
logger.debug("Failed to extract suffix from raw_path: %r", raw_path, exc_info=True)
|
||||
ext = ""
|
||||
|
||||
ext_str = str(ext or "").strip().lstrip(".")
|
||||
@@ -242,6 +249,7 @@ def extract_size_bytes_value(item: Any) -> Optional[int]:
|
||||
# Some sources might provide floats or numeric strings
|
||||
return int(float(s))
|
||||
except Exception:
|
||||
logger.debug("Failed to parse size value '%r' to int", size_val, exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
@@ -471,6 +479,7 @@ class Table:
|
||||
"get_current_cmdlet_name") else ""
|
||||
)
|
||||
except Exception:
|
||||
logger.debug("Failed to get current cmdlet name from pipeline context", exc_info=True)
|
||||
cmdlet_name = ""
|
||||
|
||||
stage_text = ""
|
||||
@@ -481,6 +490,7 @@ class Table:
|
||||
"get_current_stage_text") else ""
|
||||
)
|
||||
except Exception:
|
||||
logger.debug("Failed to get current stage text from pipeline context", exc_info=True)
|
||||
stage_text = ""
|
||||
|
||||
if cmdlet_name and stage_text:
|
||||
@@ -494,7 +504,8 @@ class Table:
|
||||
"-").startswith(normalized_cmd):
|
||||
self.title = normalized_stage
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to introspect pipeline context to set ResultTable title")
|
||||
self.title_width = title_width
|
||||
self.max_columns = (
|
||||
max_columns if max_columns is not None else 5
|
||||
@@ -558,6 +569,7 @@ class Table:
|
||||
try:
|
||||
return dict(self.table_metadata)
|
||||
except Exception:
|
||||
logger.exception("Failed to copy table metadata")
|
||||
return {}
|
||||
|
||||
def _interactive(self, interactive: bool = True) -> "Table":
|
||||
@@ -835,7 +847,8 @@ class Table:
|
||||
val = col.format_fn(val)
|
||||
row.add_column(col.header, val)
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to extract column '%s' for row %r", getattr(col, 'header', '<col>'), r)
|
||||
|
||||
return instance
|
||||
|
||||
@@ -913,11 +926,13 @@ class Table:
|
||||
md = getattr(result, "full_metadata", None)
|
||||
md_dict = dict(md) if isinstance(md, dict) else {}
|
||||
except Exception:
|
||||
logger.debug("Failed to extract full_metadata for result of type %s", type(result), exc_info=True)
|
||||
md_dict = {}
|
||||
|
||||
try:
|
||||
selection_args = getattr(result, "selection_args", None)
|
||||
except Exception:
|
||||
logger.debug("Failed to get selection_args from result of type %s", type(result), exc_info=True)
|
||||
selection_args = None
|
||||
if selection_args is None:
|
||||
selection_args = md_dict.get("_selection_args") or md_dict.get("selection_args")
|
||||
@@ -927,6 +942,7 @@ class Table:
|
||||
try:
|
||||
selection_action = getattr(result, "selection_action", None)
|
||||
except Exception:
|
||||
logger.debug("Failed to get selection_action from result of type %s", type(result), exc_info=True)
|
||||
selection_action = None
|
||||
if selection_action is None:
|
||||
selection_action = md_dict.get("_selection_action") or md_dict.get("selection_action")
|
||||
@@ -1084,13 +1100,16 @@ class Table:
|
||||
and "table" not in visible_data and "source" not in visible_data):
|
||||
visible_data["store"] = store_extracted
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to extract store value for item: %r", data)
|
||||
|
||||
try:
|
||||
ext_extracted = extract_ext_value(data)
|
||||
# Always ensure `ext` exists so priority_groups keeps a stable column.
|
||||
visible_data["ext"] = str(ext_extracted or "")
|
||||
except Exception:
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to extract ext value for item: %r", data)
|
||||
visible_data.setdefault("ext", "")
|
||||
|
||||
try:
|
||||
@@ -1099,7 +1118,8 @@ class Table:
|
||||
and "size" not in visible_data):
|
||||
visible_data["size_bytes"] = size_extracted
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to extract size bytes for item: %r", data)
|
||||
|
||||
# Handle extension separation for local files
|
||||
store_val = str(
|
||||
@@ -1168,7 +1188,8 @@ class Table:
|
||||
col_value,
|
||||
integer_only=False
|
||||
)
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
logger.debug("Failed to format 'size' column value: %r", col_value, exc_info=True)
|
||||
col_value_str = format_value(col_value)
|
||||
elif isinstance(col_name,
|
||||
str) and col_name.strip().lower() == "duration":
|
||||
@@ -1178,7 +1199,8 @@ class Table:
|
||||
else:
|
||||
dur = _format_duration_hms(col_value)
|
||||
col_value_str = dur or format_value(col_value)
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
logger.debug("Failed to format 'duration' column value: %r", col_value, exc_info=True)
|
||||
col_value_str = format_value(col_value)
|
||||
else:
|
||||
col_value_str = format_value(col_value)
|
||||
@@ -1201,7 +1223,7 @@ class Table:
|
||||
) # Don't display full metadata as column
|
||||
except Exception:
|
||||
# Fall back to regular field handling if columns format is unexpected
|
||||
pass
|
||||
logger.exception("Failed to process 'columns' dynamic field list: %r", visible_data.get("columns"))
|
||||
|
||||
# Only add priority groups if we haven't already filled columns from 'columns' field
|
||||
if column_count == 0:
|
||||
|
||||
@@ -8,6 +8,9 @@ possible and let callers decide whether to `Console.print()` or capture output.
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from SYS.result_table_api import ColumnSpec, ResultModel, ResultTable, Renderer
|
||||
|
||||
@@ -40,11 +43,13 @@ class RichRenderer(Renderer):
|
||||
if col.format_fn:
|
||||
try:
|
||||
cell = col.format_fn(raw)
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
logger.exception("Column format function failed for '%s': %s", col.header, exc)
|
||||
cell = str(raw or "")
|
||||
else:
|
||||
cell = str(raw or "")
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
logger.exception("Column extractor failed for '%s': %s", col.header, exc)
|
||||
cell = ""
|
||||
cells.append(cell)
|
||||
table.add_row(*cells)
|
||||
|
||||
@@ -29,7 +29,8 @@ try:
|
||||
except TypeError:
|
||||
_pretty_install()
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to configure rich pretty-printing")
|
||||
|
||||
_STDOUT_CONSOLE = Console(file=sys.stdout)
|
||||
_STDERR_CONSOLE = Console(file=sys.stderr)
|
||||
@@ -261,8 +262,10 @@ def render_image_to_console(image_path: str | Path, max_width: int | None = None
|
||||
console.print(line)
|
||||
|
||||
except Exception:
|
||||
# Silently fail if image cannot be rendered (e.g. missing PIL or corrupted file)
|
||||
pass
|
||||
# Emit logs to help diagnose rendering failures (PIL missing, corrupt file, terminal limitations)
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to render image to console: %s", image_path)
|
||||
return
|
||||
|
||||
|
||||
def render_item_details_panel(item: Dict[str, Any], *, title: Optional[str] = None) -> None:
|
||||
@@ -279,7 +282,8 @@ def render_item_details_panel(item: Dict[str, Any], *, title: Optional[str] = No
|
||||
view.title = ""
|
||||
view.header_lines = []
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to sanitize ItemDetailView title/header before printing")
|
||||
|
||||
# We want to print ONLY the elements from ItemDetailView, so we don't use stdout_console().print(view)
|
||||
# as that would include the (empty) results panel.
|
||||
|
||||
@@ -528,6 +528,7 @@ def get_api_key(config: dict[str, Any], service: str, key_path: str) -> str | No
|
||||
|
||||
return None
|
||||
except Exception:
|
||||
_format_logger.exception("Failed to resolve nested config key '%s'", key_path)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Any, Dict, Optional, Set, TextIO, Sequence
|
||||
|
||||
from SYS.config import get_local_storage_path
|
||||
from SYS.worker_manager import WorkerManager
|
||||
from SYS.logger import log
|
||||
|
||||
|
||||
class WorkerOutputMirror(io.TextIOBase):
|
||||
@@ -69,7 +70,8 @@ class WorkerOutputMirror(io.TextIOBase):
|
||||
try:
|
||||
self._manager.append_stdout(self._worker_id, text, channel=self._channel)
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to append stdout for worker '%s' channel '%s'", self._worker_id, self._channel)
|
||||
|
||||
@property
|
||||
def encoding(self) -> str: # type: ignore[override]
|
||||
@@ -112,7 +114,8 @@ class WorkerStageSession:
|
||||
self.stdout_proxy.flush()
|
||||
self.stderr_proxy.flush()
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to flush worker stdout/stderr proxies for '%s'", self.worker_id)
|
||||
|
||||
sys.stdout = self.orig_stdout
|
||||
sys.stderr = self.orig_stderr
|
||||
@@ -121,7 +124,8 @@ class WorkerStageSession:
|
||||
try:
|
||||
self.manager.disable_logging_for_worker(self.worker_id)
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to disable logging for worker '%s'", self.worker_id)
|
||||
|
||||
try:
|
||||
if status == "completed":
|
||||
@@ -131,14 +135,16 @@ class WorkerStageSession:
|
||||
self.worker_id, f"{self._error_label}: {error_msg or status}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to log step for worker '%s' status='%s' error='%s'", self.worker_id, status, error_msg)
|
||||
|
||||
try:
|
||||
self.manager.finish_worker(
|
||||
self.worker_id, result=status or "completed", error_msg=error_msg or ""
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to finish worker '%s' with status '%s'", self.worker_id, status)
|
||||
|
||||
if self.config and self.config.get("_current_worker_id") == self.worker_id:
|
||||
self.config.pop("_current_worker_id", None)
|
||||
@@ -177,7 +183,8 @@ class WorkerManagerRegistry:
|
||||
try:
|
||||
cls._manager.close()
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to close existing WorkerManager during registry ensure")
|
||||
cls._manager = WorkerManager(resolved_root, auto_refresh_interval=0.5)
|
||||
cls._manager_root = resolved_root
|
||||
|
||||
@@ -192,7 +199,8 @@ class WorkerManagerRegistry:
|
||||
reason="CLI session ended unexpectedly; marking worker as failed",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to expire running workers during registry ensure")
|
||||
else:
|
||||
cls._orphan_cleanup_done = True
|
||||
|
||||
@@ -202,7 +210,7 @@ class WorkerManagerRegistry:
|
||||
|
||||
return manager
|
||||
except Exception as exc:
|
||||
print(f"[worker] Could not initialize worker manager: {exc}", file=sys.stderr)
|
||||
log(f"[worker] Could not initialize worker manager: {exc}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
@@ -212,7 +220,8 @@ class WorkerManagerRegistry:
|
||||
try:
|
||||
cls._manager.close()
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to close WorkerManager during registry.close()")
|
||||
cls._manager = None
|
||||
cls._manager_root = None
|
||||
cls._orphan_cleanup_done = False
|
||||
@@ -254,7 +263,7 @@ class WorkerStages:
|
||||
if not tracked:
|
||||
return None
|
||||
except Exception as exc:
|
||||
print(f"[worker] Failed to track {worker_type}: {exc}", file=sys.stderr)
|
||||
log(f"[worker] Failed to track {worker_type}: {exc}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
if session_worker_ids is not None:
|
||||
@@ -279,7 +288,8 @@ class WorkerStages:
|
||||
try:
|
||||
worker_manager.log_step(worker_id, f"Started {worker_type}")
|
||||
except Exception:
|
||||
pass
|
||||
from SYS.logger import logger
|
||||
logger.exception("Failed to log start step for worker '%s'", worker_id)
|
||||
|
||||
return WorkerStageSession(
|
||||
manager=worker_manager,
|
||||
|
||||
@@ -400,7 +400,7 @@ class WorkerManager:
|
||||
try:
|
||||
self.flush_worker_stdout(worker_id)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to flush worker stdout for '%s'", worker_id)
|
||||
|
||||
logger.debug(
|
||||
f"[WorkerManager] Disabled logging for worker: {worker_id}"
|
||||
@@ -516,7 +516,7 @@ class WorkerManager:
|
||||
try:
|
||||
self.flush_worker_stdout(worker_id)
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to flush worker stdout for '%s' during finish", worker_id)
|
||||
kwargs = {
|
||||
"status": "finished",
|
||||
"result": result
|
||||
@@ -900,7 +900,7 @@ class WorkerManager:
|
||||
try:
|
||||
self._flush_all_stdout_buffers()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("Failed to flush all stdout buffers during WorkerManager.close()")
|
||||
logger.info("[WorkerManager] Closed")
|
||||
|
||||
def _flush_all_stdout_buffers(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user