Working with Python Click Package

Chuan Zhang
6 min readFeb 27, 2023

--

Click is a highly configurable Python package for creating command line interfaces. This tutorial demonstrates how to install it in Linux (Ubuntu 22.04) environment, and go through a few simple examples to explain some basic concepts.

Installing Click

As usual, we do all our development in a virtual environment. Python has multiple tools available for creating and managing virtual environments. Typical examples are virtualenv, pipenv, pyenv, and so on. In this tutorial, we use pyenv for creating and managing our virtual environments. Detailed instruction on how to install pyenv in different operating systems is available in the official documentation. Here we assume you have both python 3.8 and pyenv installed on your machine already, and we begin with creating virtual environment directly.

chuan@chuan-Inspiron-3793:~$ 
chuan@chuan-Inspiron-3793:~$ pyenv versions
system
* 3.8.13 (set by /home/chuan/.pyenv/version)
chuan@chuan-Inspiron-3793:~$
chuan@chuan-Inspiron-3793:~$
chuan@chuan-Inspiron-3793:~$ pyenv virtualenv tut-click
Looking in links: /tmp/tmpe_nbix9q
Requirement already satisfied: setuptools in /home/chuan/.pyenv/versions/3.8.13/envs/tut-click/lib/python3.8/site-packages (56.0.0)
Requirement already satisfied: pip in /home/chuan/.pyenv/versions/3.8.13/envs/tut-click/lib/python3.8/site-packages (22.0.4)
chuan@chuan-Inspiron-3793:~$
chuan@chuan-Inspiron-3793:~$
chuan@chuan-Inspiron-3793:~$ pyenv versions
system
* 3.8.13 (set by /home/chuan/.pyenv/version)
3.8.13/envs/tut-click
tut-click
chuan@chuan-Inspiron-3793:~$
chuan@chuan-Inspiron-3793:~$

Next, we upgrade pip to the latest version and install the click package.

