2

I'm working on a package that has a proprietary dependency with no public download location. If pip finds that this specific dependency isn't installed, I want to either abort the install, or print a warning and continue with the dependency uninstalled. I want to configure this on my end, probably in setup.py, not as something users have to specify when installing my package.

I specifically want pip to not even try to download or install the dependency from anywhere; particularly, if pip tries to download the dependency from PyPI, someone could register something bad with the dependency's name and pip would install that.

There are ways to specify alternate download links for a dependency that should be downloaded from somewhere other than PyPI, but I haven't found a way to say that a dependency shouldn't be downloaded at all.

The best workaround I've found is to simply not put the dependency on the install_requires list at all, but then there's no indication that the dependency exists and no warning if you install the package without the dependency.

Is there a way to say that a specific dependency should not be downloaded?

user2357112
  • 260,549
  • 28
  • 431
  • 505

1 Answers1

3

If pip finds that this specific dependency isn't installed, I want to either abort the install...

If I understand you correctly, you could check if the proprietary package is installed by simply trying to import a module from that package in your setup.py, aborting on ImportError. As an example, let's say your dependency that should abort the installation is numpy:

from distutils.command.build import build as build_orig
from distutils.errors import DistutilsModuleError
from setuptools import setup


class build(build_orig):

    def finalize_options(self):
        try:
            import numpy
        except ImportError:
            raise DistutilsModuleError('numpy is not installed. Installation will be aborted.')
        super().finalize_options()


setup(
    name='spam',
    version='0.1',
    author='nobody',
    author_email='nobody@nowhere.com',
    packages=[],
    install_requires=[
        # all the other dependencies except numpy go here as usual
    ],
    cmdclass={'build': build,},
)

Now, you should distribute your package as source tar because wheels won't invoke setup.py on installation:

$ python setup.py sdist

Trying to install the built tar when numpy is missing will result in:

$ pip install dist/spam-0.1.tar.gz 
Processing ./dist/spam-0.1.tar.gz
    Complete output from command python setup.py egg_info:
    running egg_info
    creating pip-egg-info/spam.egg-info
    writing pip-egg-info/spam.egg-info/PKG-INFO
    writing dependency_links to pip-egg-info/spam.egg-info/dependency_links.txt
    writing top-level names to pip-egg-info/spam.egg-info/top_level.txt
    writing manifest file 'pip-egg-info/spam.egg-info/SOURCES.txt'
    reading manifest file 'pip-egg-info/spam.egg-info/SOURCES.txt'

    error: numpy is not installed. Installation will be aborted.

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/_y/2qk6029j4c7bwv0ddk3p96r00000gn/T/pip-s8sqn20t-build/

Note that I could do the import check on top of the setup script:

from setuptools import setup

try:
    import numpy
except ImportError:
    raise DistutilsModuleError(...)

setup(...)

But in this case, the output won't be formatted and the complete stack trace will be spilled to the stdout, which is too technical and could confuse the user. Instead, I subclass one of the distutils commands that will be invoked on installation, so that the error output is properly formatted.

Now, for the second part:

...or print a warning and continue with the dependency uninstalled.

This is not possible anymore, since version 7 pip will swallow any output from your setup script. The user will see the output from the setup script only if pip runs in verbose mode, i.e. pip install -v mypkg. A questionable decision if you ask me.

Nevertheless, here is an example of issuing a warning and an error in your setup script:

from distutils.log import ERROR

class build(build_orig):

    def finalize_options(self):
        try:
            import numpy
        except ImportError:
            # issue an error and proceed
            self.announce('Houston, we have an error!', level=ERROR)
            # for warnings, there is a shortcut method
            self.warn('I am warning you!')
        super().finalize_options()
hoefling
  • 59,418
  • 12
  • 147
  • 194