Extend the setuptools install command

It is a good idea to automate the setup of a project as much as possible to allow developers (including myself) to get started quickly and not have to spend a day to get a build going. Python packages and the pip package management utilities help a lot with that, but they do not always work for all edge cases out of the box.

I recently had a python package where an additional binary dependency in a specific version had to be installed (not available in the cheeseshop) BEFORE any other packages. The reason for that lay in how some other package would handle its dependencies, and the desired behaviour needed it to be installed afterwards. All other requirements were given by the usual requirements.txt, so the development setup looked like:

$ easy_install bin/pywin32-219.win-amd64-py2.7.exe
$ python setup.py develop  # Will also parse & install requirements.txt.

I thought it would be nice to bundle these two commands together, since they both install packages that the project needs. We could add any setup steps here, and have a complex setup going on, while allowing to execute it with a single well known command. Fortunately this is easy, we can just extend our setup.py by subclassing the install command like so (both easy_install and pip can be called from the commandline or directly in code, neat!):

import setuptools

from setuptools.command import easy_install
from setuptools.command.install import install


class InstallBinariesFirst(install):

    def run(self):
        """Install special binary dependencies before the others."""
        binary_dependencies = [r"bin/pywin32-219.win-amd64-py2.7.exe"]
        easy_install.main(binary_dependencies)
        install.run(self)


setuptools.setup(
    name="binary_dependency_test",
    cmdclass={"install": InstallBinariesFirst}
)

That should do, right? Unfortunately there is a small bug that now will make the install/develop command skip any other requirements. It is weird that there is so little information about this basic feature not working properly, but at least I am not the only person encountering it. Gladly it was simply to workaround (though there now is some overlap between install and install bdist_egg, but this was not a problem in my case). The fixed version is:

import setuptools

from setuptools.command import easy_install
from setuptools.command.install import install


class InstallBinariesFirst(install):

    def run(self):
        """Install special binary dependencies before the others."""
        binary_dependencies = [r"bin/pywin32-219.win-amd64-py2.7.exe"]
        easy_install.main(binary_dependencies)
        # Unfortunately the recommended call to 'install.run(self)'
        # will completely ignore the install_requirements, see.
        # So we trick it by calling the underlying bdist_egg instead:
        self.do_egg_install()


setuptools.setup(
    name="binary_dependency_test",
    cmdclass={"install": InstallBinariesFirst}
)

Hopefully this will be of use to someone!

3 Comments

  1. This seems like the solution to the problem that I’m having here: https://stackoverflow.com/questions/50082055/python-setuptools-first-build-from-sources-then-install

    But… I feel like it would still be better to have the pip tools wrangle the files.

    By overriding the install command I feel less confident in managing different version dependencies for the

    py -m pip install

    and

    py -m pip uninstall

    There is no straightforward, declarative way to include post-built binaries?

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.