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:
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
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
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
>>> 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
>>> 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.)
check = UnicodeSequence(green, "✓", "ok") ui.info("Success!", ui.check)
The module also contains “high-level” methods such as
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,
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
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 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
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 or the newsletter
- Or follow me on Mastodon, dev.to, or twitter.