resolved RESOLVED UNSUBMITTED PASS · None tool calls · 0 s · Textualize/rich
(not found in data/tasks.jsonl)
| # | Tool | Arguments | Result |
|---|---|---|---|
| No trace captured. | |||
--- a/docs/source/progress.rst
+++ b/docs/source/progress.rst
@@ -91,7 +91,7 @@ Transient progress displays are useful if you want more minimal output in the te
Indeterminate progress
~~~~~~~~~~~~~~~~~~~~~~
-When you add a task it is automatically *started*, which means it will show a progress bar at 0% and the time remaining will be calculated from the current time. This may not work well if there is a long delay before you can start updating progress; you may need to wait for a response from a server or count files in a directory (for example). In these cases you can call :meth:`~rich.progress.Progress.add_task` with ``start=False`` which will display a pulsing animation that lets the user know something is working. This is know as an *indeterminate* progress bar. When you have the number of steps you can call :meth:`~rich.progress.Progress.start_task` which will display the progress bar at 0%, then :meth:`~rich.progress.Progress.update` as normal.
+When you add a task it is automatically *started*, which means it will show a progress bar at 0% and the time remaining will be calculated from the current time. This may not work well if there is a long delay before you can start updating progress; you may need to wait for a response from a server or count files in a directory (for example). In these cases you can call :meth:`~rich.progress.Progress.add_task` with ``start=False`` or ``total=None`` which will display a pulsing animation that lets the user know something is working. This is know as an *indeterminate* progress bar. When you have the number of steps you can call :meth:`~rich.progress.Progress.start_task` which will display the progress bar at 0%, then :meth:`~rich.progress.Progress.update` as normal.
Auto refresh
~~~~~~~~~~~~
--- a/rich/progress.py
+++ b/rich/progress.py
@@ -40,7 +40,7 @@ else:
from typing_extensions import Literal # pragma: no cover
from . import filesize, get_console
-from .console import Console, JustifyMethod, RenderableType, Group
+from .console import Console, Group, JustifyMethod, RenderableType
from .highlighter import Highlighter
from .jupyter import JupyterMixin
from .live import Live
@@ -148,7 +148,7 @@ def track(
finished_style=finished_style,
pulse_style=pulse_style,
),
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
+ TaskProgressColumn(),
TimeRemainingColumn(),
)
)
@@ -651,7 +651,7 @@ class BarColumn(ProgressColumn):
def render(self, task: "Task") -> ProgressBar:
"""Gets a progress bar widget for a task."""
return ProgressBar(
- total=max(0, task.total),
+ total=max(0, task.total) if task.total is not None else None,
completed=max(0, task.completed),
width=None if self.bar_width is None else max(1, self.bar_width),
pulse=not task.started,
@@ -675,6 +675,43 @@ class TimeElapsedColumn(ProgressColumn):
return Text(str(delta), style="progress.elapsed")
+class TaskProgressColumn(TextColumn):
+ """A column displaying the progress of a task."""
+
+ def __init__(
+ self,
+ text_format: str = "[progress.percentage]{task.percentage:>3.0f}%",
+ text_format_no_percentage: str = "",
+ style: StyleType = "none",
+ justify: JustifyMethod = "left",
+ markup: bool = True,
+ highlighter: Optional[Highlighter] = None,
+ table_column: Optional[Column] = None,
+ ) -> None:
+ self.text_format_no_percentage = text_format_no_percentage
+ super().__init__(
+ text_format=text_format,
+ style=style,
+ justify=justify,
+ markup=markup,
+ highlighter=highlighter,
+ table_column=table_column,
+ )
+
+ def render(self, task: "Task") -> Text:
+ text_format = (
+ self.text_format_no_percentage if task.total is None else self.text_format
+ )
+ _text = text_format.format(task=task)
+ if self.markup:
+ text = Text.from_markup(_text, style=self.style, justify=self.justify)
+ else:
+ text = Text(_text, style=self.style, justify=self.justify)
+ if self.highlighter:
+ self.highlighter.highlight(text)
+ return text
+
+
class TimeRemainingColumn(ProgressColumn):
"""Renders estimated time remaining.
@@ -705,6 +742,9 @@ class TimeRemainingColumn(ProgressColumn):
task_time = task.time_remaining
style = "progress.remaining"
+ if task.total is None:
+ return Text("", style=style)
+
if task_time is None:
return Text("--:--" if self.compact else "-:--:--", style=style)
@@ -734,7 +774,7 @@ class TotalFileSizeColumn(ProgressColumn):
def render(self, task: "Task") -> Text:
"""Show data completed."""
- data_size = filesize.decimal(int(task.total))
+ data_size = filesize.decimal(int(task.total)) if task.total is not None else ""
return Text(data_size, style="progress.filesize.total")
@@ -757,7 +797,7 @@ class MofNCompleteColumn(ProgressColumn):
def render(self, task: "Task") -> Text:
"""Show completed/total."""
completed = int(task.completed)
- total = int(task.total)
+ total = int(task.total) if task.total is not None else "?"
total_width = len(str(total))
return Text(
f"{completed:{total_width}d}{self.separator}{total}",
@@ -781,24 +821,34 @@ class DownloadColumn(ProgressColumn):
def render(self, task: "Task") -> Text:
"""Calculate common unit for completed and total."""
completed = int(task.completed)
- total = int(task.total)
+
+ unit_and_suffix_calculation_base = (
+ int(task.total) if task.total is not None else completed
+ )
if self.binary_units:
unit, suffix = filesize.pick_unit_and_suffix(
- total,
+ unit_and_suffix_calculation_base,
["bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"],
1024,
)
else:
unit, suffix = filesize.pick_unit_and_suffix(
- total,
+ unit_and_suffix_calculation_base,
["bytes", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
1000,
)
- completed_ratio = completed / unit
- total_ratio = total / unit
precision = 0 if unit == 1 else 1
+
+ completed_ratio = completed / unit
completed_str = f"{completed_ratio:,.{precision}f}"
- total_str = f"{total_ratio:,.{precision}f}"
+
+ if task.total is not None:
+ total = int(task.total)
+ total_ratio = total / unit
+ total_str = f"{total_ratio:,.{precision}f}"
+ else:
+ total_str = "?"
+
download_status = f"{completed_str}/{total_str} {suffix}"
download_text = Text(download_status, style="progress.download")
return download_text
@@ -839,8 +889,8 @@ class Task:
description: str
"""str: Description of the task."""
- total: float
- """str: Total number of steps in this task."""
+ total: Optional[float]
+ """Optional[float]: Total number of steps in this task."""
completed: float
"""float: Number of steps completed"""
@@ -883,8 +933,10 @@ class Task:
return self.start_time is not None
@property
- def remaining(self) -> float:
- """float: Get the number of steps remaining."""
+ def remaining(self) -> Optional[float]:
+ """Optional[float]: Get the number of steps remaining, if a non-None total was set."""
+ if self.total is None:
+ return None
return self.total - self.completed
@property
@@ -903,7 +955,7 @@ class Task:
@property
def percentage(self) -> float:
- """float: Get progress of task as a percentage."""
+ """float: Get progress of task as a percentage. If a None total was set, returns 0"""
if not self.total:
return 0.0
completed = (self.completed / self.total) * 100.0
@@ -936,7 +988,10 @@ class Task:
speed = self.speed
if not speed:
return None
- estimate = ceil(self.remaining / speed)
+ remaining = self.remaining
+ if remaining is None:
+ return None
+ estimate = ceil(remaining / speed)
return estimate
def _reset(self) -> None:
@@ -1027,7 +1082,7 @@ class Progress(JupyterMixin):
return (
TextColumn("[progress.description]{task.description}"),
BarColumn(),
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
+ TaskProgressColumn(),
TimeRemainingColumn(),
)
@@ -1358,7 +1413,11 @@ class Progress(JupyterMixin):
popleft()
if update_completed > 0:
_progress.append(ProgressSample(current_time, update_completed))
- if task.completed >= task.total and task.finished_time is None:
+ if (
+ task.total is not None
+ and task.completed >= task.total
+ and task.finished_time is None
+ ):
task.finished_time = task.elapsed
if refresh:
@@ -1423,7 +1482,11 @@ class Progress(JupyterMixin):
while len(_progress) > 1000:
popleft()
_progress.append(ProgressSample(current_time, update_completed))
- if task.completed >= task.total and task.finished_time is None:
+ if (
+ task.total is not None
+ and task.completed >= task.total
+ and task.finished_time is None
+ ):
task.finished_time = task.elapsed
task.finished_speed = task.speed
@@ -1484,7 +1547,7 @@ class Progress(JupyterMixin):
self,
description: str,
start: bool = True,
- total: float = 100.0,
+ total: Optional[float] = 100.0,
completed: int = 0,
visible: bool = True,
**fields: Any,
@@ -1495,7 +1558,8 @@ class Progress(JupyterMixin):
description (str): A description of the task.
start (bool, optional): Start the task immediately (to calculate elapsed time). If set to False,
you will need to call `start` manually. Defaults to True.
- total (float, optional): Number of total steps in the progress if know. Defaults to 100.
+ total (float, optional): Number of total steps in the progress if known.
+ Set to None to render a pulsing animation. Defaults to 100.
completed (int, optional): Number of steps completed so far.. Defaults to 0.
visible (bool, optional): Enable display of the task. Defaults to True.
**fields (str): Additional data fields required for rendering.
@@ -1585,12 +1649,12 @@ if __name__ == "__main__": # pragma: no coverage
*Progress.get_default_columns(),
TimeElapsedColumn(),
console=console,
- transient=True,
+ transient=False,
) as progress:
task1 = progress.add_task("[red]Downloading", total=1000)
task2 = progress.add_task("[green]Processing", total=1000)
- task3 = progress.add_task("[yellow]Thinking", total=1000, start=False)
+ task3 = progress.add_task("[yellow]Thinking", total=None)
while not progress.finished:
progress.update(task1, advance=0.5)
--- a/rich/progress_bar.py
+++ b/rich/progress_bar.py
@@ -19,10 +19,10 @@ class ProgressBar(JupyterMixin):
"""Renders a (progress) bar. Used by rich.progress.
Args:
- total (float, optional): Number of steps in the bar. Defaults to 100.
+ total (float, optional): Number of steps in the bar. Defaults to 100. Set to None to render a pulsing animation.
completed (float, optional): Number of steps completed. Defaults to 0.
width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
- pulse (bool, optional): Enable pulse effect. Defaults to False.
+ pulse (bool, optional): Enable pulse effect. Defaults to False. Will pulse if a None total was passed.
style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done".
@@ -32,7 +32,7 @@ class ProgressBar(JupyterMixin):
def __init__(
self,
- total: float = 100.0,
+ total: Optional[float] = 100.0,
completed: float = 0,
width: Optional[int] = None,
pulse: bool = False,
@@ -58,8 +58,10 @@ class ProgressBar(JupyterMixin):
return f"<Bar {self.completed!r} of {self.total!r}>"
@property
- def percentage_completed(self) -> float:
+ def percentage_completed(self) -> Optional[float]:
"""Calculate percentage complete."""
+ if self.total is None:
+ return None
completed = (self.completed / self.total) * 100.0
completed = min(100, max(0.0, completed))
return completed
@@ -157,23 +159,29 @@ class ProgressBar(JupyterMixin):
width = min(self.width or options.max_width, options.max_width)
ascii = options.legacy_windows or options.ascii_only
- if self.pulse:
+ should_pulse = self.pulse or self.total is None
+ if should_pulse:
yield from self._render_pulse(console, width, ascii=ascii)
return
- completed = min(self.total, max(0, self.completed))
+ completed: Optional[float] = (
+ min(self.total, max(0, self.completed)) if self.total is not None else None
+ )
bar = "-" if ascii else "━"
half_bar_right = " " if ascii else "╸"
half_bar_left = " " if ascii else "╺"
complete_halves = (
- int(width * 2 * completed / self.total) if self.total else width * 2
+ int(width * 2 * completed / self.total)
+ if self.total and completed is not None
+ else width * 2
)
bar_count = complete_halves // 2
half_bar_count = complete_halves % 2
style = console.get_style(self.style)
+ is_finished = self.total is None or self.completed >= self.total
complete_style = console.get_style(
- self.complete_style if self.completed < self.total else self.finished_style
+ self.finished_style if is_finished else self.complete_style
)
_Segment = Segment
if bar_count:
..................................... [100%] 37 passed in 0.47s