This commit is contained in:
2026-01-15 00:45:42 -08:00
parent ac10e607bb
commit 3a02a52863
5 changed files with 837 additions and 784 deletions

View File

@@ -7,6 +7,7 @@ import os
import shutil
import sys
import time
from threading import RLock
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Protocol, TextIO
@@ -755,6 +756,7 @@ class PipelineLiveProgress:
def __init__(self, pipe_labels: List[str], *, enabled: bool = True) -> None:
self._enabled = bool(enabled)
self._pipe_labels = [str(x) for x in (pipe_labels or [])]
self._lock = RLock()
self._console: Optional[Console] = None
self._live: Optional[Live] = None
@@ -826,26 +828,27 @@ class PipelineLiveProgress:
the spinner without needing manual Live.update() calls.
"""
pipe_progress = self._pipe_progress
status = self._status
transfers = self._transfers
overall = self._overall
if pipe_progress is None or transfers is None or overall is None:
# Not started (or stopped).
yield Panel("", title="Pipeline", expand=False)
return
with self._lock:
pipe_progress = self._pipe_progress
status = self._status
transfers = self._transfers
overall = self._overall
if pipe_progress is None or transfers is None or overall is None:
# Not started (or stopped).
yield Panel("", title="Pipeline", expand=False)
return
body_parts: List[Any] = [pipe_progress]
if status is not None and self._status_tasks:
body_parts.append(status)
body_parts.append(transfers)
body_parts: List[Any] = [pipe_progress]
if status is not None and self._status_tasks:
body_parts.append(status)
body_parts.append(transfers)
yield Group(
Panel(Group(*body_parts),
title=self._title_text(),
expand=False),
overall
)
yield Group(
Panel(Group(*body_parts),
title=self._title_text(),
expand=False),
overall
)
def _render_group(self) -> Group:
# Backward-compatible helper (some callers may still expect a Group).
@@ -1029,52 +1032,58 @@ class PipelineLiveProgress:
return
if not self._ensure_pipe(int(pipe_index)):
return
prog = self._status
if prog is None:
return
with self._lock:
prog = self._status
if prog is None:
return
try:
pidx = int(pipe_index)
msg = str(text or "").strip()
except Exception:
return
# For long single-item work, hide the per-item spinner line and use this
# dedicated status line instead.
if self._pipe_percent_mode.get(pidx, False):
try:
self._hide_pipe_subtasks(pidx)
pidx = int(pipe_index)
msg = str(text or "").strip()
except Exception:
return
# For long single-item work, hide the per-item spinner line and use this
# dedicated status line instead.
if self._pipe_percent_mode.get(pidx, False):
try:
self._hide_pipe_subtasks(pidx)
except Exception:
pass
task_id = self._status_tasks.get(pidx)
if task_id is None:
try:
task_id = prog.add_task(msg)
except Exception:
return
self._status_tasks[pidx] = task_id
try:
prog.update(task_id, description=msg, refresh=True)
except Exception:
pass
task_id = self._status_tasks.get(pidx)
if task_id is None:
def clear_pipe_status_text(self, pipe_index: int) -> None:
if not self._enabled:
return
with self._lock:
prog = self._status
if prog is None:
return
try:
task_id = prog.add_task(msg)
pidx = int(pipe_index)
except Exception:
return
self._status_tasks[pidx] = task_id
try:
prog.update(task_id, description=msg, refresh=True)
except Exception:
pass
def clear_pipe_status_text(self, pipe_index: int) -> None:
prog = self._status
if prog is None:
return
try:
pidx = int(pipe_index)
except Exception:
return
task_id = self._status_tasks.pop(pidx, None)
if task_id is None:
return
try:
prog.remove_task(task_id)
except Exception:
pass
task_id = self._status_tasks.pop(pidx, None)
if task_id is None:
return
try:
prog.remove_task(task_id)
except Exception:
pass
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)."""
@@ -1095,6 +1104,31 @@ class PipelineLiveProgress:
pct = max(0, min(100, int(percent)))
pipe_task = self._pipe_tasks[pidx]
pipe_progress.update(pipe_task, completed=pct, total=100, refresh=True)
self._update_overall()
except Exception:
pass
def _update_overall(self) -> None:
"""Update the overall pipeline progress task."""
if self._overall is None or self._overall_task is None:
return
completed = 0
try:
# Count a pipe as completed if its 'done' count matches or exceeds the advertised total.
completed = sum(
1 for i in range(len(self._pipe_labels))
if self._pipe_done[i] >= max(1, self._pipe_totals[i])
)
except Exception:
completed = 0
try:
self._overall.update(
self._overall_task,
completed=min(completed, max(1, len(self._pipe_labels))),
description=f"Pipeline: {completed}/{len(self._pipe_labels)} pipes completed",
)
except Exception:
pass
@@ -1108,24 +1142,25 @@ class PipelineLiveProgress:
if not self._ensure_pipe(int(pipe_index)):
return
try:
pidx = int(pipe_index)
tot = max(1, int(total_steps))
except Exception:
return
with self._lock:
try:
pidx = int(pipe_index)
tot = max(1, int(total_steps))
except Exception:
return
self._pipe_step_total[pidx] = tot
self._pipe_step_done[pidx] = 0
self._pipe_step_total[pidx] = tot
self._pipe_step_done[pidx] = 0
# Reset status line and percent.
try:
self.clear_pipe_status_text(pidx)
except Exception:
pass
try:
self.set_pipe_percent(pidx, 0)
except Exception:
pass
# Reset status line and percent.
try:
self.clear_pipe_status_text(pidx)
except Exception:
pass
try:
self.set_pipe_percent(pidx, 0)
except Exception:
pass
def advance_pipe_step(self, pipe_index: int, text: str) -> None:
"""Advance the pipe's step counter by one.
@@ -1287,6 +1322,8 @@ class PipelineLiveProgress:
except Exception:
pass
self._update_overall()
labels: List[str] = []
if isinstance(items_preview, list) and items_preview:
labels = [_pipeline_progress_item_label(x) for x in items_preview]
@@ -1372,6 +1409,8 @@ class PipelineLiveProgress:
else:
pipe_progress.update(pipe_task, completed=done)
self._update_overall()
# Clear any status line now that it emitted.
try:
self.clear_pipe_status_text(pipe_index)
@@ -1452,23 +1491,7 @@ class PipelineLiveProgress:
except Exception:
pass
if self._overall_task is not None:
completed = 0
try:
completed = sum(
1 for i in range(len(self._pipe_labels))
if self._pipe_done[i] >= max(1, self._pipe_totals[i])
)
except Exception:
completed = 0
overall.update(
self._overall_task,
completed=min(completed,
max(1,
len(self._pipe_labels))),
description=
f"Pipeline: {completed}/{len(self._pipe_labels)} pipes completed",
)
self._update_overall()
class PipelineStageContext: