Introduction to distutils (II)

2013-11-22 18:18

Hey there! In the first part of this tutorial I showed how to write a simple setup script for distutils (you may want to check that out if you haven't), and now it's time to talk about building and installing the module. Keep on reading and discover the superfoo.

Source code

For the sake of simplicity, I have prepared a really simple python module named superfoo that you can find in my GitHub repository. You can get the source by going to the repository and downloading a ZIP file or if you know your git commands, you can simply clone the source:

$ git clone git@github.com:RMed/superfoo.git

Now that you have the source, you can surely see that its structure looks like this:

superfoo/             -- Directory containing the source
    __init__.py       -- Module that identifies the directory as a Python package
    module_a.py       -- Simple module
    module_b.py       -- Simple module

setup.py              -- Setup script

The first build

We are now going to build the source for the first time. First open the setup.py file and modify the author, author_email and url arguments as you like. Once you are done, navigate to the directory with a terminal/command line and execute:

$ python setup.py build

The build command creates a new directory named build and builds everything needed to install. In this case, the module is very simple and does not have any additional scripts or libraries.

Installing the package

Now that we know how to build the package, installing it is as simple as using:

# python setup.py install

Note the # I have written up there. This means that you most likely need to run this command with superuser/administrator permissions.

Once the process is completed, you will have the superfoo package available for use. To try this, enter a python command line and use some of the functions of the package:

>>> import superfoo
>>> superfoo.hello_world()
Hello World!
>>> bar = superfoo.Bar()
>>> super_bar = superfoo.SuperBar("awesome person")
>>> superfoo.hello_name(super_bar.name)
Hello awesome person!

That's as easy as it gets. You can find the package in the dist-packages or site-packages directories of your Python installation, depending on your system.

Custom commands

Another interesting feature that is available in distutils is creating your own commands. The list of available commands in the script is shown by running:

$ python setup.py --help-commands

Imagine that, for any reason, you need a command that provides some functionality not available in your script (compiling locale, copying files, etc). You could create a new class before setup and then make the script recognize it as a command. For instance, let's add a command that prints I'm done! whenever it is called:

from distutils.core import Command

class IsItDone(Command):
    # Description of the command that is shown when --help-commands is called   
    description = "Are you done yet?"
    # Here you can specify special options
    user_options = []

    def initialize_options(self):
        """ Include any options prior to execution of the command here """

    def finalize_options(self):
        """ Include any options after execution of the command here """

    def run(self):
        """ The function that is executed when the command is used """
        print "I'm done!"

Again, this is a really basic example, but should serve as a way to understand the structure of commands.

The next thing to do is to make the script aware of our new command, for which we add a new attribute to the setup() function:

    # Custom commands

    cmdclass = {

        'doneyet': IsItDone


If we now run:

$ python setup.py --help-commands

We should see a new part at the bottom:

Extra commands:

  doneyet          Are you done yet?

Alright! now let's try running our new command:

$ python setup.py doneyet
running doneyet
I'm done!

Awesome, the command is working as expected, but let's make it even more awesome in the next section.

Overriding commands

Overriding is another useful feature that we can use, for instance, to call the doneyet command after the build has finished. To do this, we create a class like this:

from distutils.command.build import build as _build

class AwesomeBuild(_build):
    # Add custom commands
    sub_commands = _build.sub_commands + [('doneyet', None)]

    def run(self):

And then add the command to the list in setup(), which should now look like this:

    # Module name
    name = 'superfoo',
    # Module version
    version = '1.0.0',
    # Short description
    description = 'Simple Python module example',
    # Author
    author = 'YOUR_NAME_HERE',
    # Contact email
    author_email = 'YOUR_EMAIL_HERE',
    # Support url
    url = 'YOUR_URL_HERE',
    # License (if any)
    license = '',
    # Packages to include in the build
    packages = ['superfoo'],
    # Custom commands
    cmdclass = {
        'build': AwesomeBuild,
        'doneyet': IsItDone

Note that we gave the command the name build so that the original build command is overriden.

That's it for now, next time we will talk about packaging and distributing the modules to the world.