Code of the Day
IntermediateBetter Interfaces

Rich progress

Add progress bars and spinners to Python CLI tools using rich.progress.track(), Progress(), and Console.status().

UtilitiesIntermediate8 min read
Recommended first
By the end of this lesson you will be able to:
  • Use rich.progress.track() as a one-liner progress bar over any iterable
  • Use the Progress() context manager to add custom columns and multiple tasks
  • Use Console().status() to show a spinner for an operation of unknown duration

Rich gives you three tools for progress feedback, in increasing order of customisation: track(), Progress(), and Console.status(). Start with the simplest that meets your needs.

track() — the one-liner

rich.progress.track() wraps any iterable and displays a progress bar as you consume it. The total is inferred from len() if available, or you can pass it explicitly.

from rich.progress import track
import time

results = []
for item in track(range(100), description="Processing..."):
    time.sleep(0.05)          # simulate work
    results.append(item * 2)

print(f"Done. {len(results)} results.")

That is the entire API. Wrap your iterable, name the description, iterate normally.

Progress() — custom columns

When you need more control — multiple tasks, custom columns, nested bars — use the Progress context manager directly:

from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
import time

with Progress(
    SpinnerColumn(),
    TextColumn("[progress.description]{task.description}"),
    BarColumn(),
    TextColumn("{task.completed}/{task.total}"),
) as progress:
    task = progress.add_task("Scanning files...", total=50)
    for _ in range(50):
        time.sleep(0.05)
        progress.advance(task)

The columns you pass to Progress() are rendered in order. Rich ships with SpinnerColumn, BarColumn, TimeRemainingColumn, FileSizeColumn, and more.

Console.status() — spinner for unknown durations

When the total is unknown, Console().status() shows an animated spinner with a message:

from rich.console import Console
import time

console = Console()
with console.status("[bold]Fetching configuration...[/]"):
    time.sleep(2)             # simulate a network call
print("[bold green]Configuration loaded.[/]")

The spinner runs until the with block exits. The message updates the moment the block ends.

Try it

The demo below simulates a batch processing task. The browser environment does not render live animated bars, but the final output of each approach is shown. Try reading and modifying the code:

Python — editable, runs in your browser

track() and Progress() both re-render on the same terminal lines using ANSI cursor movement. In a log file or a CI environment where output is not a real terminal, Rich falls back to simpler non-animated output automatically.

Where to go next

Next: tables and trees — when tabular or hierarchical output is the right choice, and how to choose between them.

Finished reading? Mark it complete to track your progress.

On this page