73.4 rich: Beautiful Output, Tables, Progress Bars, and Markup
Now, let’s be honest: your command-line tool could be a masterpiece of algorithmic efficiency, but if its output looks like a teletype from the 1970s, people will assume it is from the 1970s. We’ve spent all this time crafting a beautiful, intuitive interface for the user’s input; it’s borderline criminal to neglect their output. This is where rich saunters in, adjusts its cufflinks, and transforms your CLI from a greyscale terminal to a high-resolution console.
rich isn’t just a library; it’s a philosophy. The philosophy is that your terminal deserves nice things. Colors, tables, progress bars, syntax highlighting, markdown rendering—it handles all of it with a shockingly elegant API. It’s the difference between printing Task completed and having a shimmering green checkmark appear next to the words.
Why rich Over Just Printing?
You could spend your afternoon manually inserting ANSI escape codes to color your text. You’d end up with a string that looks like "\033[31mError:\033[0m Something broke", which is hideous to write and impossible to read. rich abstracts this into a sane, object-oriented, and, crucially, measurable system. It knows the capabilities of your terminal, so it won’t try to output truecolor to a device that can’t handle it. It automatically handles rendering and, most importantly, it makes your code expressive. You’re not describing color codes; you’re describing meaning: “this is an error,” “this is a warning,” “this is a success.” rich then maps that meaning to the most appropriate presentation.
The print That Actually Gets You
The simplest way to start is by replacing your boring old print() with rich’s enhanced version.
from rich import print
print("Hello, [bold magenta]World[/bold magenta]!")
print(":skull: [u]This[/u] is not your father's terminal. :skull:")
print("[on red]ERROR:[/on red] [red]The system is down.[/red]")
See that? It uses a simple markup language within the string. [bold magenta] opens a style, and [/bold magenta] closes it. The :skull: becomes an emoji. It’s intuitive and incredibly powerful for sprinkling style into your logs and messages without pulling your hair out.
The Console Object and Logging
For more control, you create a Console object. This is your workhorse. It gives you fine-grained control over where output goes (e.g., to a file, to stderr) and allows for advanced features like capturing output.
from rich.console import Console
console = Console(record=True) # record=True lets us save output as HTML later
console.print("This goes to stdout by default.")
console.log("This is a log message with a timestamp! Magic.", style="bold blue")
# Need to yell at the user?
error_console = Console(stderr=True, style="bold red")
error_console.print("This urgent message goes to stderr, not stdout.")
The console.log() method is a revelation. It automatically includes a timestamp and the calling code’s location (file and line number). It’s an instant, beautiful logging system.
Building Beautiful Tables
This is where rich moves from “nice” to “non-negotiable.” Manually aligning columns with spaces is a fool’s errand. rich tables are a declarative dream.
from rich.console import Console
from rich.table import Table
table = Table(title="Star Wars Trilogy Box Office", show_header=True, header_style="bold magenta")
table.add_column("Film", style="dim", width=20)
table.add_column("Release Year", justify="center")
table.add_column("Adjusted Gross (USD)", justify="right")
table.add_row("A New Hope", "1977", "$1,753,554,035")
table.add_row("The Empire Strikes Back", "1980", "$1,405,102,646")
table.add_row("Return of the Jedi", "1983", "$1,365,256,325")
console = Console()
console.print(table)
You define your columns, their styles, and justification, then add rows. rich handles all the pesky alignment, ensuring everything is perfectly spaced regardless of the content. The justify parameter ("left", "center", "right", "full") is what you always wished str.format had.
Progress Bars That Don’t Suck
A progress bar that just sits there is worse than no progress bar at all. rich.progress is the most comprehensive progress display system I’ve ever used in Python. It handles everything: multiple concurrent tasks, elapsed time, estimated completion, file transfer speeds, and even rendering details about the current task.
from rich.progress import track
import time
# For simple cases, `track` is a drop-in replacement for a loop
for step in track(range(100), description="Processing..."):
time.sleep(0.05) # Simulate work
# For complex tasks, use the Progress context manager
from rich.progress import Progress
with Progress() as progress:
task1 = progress.add_task("[red]Downloading...", total=1000)
task2 = progress.add_task("[green]Processing...", total=500)
while not progress.finished:
progress.update(task1, advance=10)
progress.update(task2, advance=5)
time.sleep(0.05)
The context manager is the way to go for anything non-trivial. It automatically renders a live-updating display with stats for every task. It feels professional because it is.
Rule of Thumb: When to Use rich
Use it everywhere. Seriously. The cost is minimal, and the payoff in usability and polish is immense. The one caveat is if you’re writing a tool that must function in environments where you cannot guarantee a capable terminal (e.g., truly ancient systems or blind users relying on screen readers). In those cases, rich can detect the environment and downgrade its output gracefully to plain text. But for 99% of modern CLI tools, rich is the secret weapon that makes users say, “Wow, this is a pleasure to use.” And that’s the whole point, isn’t it?