Setuptools – run custom code in setup.py

A week or so ago I started developing an experimental Python package for one of our projects. At some point I realized that it would be convenient to automatically execute some additional initialization code during the package installation process (i.e. when “python setup.py install” is run).

This can be achived by subclassing the setuptools.command.install class and overriding its run() method, like this (in setup.py):

from setuptools import setup
from setuptools.command.install import install


class CustomInstallCommand(install):
    """Customized setuptools install command - prints a friendly greeting."""
    def run(self):
        print "Hello, developer, how are you? :)"
        install.run(self)


setup(
    ...

NOTE: We reference the parent class’ run method directly – we can’t use super(…).run(self), because setuptools commands are old-style Python classes and super() does not support them.

Now that we have a customized install class, we must tell the setuptools machinery to actually use it instead of the built-in version. We do this through the cmdclass parameter of the setup() function:

...

setup(
    ...

    cmdclass={
        'install': CustomInstallCommand,
    },

    ...
)

The value of the cmdclass parameter should be a dictionary whose keys are the names of the setuptools commands we’re customizing (‘install’ in our case), while the corresponding values are our custom command classes we have defined eariler (CustomInstallCommand in this example).

BONUS

Sometimes you will want to apply the the same modification to more than a single command class. For instance your package could also be installed in development mode (by running python setup.py develop), meaning that the setuptools.command.develop class should be overriden as well in order for your modifications of the installation procedure to have any effect in this scenario, too.

A straightforward approach would be to implement another class (e.g. CustomDevelopCommand) similar to the the existing CustomInstallCommand class, but this would violate the DRY principle (“don’t repeat yourself”). What you can do is to define a decorator which accepts command class as a parameter, modifies its run() method and returns a modified version of the class.

Here’s an example:

from setuptools import setup
from setuptools.command.develop import develop
from setuptools.command.install import install


def friendly(command_subclass):
    """A decorator for classes subclassing one of the setuptools commands.

    It modifies the run() method so that it prints a friendly greeting.
    """
    orig_run = command_subclass.run

    def modified_run(self):
        print "Hello, developer, how are you? :)"
        orig_run(self)

    command_subclass.run = modified_run
    return command_subclass

...

@friendly
class CustomDevelopCommand(develop):
    pass

@friendly
class CustomInstallCommand(install):
    pass


setup(
    ...

It’s very simple – we just replace the run() method of a command class with our customized version of it and then apply the decorator where necessary. If we later need to replace the greeting with something different, we only have to change the code in one place.

NOTE: Do not forget to provide the right value of the cmdclass parameter to the setup() function.

By the way – you might be looking at the decorator code and wondering why we explicitly store a reference (‘orig_run’) to the original run method. The reason is we can’t simply call command_subclass.run() in modified_run function directly, because that would cause an infinite loop!
Just look at the code carefully – at the end of the decorator, command_subclass.run becomes a reference to modified_run. If modified_run then calls command_subclass.run(self) in its body, it actually calls itself – again and again and again, until maximum recursion depth is exceeded. Explicitly storing a reference to the original run() method is thus not redunant at all, it’s simply necessary.

Peter Lamut

Peter was one of the original co-founders of Niteo and the main developer on our early projects.

See other posts »