Introducing python-cli-ui
For quite some time I’ve been adding a file called ui.py in some
of the Python projects I was working on.
Since I believe in the rule of three and I already have three different projects using it, I’ve decided to share it to the world.
Feel free to take a look at the github page.
What it does #
Let’s start with a screen shot:

Coloring #
It’s a module to write colorful command line interfaces in Python.
It’s a tiny wrapper on top of colorama, with a (IMHO) nicer API than
crayons or
lazyme.
Here’s an example:
ui.info(ui.red, "Error", ui.reset, ui.bold, file_path, ui.reset, "not found")
This will print the word ‘Error’ in red, the file path in bold, and the ’not found’ normally.
The API follows the behavior of the print() function: by default, tokens
are separated by spaces, \n is added at the end, and you can specify
your own sep and end keywords if you need to.
Displaying enumerations #
It also allows to display items of a list, taking care of
“off-by-one” errors and aligning the numbers nicely (note the
leading space for 1/12)
>>> months = ["January", "February", ..., "December"]
>>> for i, month in enumerate(months):
>>> ui.info_count(i, 12, month)
* ( 1/12) January
* ( 2/12) February
...
* (12/12) December
Indenting #
>>> first_name = "John"
>>> last_name = "Doe"
>>> adress = """\
ACME Inc.
795 E Dragram
Tucson AZ 85705
USA
"""
>>> ui.info("People")
>>> ui.info(ui.tabs(1), first_name, last_name)
>>> ui.info(ui.tabs(1), "Adress:")
>>> ui.info(ui.indent(2, adress))
People:
John Doe
Adress:
795 E Dragram
Tucson AZ 85705
USA
Unicode goodness #
You can define your own suite of characters, which will get a color, a unicode and an ASCII representation (so that it works on Windows too.)
For instance:
check = UnicodeSequence(green, "✓", "ok")
ui.info("Success!", ui.check)
On Linux:

On Windows:

Semantics #
The module also contains “high-level” methods such as
info_1, info_2, info_3, so that you can write:
ui.info_1("Making some tea")
...
ui.info_2("Boiling water")
...
ui.info_3("Turning kettle on")
...
ui.info_1("Done")
Which will be rendered as:

This allows you to group your messages in a coherent way. Here the main message is ‘Making some tea’. ‘Boiling water’ is a sub-task of making the tea, and ‘Turning the kettle on’ is a sub-task of the boiling water process.
In the same vein, warning, error and fatal methods are provided for when
things go wrong. (The last one calls sys.exit(), hence the name)
There’s also a debug function, for messages you are only interested when
debugging: you can control the verbosity using the CONFIG global dictionary.
ui.CONFIG['quiet'] = True
ui.info("this is some info") # won't get printed
ui.CONFIG['verbose'] = True
ui.debug("A debug message") # will get printed
Interaction #
Arbitrary string with a default #
>>> domain = ui.ask_string("Please enter your domain name", default="example.com")
>>> print("You chose:", domain)
> Please enter your domain name (example.com)
(nothing)
> You chose example.com
>>> domain = ui.ask_string("Please enter your domain name", default="example.com")
>>> print("You chose:", domain)
> Please enter your domain name (example.com)
foobar.com
> You chose foobar.com
Boolean choice #
Note how the prompt goes from Y/n (y uppercase), to y/N (n uppercase)
depending on the default value:
>>> with_sugar = ui.ask_yes_no("With sugar?", default=True)
> "With sugar ? (Y/n)"
n
> False
>>> with_cream = ui.ask_yes_no("With cream?", default=False)
> "With cream? (y/N)"
(nothing)
> False
Choice in a list #
Note how the user is stuck in a loop until he enters a valid answer, and how the first item is selected by default:
>>> choices = ["apple", "orange", "banana"]
>>> answer = ui.ask_choice("Select a fruit:", choices)
> Select a fruit:
1. apple (default)
2. orange
3. banana
> foobar
Please enter a number between 1 and 3
> 4
Please enter a number between 1 and 3
> 2
>>> print(answer)
oranges
Other goodies #
A timer #
@ui.timer("Doing foo")
def foo():
# something that takes time
>>> foo()
... # output of the foo method
Doing foo took 3min 13s
Works also in a with statement:
with ui.timer("making foobar"):
foo()
bar()
Did you mean? #
>>> commands = ["install", "remove"]
>>> user_input = input()
intall
>>> ui.did_you_mean("No such command", user_input, choices)
No such command.
Did you mean: install?
A pytest fixture #
You can also write tests to assert that a certain message matching a regexp was emitted.
def say_hello(name):
ui.info("Hello", name)
def test_say_hello(messages):
say_hello("John")
assert messages.find(r"Hello\w+John")
Parting words #
Well, I hope you’ll find this module useful.
It is available as python-cli-ui on pypi
Cheers!
Thanks for reading this far :)
I'd love to hear what you have to say, so please feel free to leave a comment below, or read the contact page for more ways to get in touch with me.
Note that to get notified when new articles are published, you can either:
- Subscribe to the RSS feed
- Follow me on Mastodon
- Follow me on dev.to (mosts of my posts are mirrored there)
- Or send me an email to subscribe to my newsletter
Cheers!