This post was originally posted on my blog
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 = _Characters(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!
Top comments (0)