From 20ba851a5aaf7490b750c9aec5d9a7328ae7625f Mon Sep 17 00:00:00 2001 From: PierreLapolla Date: Fri, 5 Jun 2026 13:05:01 +0200 Subject: [PATCH 1/3] feat: add IterationSpeedColumn to progress --- rich/progress.py | 41 ++++++++++++++++++++++++++++++++++------- tests/test_progress.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/rich/progress.py b/rich/progress.py index c2de125aec..89bc807d74 100644 --- a/rich/progress.py +++ b/rich/progress.py @@ -734,24 +734,33 @@ def __init__( ) @classmethod - def render_speed(cls, speed: Optional[float]) -> Text: - """Render the speed in iterations per second. + def render_speed( + cls, + speed: Optional[float], + style: StyleType = "progress.percentage", + unit: str = "it", + ) -> Text: + """Render the speed in iterations per second, or seconds per iteration for slow tasks. Args: - task (Task): A Task object. + speed (Optional[float]): Speed in iterations per second, or None if unknown. + style (StyleType, optional): Style to apply. Defaults to "progress.percentage". + unit (str, optional): Unit label for iterations. Defaults to "it". Returns: Text: Text object containing the task speed. """ if speed is None: - return Text("", style="progress.percentage") - unit, suffix = filesize.pick_unit_and_suffix( + return Text("", style=style) + if speed < 1: + return Text(f"{1 / speed:.1f} s/{unit}", style=style) + scale, suffix = filesize.pick_unit_and_suffix( int(speed), ["", "×10³", "×10⁶", "×10⁹", "×10¹²"], 1000, ) - data_speed = speed / unit - return Text(f"{data_speed:.1f}{suffix} it/s", style="progress.percentage") + data_speed = speed / scale + return Text(f"{data_speed:.1f}{suffix} {unit}/s", style=style) def render(self, task: "Task") -> Text: if task.total is None and self.show_speed: @@ -769,6 +778,24 @@ def render(self, task: "Task") -> Text: return text +class IterationSpeedColumn(ProgressColumn): + """Renders iteration speed, e.g. '1.2 it/s', or seconds per iteration for slow tasks, e.g. '2.0 s/it'. + + Args: + unit (str, optional): Unit label for iterations. Defaults to "it". + """ + + def __init__(self, unit: str = "it", table_column: Optional[Column] = None) -> None: + self.unit = unit + super().__init__(table_column=table_column) + + def render(self, task: "Task") -> Text: + speed = task.finished_speed or task.speed + return TaskProgressColumn.render_speed( + speed, style="progress.data.speed", unit=self.unit + ) + + class TimeRemainingColumn(ProgressColumn): """Renders estimated time remaining. diff --git a/tests/test_progress.py b/tests/test_progress.py index 20f426a4cd..a7078f0423 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -14,6 +14,7 @@ BarColumn, DownloadColumn, FileSizeColumn, + IterationSpeedColumn, MofNCompleteColumn, Progress, RenderableColumn, @@ -683,6 +684,34 @@ def test_task_progress_column_speed() -> None: speed_text = TaskProgressColumn.render_speed(8888888) assert speed_text.plain == "8.9×10⁶ it/s" + speed_text = TaskProgressColumn.render_speed(0.5) + assert speed_text.plain == "2.0 s/it" + + speed_text = TaskProgressColumn.render_speed(0.1) + assert speed_text.plain == "10.0 s/it" + + +def test_iteration_speed_column() -> None: + column = IterationSpeedColumn() + task = Task(TaskID(1), "test", 100, 50, _get_time=lambda: 1.0) + + assert column.render(task).plain == "" + + task.finished_speed = 5.0 + assert column.render(task).plain == "5.0 it/s" + + task.finished_speed = 0.5 + assert column.render(task).plain == "2.0 s/it" + + assert column.render(task).style == "progress.data.speed" + + column = IterationSpeedColumn(unit="sample") + task.finished_speed = 5.0 + assert column.render(task).plain == "5.0 sample/s" + + task.finished_speed = 0.5 + assert column.render(task).plain == "2.0 s/sample" + if __name__ == "__main__": _render = render_progress() From b9ded128355157407f9f2e0f0e2587def6814a03 Mon Sep 17 00:00:00 2001 From: PierreLapolla Date: Fri, 5 Jun 2026 13:13:32 +0200 Subject: [PATCH 2/3] docs: update changelog, contributors, and progress docs for IterationSpeedColumn --- CHANGELOG.md | 7 +++++++ CONTRIBUTORS.md | 1 + docs/source/progress.rst | 1 + 3 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b016d4846..4c31646e96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- Added `IterationSpeedColumn` to display iteration speed (e.g. `1.2 it/s`), with tqdm-style inversion for slow tasks (e.g. `2.0 s/it`), and an optional `unit` parameter for custom labels. https://github.com/Textualize/rich/issues/926 +- Enhanced `TaskProgressColumn.render_speed` with slow-task inversion and a `style` parameter. + ## [15.0.0] - 2026-04-12 ### Changed diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2155a42a4d..74e057d79e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -39,6 +39,7 @@ The following people have contributed to the development of Rich: - [Hugo van Kemenade](https://github.com/hugovk) - [Andrew Kettmann](https://github.com/akettmann) - [Alexander Krasnikov](https://github.com/askras) +- [Pierre Lapolla](https://github.com/PierreLapolla) - [Martin Larralde](https://github.com/althonos) - [Hedy Li](https://github.com/hedythedev) - [Henry Mai](https://github.com/tanducmai) diff --git a/docs/source/progress.rst b/docs/source/progress.rst index c6e0bbece9..b5aeacc584 100644 --- a/docs/source/progress.rst +++ b/docs/source/progress.rst @@ -161,6 +161,7 @@ The following column objects are available: - :class:`~rich.progress.TotalFileSizeColumn` Displays total file size (assumes the steps are bytes). - :class:`~rich.progress.DownloadColumn` Displays download progress (assumes the steps are bytes). - :class:`~rich.progress.TransferSpeedColumn` Displays transfer speed (assumes the steps are bytes). +- :class:`~rich.progress.IterationSpeedColumn` Displays iteration speed (e.g. ``1.2 it/s``), or seconds per iteration for slow tasks (e.g. ``2.0 s/it``). Accepts a ``unit`` parameter for custom labels. - :class:`~rich.progress.SpinnerColumn` Displays a "spinner" animation. - :class:`~rich.progress.RenderableColumn` Displays an arbitrary Rich renderable in the column. From 1d5f69775ca6222d346aaf1ae001ca0bc1b3148d Mon Sep 17 00:00:00 2001 From: PierreLapolla Date: Fri, 5 Jun 2026 13:19:47 +0200 Subject: [PATCH 3/3] docs: remove PR link from changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c31646e96..e1ffe09a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added `IterationSpeedColumn` to display iteration speed (e.g. `1.2 it/s`), with tqdm-style inversion for slow tasks (e.g. `2.0 s/it`), and an optional `unit` parameter for custom labels. https://github.com/Textualize/rich/issues/926 +- Added `IterationSpeedColumn` to display iteration speed (e.g. `1.2 it/s`), with tqdm-style inversion for slow tasks (e.g. `2.0 s/it`), and an optional `unit` parameter for custom labels. - Enhanced `TaskProgressColumn.render_speed` with slow-task inversion and a `style` parameter. ## [15.0.0] - 2026-04-12