This commit is contained in:
2026-01-31 19:00:04 -08:00
parent dcf16e0cc4
commit 6513a3ad04
25 changed files with 617 additions and 397 deletions

View File

@@ -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)."""