Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
- Enhanced `TaskProgressColumn.render_speed` with slow-task inversion and a `style` parameter.

## [15.0.0] - 2026-04-12

### Changed
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/source/progress.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
41 changes: 34 additions & 7 deletions rich/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.

Expand Down
29 changes: 29 additions & 0 deletions tests/test_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
BarColumn,
DownloadColumn,
FileSizeColumn,
IterationSpeedColumn,
MofNCompleteColumn,
Progress,
RenderableColumn,
Expand Down Expand Up @@ -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()
Expand Down