Making slick looking Python CLI’s with rich ✨
I’ve been making CLIs for some time now, but I’ve never really considered on how to improve the user experience of using the CLI.
The CLI I wrote for the video game API is an example of where the user experience isn’t great. I’d best describe it as boring and unimaginative.
That’s where I discovered the Python library rich! A package that makes it easy to add style and colour to Terminal output.
You can….
- Choose from over 16 million colours from Truecolor
- Choose all ansi styles (including blink!)
- Insert Tables
- Insert markdown
- Add a progress bar
- Insert emojis
And more!
It’s just what I need to make CLIs look good and user friendly.
So this week on my blog, I’ll be rewriting the Terminal application that sends requests to my video game API! 🖥
Be sure to check out the new repo here and the old repo here (so you can compare if you want)
This won’t be the longest blog post as I’ve covered how this CLI works in a previous post so if you want to know how the requests library is used, I suggest reading that post (or look at the repo).
Starting out - improving the Login process
The first part of the application is to login and retrieve an authorisation token.
The current user experience is incredibly boring. In fact, it can be difficult to alert the user if the login fails.
With rich, I can use colour to highlight if the login was successful (with green) or unsuccessful (with red).
So we’ll get started with improving this process!
This is how it looks in the old version of the CLI
Looks pretty bad!
And with using Rich…
It’s much improved!
And there’s even an animation when logging in!
This looks much better! The user of colours, animations and panels make the overall experience easier and more appealing to use.
How is this achieved with Rich?
Let’s take a look at using panels and colours first.
Adding colour and panels to the terminal
Here’s an example of the panel that’s used to greet the user when they load the application:
console.print(Panel.fit(“Welcome to the Video Game API!”, /style/=“bold cyan”))
You can even add a title and a subtitle with by using the appropriate arguments:
console.print(Panel.fit(“[italic yellow]Please type the letters that are highlighted in green to access data from the API[/italic yellow]\n[bold green]D[/bold green]isplay games\n[bold green]F[/bold green]ind game\n[bold green]U[/bold green]pdate game\n[bold green]A[/bold green]dd game\n[bold green]De[/bold green]lete game\n[bold green]Q[/bold green]uit”, /title/=“Video Game API - Menu 🎮”, /subtitle/=“By Joshua Blewitt”))
The Rich library has a Console class that has a print method, which is used to print the display panel.
As you can see from the example above, the panel is it’s own class which has it’s own method called ‘fit’. This takes in the message I want to display and (in this case), the style I want to use.
And for colour:
console.print(“[green]Enter your choice[/green]”)
Using the same console class (where I have created an object in this case), I can use a HTML like syntax for choosing what colour I want.
It’s not just colour, I can even set the text to be bold or italic.
console.print(“[bold red]Your command was invalid! Please try again![/bold red]”)
Adding tables to display information
Let me show you how showing the results from the API used to look:
And with using Rich, it now looks like:
This was achieved by using a table:
# make a new table
table = Table(show_header=True, header_style="bold magenta")
# configure table
table = Table(show_header=True, header_style="bold magenta")
table.add_column("ID", style="dim", width=12)
table.add_column("Name", justify="center")
table.add_column("Genre", justify="center")
table.add_column("Platform", justify="center")
table.add_column("Publisher", justify="center")
table.add_column("Year", justify="center")
# decode JSON
json_response = json_data.json()
games = json_response.get('Games')
# iterate through response
for i in range(len(games)):
new_dict = games[i]['Game']
# add response to table
table.add_row(f"{new_dict['ID: ']}", f"{new_dict['Name:']}", f"{new_dict['Genre: ']}",f"{new_dict['Platform:']}",f"{new_dict['Publisher: ']}",f"{new_dict['Year']}")
console.print(table)
By creating and configuring a new table (plus adding rows), allows us to create a table that makes displaying information from the API way easier to read.
Tables are used across many functions, from displaying games to confirming which game has been deleted.
Using animations when HTTP requests are being sent.
One thing that bugged me about the previous CLI is the that there is no indication that something is in progress when logging in or retrieving data from the API.
With Rich, we can use animations to let the user know that their request is in progress.
Here’s an example of an animation being used:
# display animation to show user that the credentials are being sent to the api
with console.status("[purple]Logging in...[/purple]") as status:
r = requests.post(f'{apiUrl}/login', headers=headers_login, data=payload)
Essentially, while the request is being sent, the animation plays! It looks great in motion (see the GIF above!)
If you want to see all the animations that you can use in Rich, run the following in the terminal:
python -m rich.spinner
Other changes and improvements
One of the biggest changes to this project is that the program is no longer a single file. The functions have been spread across multiple files.
There have been some other smaller changes; such as reducing the amount of declared variables when accessing certain JSON properties.
Wrap up!
And that’s how I used Rich to improve the CLI for accessing the Video Game API.
Rich makes it easy to make appealing CLIs. I’m definitely planning on using Rich again when I need to work on a CLI related project.
Thanks for reading! 👍