A lot of people still write their command-line applications with Argparse or even Optparse plus input(). Creating outputs is often done directly with print and formatting strings — which gets really hairy when you want to print tables or have some color/emphasis.
There are five libraries that allow you to do WAY better. I can promise the last two will surprise you — let’s get started!
№1 and 2: Use click or Typer for CLIs
click is a library in the Flask ecosystem for creating non-interactive command-line interfaces. It parses command-line arguments/options but uses decorators in a very clever way to do so. Especially the options for creating subcommands are top-notch.
import click
@click.group()
def app():
"""Awesome app doing awesome things."""
@app.command()
@click.option("--count", default=1, help="How much love you want")
@click.argument("name")
def spread(name, count):
"""Spread the love."""
for i in range(count):
print(f"{name} loves you ❤️")
@app.command(name="print")
@click.argument("filepath", metavar="FILE", type=click.Path(exists=True))
@click.option("--show-meta", default=False, is_flag=True)
def print_(filepath, show_meta):
"""Print the file."""
if show_meta:
print(f"File path: {filepath}")
print("-" * 80)
with open(filepath, "r") as f:
print(f.read())
if __name__ == "__main__":
app()
which looks like this:
$ python click-example.py --help
Usage: click-example.py [OPTIONS] COMMAND [ARGS]...
Awesome app doing awesome things.
Options:
--help Show this message and exit.
Commands:
print Print the file.
spread Spread the love.
$ python click-example.py print --help
Usage: click-example.py print [OPTIONS] FILE
Print the file.
Options:
--show-meta
--help Show this message and exit.
Typer is a library in the FastAPI ecosystem and very similar to click. In fact, it uses click under the hood. Typer makes great use of type annotations to allow you to create beautiful command-line interfaces with very little effort.
№3: Use rich for output formatting
Rich is a Python library for rich text and beautiful formatting in the terminal. Its documentation is excellent, so I’ll use pretty much just examples from there.
Progress Bars
import time
from rich.progress import Progress
with Progress() as progress:
task1 = progress.add_task("[red]Downloading...", total=1000)
task2 = progress.add_task("[green]Processing...", total=1000)
task3 = progress.add_task("[cyan]Cooking...", total=1000)
while not progress.finished:
progress.update(task1, advance=0.5)
progress.update(task2, advance=0.3)
progress.update(task3, advance=0.9)
time.sleep(0.02)
There is also the basic example:
from rich.progress import track
for step in track(range(100), description="Processing..."):
do_work(step)
Tables
from rich.console import Console
from rich.table import Table
table = Table(title="Star Wars Movies")
table.add_column("Released", justify="right", style="cyan", no_wrap=True)
table.add_column("Title", style="magenta")
table.add_column("Box Office", justify="right", style="green")
table.add_row(
"Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690"
)
table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
table.add_row(
"Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889"
)
table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")
console = Console()
console.print(table)
Syntax Highlighting
Syntax highlighting in the console is AMAZING. Apparently rich.syntax.Syntax uses pygments under the hood, so you can choose between many different syntax highlighting themes.
import typer
from pathlib import Path
from rich.console import Console
from rich.syntax import Syntax
def app(filepath: Path = typer.Option(..., exists=True)):
"""Print a python file."""
with open(filepath, "r") as f:
code = f.read()
syntax = Syntax(code, "python", theme="solarized-dark", line_numbers=True)
console = Console()
console.print(syntax)
if __name__ == "__main__":
typer.run(app)
Trees
from rich.tree import Tree
from rich import print
tree = Tree("Life")
prokaryota = tree.add("Prokaryota")
prokaryota.add("Eubacteria")
prokaryota.add("Archaebacteria")
eukaryota = tree.add("Eukaryota")
eukaryota.add("Protozoa")
eukaryota.add("Chordata")
plantae = eukaryota.add("Plantae")
plantae.add("glaucophytes")
plantae.add("[red]red algae")
plantae.add("[green][bold]green algae")
plantae.add("land plants 🌳")
eukaryota.add("Fungi")
eukaryota.add("Animalia")
print(tree)
Prompts and Confirmations
from rich.prompt import Prompt, Confirm
is_correct = False
while not is_correct:
name = Prompt.ask("Enter your name", default="Martin")
is_correct = Confirm.ask(f"Is your name '{name}'?")
age = Prompt.ask(
"What is your age group",
choices=["<18", "18-25", "25-35", ">35"],
default="25-35",
)
print(f"You are {name} and your age is {age}.")
Bonus: Rich in the REPL
If you want pretty printing directly in the Python REPL, try this:
>>> from rich import pretty
>>> pretty.install()
>>> ["Rich and pretty", True]
Rich also provides an inspect function which is excellent to figure out what a given object can do:
>>> from rich import inspect
>>> from rich.color import Color
>>> color = Color.parse("red")
>>> inspect(color, methods=True)
№4: Use textual for TUI applications
Textual is a TUI (Text User Interface) framework for Python. It allows you to interact with mouse movements/clicks, have components that listen to keypresses, re-render parts of the screen. I’ve always thought this was only possible in GUI development. Crazy. I love it.
The following code from the README of textual generates the interactive example from the very top:
from rich.panel import Panel
from textual.app import App
from textual.reactive import Reactive
from textual.widget import Widget
class Hover(Widget):
mouse_over = Reactive(False)
def render(self) -> Panel:
return Panel(
"Hello [b]World[/b]", style=("on red" if self.mouse_over else "")
)
def on_enter(self) -> None:
self.mouse_over = True
def on_leave(self) -> None:
self.mouse_over = False
class HoverApp(App):
"""Demonstrates custom widgets"""
async def on_mount(self) -> None:
hovers = (Hover() for _ in range(10))
await self.view.dock(*hovers, edge="top")
HoverApp.run(log="textual.log")
№5: Graphs in the Terminal
If the capabilities of rich and textual weren’t crazy enough for you, give plotext a shot. plotext plots directly on terminal: the syntax is very similar to matplotlib, it has no dependencies (except for an optional dependency required for image plotting) and can save plots as text or as colored HTML 🤯
import plotext as plt # <--- this is plotext, not matplotlib!
y = plt.sin() # sinusoidal signal
plt.scatter(y)
plt.title("Scatter Plot")
plt.show()
How to plot a chart in the terminal? I'm researching ML/Theano and recently came across this script…
Summary
click/typer allows you to create a nice static interface to your application, rich gives you a lot of formatting options like progress bars and tables. If you want to stay on the terminal but get GUI-like features, give textual and plotext a try.
Now that you know how to create nice terminal applications with Python, you should make sure that your terminal is shiny as well:
How to get an amazing Terminal In Windows and Linux; including prompts, fonts, and colors