Advanced Tutorial

There are plenty of advanced topics available to us. Here, we will try to look at them with an enphasis on simplicity.

Multiple commands and execution

Once we start adding more capabilities to our program, we might find having multiple commands with their own arguments and options does make the interface cleaner. To keep it simple, let’s look at a small program that can install, run and uninstall itself:

$ ls -l netinstall.py
-rwxr-xr-x 1 admin admin 745 Jul  2 21:00 netapplet.py

$ cat netinstall.py
#!/usr/bin/env python

from loadconfig import Config
import sys

conf = """\
    clg:
        subparsers:
            run:
                help: 'run as:  $prog run'
            install:
                help: 'run as:  $prog install | sudo bash'
            uninstall:
                help: 'run as:  $prog uninstall | sudo bash'
    """

def install(c):
    print('cp {} /usr/bin'.format(c.prog))

def uninstall(c):
    print('rm -f /usr/bin/{}'.format(c.prog))

def run(c):
    print('Running {}'.format(c.prog))

def main(args):
    c = Config(conf, args=args)
    c.run()

if __name__ == '__main__':
    main(sys.argv)

A couple of runs will show:

# Our program is not installed in the system /usr/bin directory
netapplet.py
bash: netapplet.py: command not found


# Running netapplet.py from the current directory.
# ( ./ needs to be prepended to the command as . is not usually on $PATH )
$ ./netapplet.py
usage: netapplet.py [-h] {run,install,uninstall} ...


# Retrieving help
$ ./netapplet.py --help
usage: netapplet.py [-h] {run,install,uninstall} ...

positional arguments:
  {run,install,uninstall}
    run                 run as:  ./netapplet.py run
    install             run as:  ./netapplet.py install | sudo bash
    uninstall           run as:  ./netapplet.py uninstall | sudo bash

optional arguments:
  -h, --help            show this help message and exit


# Looking the output of install
./netapplet.py install
cp ./netapplet.py /usr/bin


# Sounds good. Lets install it according to the help.
$ ./netapplet.py install | sudo bash


# Checking our program is working on the system. Yeey!
$ netapplet.py run
Running /usr/bin/netapplet.py


# Time to uninstall it
$ ./netapplet.py uninstall | sudo bash

# And... it's gone
$ netapplet.py run
bash: /usr/bin/netapplet.py: No such file or directory

The program is clean, self-installable and self-documented.

The first line is a typical unix shebang to look for the system or virtualenv python shell. (Note: $VIRTUAL_ENV could had been used to optionally autoinstall within the virtualenv). It follows a few import lines and the conf global variable. The install, uninstall and run functions are simple print statements that will be leveraged by a sudo shell in our program, though these functions can be as complex as wanted, being part of other modules, etc. They receive the configuration as the first argument. The main function is called if our program is executed directly. This is a good programming and testing practice. Having main as a function with parameters allows to test it with handcrafted arguments. Lets now focus on the conf and the main functions.

conf is a clg key with subparsers. This is an argparse concept which basically means a subcommand. In our case we have three of them with their documentation. If you are wondering why the quotes in the help of the subparsers keys, this is to escape the usual yaml meaning of the colon (:) char on these help strings. As we are using a clg key, loadconfig assigns args[0] to the $prog attribute, decoupling the program name from sys.argv[0]. Next, it loads the configuration in the c variable, and finally c.run() executes the function invoked by the cli arguments. c.run() only makes sense if the config holds a clg key with subparsers.

This simple program succinctly highlights very common needs in a program lifecycle (configuration, interface, documentation, deployment) and it can easily be used as a base for more complex ones.