← mined_oracle

rich_2166

resolved RESOLVED UNSUBMITTED PASS · None tool calls · 0 s · Textualize/rich

Task input

(not found in data/tasks.jsonl)

Tool calls (0)

#ToolArgumentsResult
No trace captured.

Patch

--- 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:

Test output

show
.....................................                                    [100%]
37 passed in 0.47s