Better Python console apps with Rich


Recently I’ve been working on a few internal tools for my company, Teclado. Things like web scrapers to collect data and store it in a database, or simple tools to make people’s jobs easier.

Because these are intended for a very small audience who are doing very specific tasks, I normally make console applications. And console applications can be tricky to use (and look hideous) most of the time.

Rich is a Python library that changes that completely. My new console apps are a joy to use, look great, and it’s super easy to create them.

In this post, let me tell you about Rich and how I’ve been using it. I’m not a Rich expert by any means, but I hope it helps!

A Rich Example

The Rich documentation is excellent, so I’ll just give you a quick example of the sorts of things you can do with it.

from rich import print
from rich.prompt import Prompt

name = Prompt.ask(
    "What is your name?",
    choices=["Jose", "Adam", "Jen"]
)
# Get the person's data from an API
helped = 50  # This would come from the API
print(f"You have helped [bold green]{helped}[/] students this week! :muscle:")

The output of this code is this:

Terminal demo of the Prompt code

Sadly, Windows support for emojis in the terminal is extremely subpar. It works well on Mac, though!

You can see that Rich does a lot for you already, such as limiting choices in prompts, or support for colours and styles in text using a BBCode-like syntax.

There’s more though!

Tables with Rich

By far one of my favourite features of Rich is Tables. I’m using tables for all sorts of stuff in my console apps, and I find myself doing the same pattern over and over:

  • Clear the console
  • Add a table row
  • Draw the table

With this pattern, tables can look like they’re growing over time, effectively making your console window into a standalone app.

Terminal demo of growing tables

For example, recently I was writing a scraper for house prices nearby (I’m thinking of moving), and I naturally wanted to display the house information, price, and so forth. Nothing better than a table for this, so I got to writing:

import requests
from bs4 import BeautifulSoup
from rich.console import Console
from rich.table import Table

console = Console()
page = requests.get(RIGHTMOVE_URL).content
soup = BeautifulSoup(page)
# ... Get properties as a list ...

property_table = Table()
property_table.add_column("Address", style="bold")
property_table.add_column("Rooms")
property_table.add_column("Price")

for property in properties:
    property_table.add_row(property[0], str(property[1]), str(property[2]))

console.print(property_table)

That’s how easy it is to draw a table using Rich! It doesn’t have dynamic growing, but all you have to do to make a table grow dynamically is clear the console and re-create the table each time, with increasing numbers of rows.

That could be particularly useful if we had to scrape each property’s page to get more information. If we did that, the table could grow as we scrape, which would be awesome!

Adapting Classes to Rich

Another great thing about Rich (especially if you’re making applications that specifically output to a Rich Console), is that your classes can become Renderable. Then you can just print them, and Rich will display them in the format you choose.

For example, you could make a class like this one:

from rich.console import Console, ConsoleOptions, RenderResult
from rich.table import Table


class PropertyTable:
    def __init__(self):
        self._properties = []

    def __len__(self):
        return len(self._properties)

    def append(self, prop):
        self._properties.append(prop)

    def pop(self):
        return self._properties.pop()

    def clear(self):
        self._properties.clear()

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        properties = Table(expand=True)
        properties.add_column("Address")
        properties.add_column("Rooms", style="bold")
        properties.add_column("Price", style="bold green")

        for prop in self._properties:
            prop.add_row(prop[0], str(prop[1]), str(prop[2]))

        yield properties

And when printed using a Rich Console, that shows a table with a row per item in the _properties list.

Better Logging and Tracebacks with Rich

Rich can do many other things for you, but another good one is that it can make it easier to read tracebacks and errors. Just do this:

from rich.traceback import install
install()

And from then on, your tracebacks will look like this:

Screenshot of Rich tracebacks

Image taken directly from the Rich GitHub page (which by the way, is great).

Conclusion

If you make console apps, as most of us Python developers do, I really recommend installing and using Rich! It’ll make your apps easier to code and use.

Thank you for reading!

Jose


Follow me on Twitter if you like!