chuan@chuan-Inspiron-3793:~$ 
chuan@chuan-Inspiron-3793:~$ pyenv activate tut-click
pyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.
(tut-click) chuan@chuan-Inspiron-3793:~$
(tut-click) chuan@chuan-Inspiron-3793:~$ pip install --upgrade pip
Requirement already satisfied: pip in ./.pyenv/versions/3.8.13/envs/tut-click/lib/python3.8/site-packages (22.0.4)
Collecting pip
Downloading pip-22.3.1-py3-none-any.whl (2.1 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 3.0 MB/s eta 0:00:00
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 22.0.4
Uninstalling pip-22.0.4:
Successfully uninstalled pip-22.0.4
Successfully installed pip-22.3.1
(tut-click) chuan@chuan-Inspiron-3793:~$
(tut-click) chuan@chuan-Inspiron-3793:~$
(tut-click) chuan@chuan-Inspiron-3793:~$ pip list
Package Version
---------- -------
pip 22.3.1
setuptools 56.0.0
(tut-click) chuan@chuan-Inspiron-3793:~$
(tut-click) chuan@chuan-Inspiron-3793:~$
(tut-click) chuan@chuan-Inspiron-3793:~$ pip install click
Collecting click
Downloading click-8.1.3-py3-none-any.whl (96 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 96.6/96.6 kB 136.2 kB/s eta 0:00:00
Installing collected packages: click
Successfully installed click-8.1.3
(tut-click) chuan@chuan-Inspiron-3793:~$
(tut-click) chuan@chuan-Inspiron-3793:~$
(tut-click) chuan@chuan-Inspiron-3793:~$ pip list
Package Version
---------- -------
click 8.1.3
pip 22.3.1
setuptools 56.0.0
(tut-click) chuan@chuan-Inspiron-3793:~$
(tut-click) chuan@chuan-Inspiron-3793:~$

Basic Concepts and Examples

Different from some other popular command line packages such as argparse, docopt, etc, click declares commands with decorators.

Example 1: using click.command()

In the first example below, we turn a function into a command by decorating it with the click.command() decorator.

import click

@click.command()
def hello_world():
click.echo('Hello World!')

if __name__ == '__main__':
hello()

The code snippet above is the simplest example showing how click turns a function into a command, which can be executed as follows.

(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$ 
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$ python ex01_cmd-no-param.py
Hello World!
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$ python ex01_cmd-no-param.py --help
Usage: ex01_cmd-no-param.py [OPTIONS]

Options:
--help Show this message and exit.
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$

The decorator click.command() takes two parameters name and cls. The name parameter specifies the name of the command. It defaults to the name of the decorated function with underscores replaced by dashes. The cls parameter defines the command class to instantiate. Its default value is the Command class.

In this above example, the decorator click.command() does not take any parameter, hence the command name is the function name, hello, and the command class to instantiate is the default Command class.

Example 2: using click.option() and click.argument() to add parameters

In practice, a command usually comes with parameters. To add parameters, the click.option() and click.argument() decorators can be used.

Parameters can be of different types. The supported types include: str or click.STRING, int or click.INT, float or click.FLOAT, bool or click.BOOL, click.UUID, click.File() (declares a parameter to be a file for reading or writing), click.Path() (similar to the File type, but instead of an open file, it just returns the filename), click.Choice() (allows a value to be checked against a fixed set of supported values), click.IntRange() (restricts an int value to a range of accepted values), click.FloatRange() (restricts a float value to a range of accepted values), and click.DateTime() (converts date strings into datetime objects).

While both decorators can be used to add parameters, argument() decorator is more restricted. The example below demonstrates how to include parameters in a command to connect to a MySQL database, test, to retrieve and print its rows.

import click
import mysql.connector as conn

@click.command()
@click.option(
'-h', '--host',
type=str,
default='localhost',
help='Hostname or IP address'
)
@click.option(
'-p', '--port',
type=click.IntRange(min=1, max=65535),
default=3306,
help='Port'
)
@click.option(
'-u', '--user',
type=click.STRING,
default='root',
help='user name'
)
@click.option(
'-P', '--password',
type=str,
default='root',
help='password'
)
@click.argument('database', type=str, default='test')
@click.argument('table', type=click.STRING, default='tasks')
def connect(host, port, user, password, database, table):
click.echo(f"retrieving ({user}:{password}@{host}:{port}/{database}.{table})")
try:
cnx = conn.connect(
host=host,
port=port,
user=user,
password=password,
database=database
)
cursor = cnx.cursor()
query = f"SELECT task_name, status, start_date, due_date FROM {database}.{table};"
cursor.execute(query)
for (task_name, status, start_date, due_date) in cursor:
print(f"task_name: {task_name}, status: {status}, start_date: {start_date}, due_date: {due_date}\n")
except Exception as err:
print(str(err))
finally:
cursor.close()
cnx.close()

if __name__ == '__main__':
connect()

With the MySQL database, test, and the table tasks created and populated with some sample data as follows

mysql> use test;
Database changed
mysql>
mysql> create table tasks(
-> task_id INT AUTO_INCREMENT PRIMARY KEY,
-> task_name VARCHAR(255) NOT NULL,
-> status TINYINT NOT NULL,
-> start_date DATE,
-> due_date DATE,
-> creator VARCHAR(255),
-> description TEXT
-> ) ENGINE=INNODB;
Query OK, 0 rows affected (2.47 sec)

mysql>
mysql> insert into tasks values(0, 'health-check', 0, DATE('2022-12-18'), DATE('2022-12-21'), 'chuan', 'test health check task');
Query OK, 1 row affected (0.26 sec)

mysql>
mysql> select * from tasks;
+---------+--------------+--------+------------+------------+---------+------------------------+
| task_id | task_name | status | start_date | due_date | creator | description |
+---------+--------------+--------+------------+------------+---------+------------------------+
| 1 | health-check | 0 | 2022-12-18 | 2022-12-21 | chuan | test health check task |
+---------+--------------+--------+------------+------------+---------+------------------------+
1 row in set (0.00 sec)

mysql>

The above example python code can be executed as follows

(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$ 
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$ python ex02_cmd-with-param.py --help
Usage: ex02_cmd-with-param.py [OPTIONS] [DATABASE] [TABLE]

Options:
-h, --host TEXT Hostname or IP address
-p, --port INTEGER RANGE Port [1<=x<=65535]
-u, --user TEXT user name
-P, --password TEXT password
--help Show this message and exit.
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$ python ex02_cmd-with-param.py
retrieving (root:root@localhost:3306/test.tasks)
task_name: health-check, status: 0, start_date: 2022-12-18, due_date: 2022-12-21

(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$ python ex02_cmd-with-param.py -u root -P root test
retrieving (root:root@localhost:3306/test.tasks)
task_name: health-check, status: 0, start_date: 2022-12-18, due_date: 2022-12-21

(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$ python ex02_cmd-with-param.py -u root -P root test task
retrieving (root:root@localhost:3306/test.task)
1146 (42S02): Table 'test.task' doesn't exist
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$
(tut-click) chuan@chuan-Inspiron-3793:~/demo-click/1_basic-concepts$

Conclusion

This short tutorial has demonstrated how to install the click library in Linux environment, and how it can be used in two simple examples.

--

--

Chuan Zhang
Chuan Zhang

No responses yet