rich
Rich is the Python library for making terminal output look good. It gives you a drop-in print replacement with a bbcode-style markup for color and styling, plus ready-made renderables: tables, progress bars, spinners, trees, columns, markdown, syntax-highlighted code, and pretty tracebacks. It auto-detects terminal capability, word-wraps to the terminal width, works in Jupyter notebooks with no configuration, and pretty-prints any Python data structure. It is why so many modern Python CLIs (pip included) look the way they do.
The best-in-class way to make Python terminal output presentable, and the default choice for application CLIs. Skip it inside libraries and machine-facing tools, and reach for Textual once you need interaction rather than display.
Use it if
- You build CLI tools and want tables, progress bars, and colored output without writing ANSI escape codes
- You want readable debugging: rich tracebacks and inspect() beat staring at plain repr output
- You need progress reporting for long jobs, including multiple concurrent bars and spinners
- Your output runs in mixed environments (Linux, macOS, Windows Terminal, Jupyter) and you want capability detection handled for you
- You are writing a library, not an application: forcing a display dependency on your users for a bit of color is poor manners, and logging plus plain text serves them better
- Your output is consumed by machines or log aggregators: styled output must be disabled or stripped there, so if that is your main audience Rich adds surface without payoff
- You need interactive UI (input handling, widgets, screens): that is Textual, Rich's sister project, not Rich
- You are on classic Windows conhost and expect the screenshots: it is limited to 16 colors there per the README
- You count dependencies in a minimal tool: Rich pulls in pygments and markdown-it-py, which is real weight for printing a colored warning
Setup reality
pip install rich and you are running in under a minute; python -m rich demos everything in your own terminal. There is no config. The gotchas are operational: Console(force_terminal=True) or the FORCE_COLOR conventions for CI where you DO want color, remembering markup like [bold] gets parsed in print calls (escape square brackets in user data), and major version bumps that arrive fairly frequently (v15 is current) occasionally adjust rendering details your snapshot tests may notice. Requires Python 3.9+ per current metadata.
Patterns
Drop-in print with color markupstyled-print
from rich import print
print("[bold magenta]Deploy[/bold magenta] finished :rocket:")
print({"status": "ok", "replicas": 3})Square brackets in user-supplied strings are parsed as markup; print untrusted text via rich.markup.escape() or console.print(text, markup=False).
Use a Console for real applicationsconsole-object
from rich.console import Console
console = Console()
console.print("Hello", "World!", style="bold red")
console.print("Where there is a [bold cyan]Will[/bold cyan] there [u]is[/u] a way.")Create one Console at module level and share it; it owns terminal-width detection and color capability for the process.
Render a data tablerender-table
from rich.console import Console
from rich.table import Table
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Date", style="dim", width=12)
table.add_column("Title")
table.add_column("Box Office", justify="right")
table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$375,126,118")
Console().print(table)Columns auto-shrink and wrap to fit the terminal; cells accept any Rich renderable, including nested tables.
Progress bar around any loopprogress-bar
from rich.progress import track
for step in track(range(100), description="Processing..."):
do_step(step)track() needs a sequence with a known length; for unknown totals use Progress with total=None or a status spinner.
Spinner for work with unknown durationstatus-spinner
from rich.console import Console
console = Console()
with console.status("[bold green]Working on tasks...") as status:
for task in tasks:
run(task)
console.log(f"{task} complete")console.log inside the status block prints above the live spinner without breaking it; run python -m rich.spinner to browse spinner styles.
Colorize the standard logging modulelogging-handler
import logging
from rich.logging import RichHandler
logging.basicConfig(
level="INFO",
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler()]
)
logging.getLogger("app").info("Server started")Keep format minimal; RichHandler renders level, time, and caller location itself, so a full format string doubles the noise.
Install pretty tracebacks globallypretty-tracebacks
from rich.traceback import install
install(show_locals=True)
# any uncaught exception now renders with syntax highlighting
1 / 0show_locals prints variable values at each frame, which is great for debugging and a secrets-leak risk in production logs.
Inspect any Python objectinspect-object
from rich import inspect
my_list = ["foo", "bar"]
inspect(my_list, methods=True)inspect(obj, help=True) includes full docstrings; it is a REPL power tool, not something to leave in committed code.
Render Markdown in the terminalrender-markdown
from rich.console import Console
from rich.markdown import Markdown
with open("README.md") as readme:
md = Markdown(readme.read())
Console().print(md)Rendering is approximate by design; tables and headings translate well, raw HTML blocks inside the Markdown do not.
Print syntax-highlighted source codesyntax-highlight
from rich.console import Console
from rich.syntax import Syntax
code = open("app.py").read()
syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
Console().print(syntax)Highlighting comes from pygments, so any pygments lexer name works as the language argument.
Pretty-print everything in the REPLpretty-repl
>>> from rich import pretty
>>> pretty.install()
>>> ["data", {"structures": True}]After install(), every evaluated expression is pretty-printed and highlighted; add it to PYTHONSTARTUP to make it permanent.
Keep color in CI or piped outputforce-color-ci
from rich.console import Console
# CI pipes are not TTYs, so Rich disables color by default
console = Console(force_terminal=True, width=120)
console.print("[green]PASS[/green] 128 tests")Fix the width too: without a TTY Rich cannot detect it and falls back to 80 columns, which mangles wide tables.