Back to Blog
Python

Python Rich Progress Bars, Spinners, and Status

python rich progress bars spinners and status: Learn how to add progress bars, spinners, and status messages to Python CLI apps using the Rich library, with practical...

Richprogress barsspinnersterminal UICLI
Illustration of a Python terminal with progress bars, spinners, and status indicators.

When a Python command-line tool runs a long operation, users need feedback. The Rich library provides three primary mechanisms for that feedback: progress bars, spinners, and status messages. This article shows how to use python rich progress bars spinners and status in real CLI applications, with working examples and configuration details.

What Rich Provides for Progress Indication

Rich offers three distinct components for displaying progress in a terminal:

  • Progress bars for tasks with known total work or a defined number of steps.
  • Spinners for indeterminate operations where the completion point is unknown.
  • Status messages for showing a short-lived message while a task runs.

Each component is rendered by Rich's live display engine, which redraws the terminal output in place. This keeps the interface clean and avoids flooding the console with repeated lines.

The core classes are Progress, Spinner, and Status, all available from the rich package. You can use them independently or combine them to match the nature of the operation you are reporting.

Building a Basic Progress Bar

The Progress class is the most common way to show a determinate task. You create an instance, add a task with a known total, and update the task's progress as work completes.

from rich.progress import Progress import time with Progress() as progress: task = progress.add_task("Processing files", total=100) for i in range(100): time.sleep(0.02) progress.update(task, advance=1)

The add_task method returns a task ID, which you use in update to advance the progress. The advance parameter increments the completed amount. When the task reaches the total, the bar fills completely and the task is marked as finished.

The with block ensures the live display starts and stops correctly. Without it, you would need to call start() and stop() manually.

If you have a simple loop over a known iterable, Rich provides the track convenience function that wraps an iterable and displays a progress bar automatically.

from rich.progress import track import time for item in track(range(100), description="Processing"): time.sleep(0.02)

track is ideal for quick scripts where you do not need fine-grained control over the task ID.

Customizing Progress Bar Columns

By default, a progress bar shows a description, a bar, a percentage, and a time estimate. You can change the columns to show different information, such as transfer speed, file count, or a custom field.

Rich uses the Progress constructor's columns parameter to accept a list of column objects. For example, to show the current file name and a transfer speed:

from rich.progress import Progress, BarColumn, TextColumn, TransferSpeedColumn progress = Progress( TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), TransferSpeedColumn(), )

Each column can be configured with format strings and styles. The task object inside the format string exposes fields like description, percentage, completed, total, and elapsed.

You can also set the bar width, change the bar character, or add a spinner inside the bar. The following table lists common column classes and their purpose:

Column classPurpose
TextColumnDisplay a formatted text field
BarColumnRender the visual progress bar
PercentageColumnShow the percentage complete
TimeElapsedColumnShow time elapsed since the task started
TimeRemainingColumnShow estimated time remaining
TransferSpeedColumnShow bytes per second (for file transfers)

These columns can be mixed and matched to fit the information your users need.

Using Spinners for Indeterminate Work

When you cannot know how long an operation will take, a spinner provides visual feedback without implying a completion percentage. Rich's Spinner class renders an animated character that cycles through a set of frames.

from rich.spinner import Spinner from rich.console import Console import time console = Console() with console.status("Working..."): time.sleep(3)

The console.status context manager is a convenient wrapper that shows a spinner with a message. It is perfect for operations that do not need a progress bar, such as connecting to a remote service or parsing a large file.

If you need more control, you can create a Spinner instance directly and render it in a loop:

from rich.spinner import Spinner from rich.console import Console import time console = Console() spinner = Spinner("dots", text="Loading") with console.live(spinner): for _ in range(10): time.sleep(0.1)

Rich includes many spinner styles, such as dots, line, arrow, and aesthetic. You can list them with rich.spinner.SPINNERS or by running python -m rich.spinner in the terminal.

Showing Status Messages

The Status class is similar to a spinner but is designed for short-lived messages that disappear when the operation completes. It is often used to indicate that a step is in progress without permanently occupying the terminal.

from rich.console import Console import time console = Console() with console.status("Uploading...") as status: time.sleep(2) status.update("Uploaded, now verifying...") time.sleep(2)

The status.update method lets you change the message while the operation runs. This is useful for multi-stage processes where you want to inform the user of the current step.

Unlike a progress bar, a status message does not show a percentage or a bar. It is best used for operations that are too short or too unpredictable to warrant a progress bar.

Handling Nested and Concurrent Tasks

The Progress class supports multiple tasks running concurrently. You can add several tasks and update them independently, which is useful for parallel downloads or processing multiple files.

from rich.progress import Progress import time with Progress() as progress: task1 = progress.add_task("Task A", total=100) task2 = progress.add_task("Task B", total=100) for i in range(100): time.sleep(0.01) progress.update(task1, advance=1) progress.update(task2, advance=1)

Rich also allows nesting progress bars. You can create a Progress instance and add a child progress bar as a column, or use the ProgressGroup class to display multiple progress bars stacked vertically.

When using threads or asyncio, you must ensure that updates to the progress bar are thread-safe. Rich's Progress is not thread-safe by default; you should use the Progress instance from a single thread or use a lock. For asyncio, you can use rich.progress.Progress with asyncio by calling await progress.start() and await progress.stop().

Performance and Refresh Overhead

Every time you call progress.update, Rich redraws the progress bar. If you update too frequently, the terminal may become sluggish, especially with many tasks or complex columns. Rich mitigates this by using a refresh interval, which defaults to 0.1 seconds. Updates that occur faster than the refresh interval are batched and rendered only once per interval.

You can adjust the refresh rate with the refresh_per_second parameter in the Progress constructor. For very fast operations, you might want to lower the refresh rate to reduce CPU usage. For animations that need to be smooth, you can increase it, but be mindful of the overhead.

progress = Progress(refresh_per_second=30)

In practice, the overhead of a progress bar is negligible compared to the actual work in most CLI tools. The main concern is avoiding excessive updates in tight loops that do not do significant work.

Terminal Compatibility and Non-Interactive Output

Rich detects whether the output is a terminal. When the output is redirected to a file or piped to another program, Rich disables the live display and falls back to printing the final state. This is important for scripts that are run in CI pipelines or with cron.

For progress bars, this means that in non-interactive mode, you might see the final line printed once instead of an animated bar. You can control this behavior with the console parameter or by using the force_terminal option, but it is usually best to let Rich decide automatically.

Spinners and status messages also degrade gracefully. In a non-terminal context, they simply print the message once without animation. This ensures that your logs remain readable and do not contain escape sequences.

If you are integrating Rich with a logging framework, be aware that the live display can interfere with log output. A common pattern is to use rich.logging.RichHandler to keep log messages and progress bars separate. Rich's live display will pause while a log message is written, then resume, preventing interleaving.

When building a CLI that must work in both interactive and non-interactive environments, test both modes. You can simulate a non-terminal by piping the output to cat or redirecting to a file.

Rich's progress, spinner, and status components are powerful tools for improving the user experience of Python command-line applications. By understanding their configuration options and runtime behavior, you can add clear, responsive feedback to any long-running task.

python rich progress bars spinners and status: Practical Usa | RYUSLOG DEV