Skip to content

Commit

Permalink
Merge pull request #3750 from msabramo/check_command_rebase_develop
Browse files Browse the repository at this point in the history
Add a `pip check` command.
  • Loading branch information
pfmoore authored Jun 25, 2016
2 parents e04941b + 76c3561 commit d7444b7
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 0 deletions.
3 changes: 3 additions & 0 deletions pip/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pip.commands.hash import HashCommand
from pip.commands.help import HelpCommand
from pip.commands.list import ListCommand
from pip.commands.check import CheckCommand
from pip.commands.search import SearchCommand
from pip.commands.show import ShowCommand
from pip.commands.install import InstallCommand
Expand All @@ -27,6 +28,7 @@
UninstallCommand.name: UninstallCommand,
DownloadCommand.name: DownloadCommand,
ListCommand.name: ListCommand,
CheckCommand.name: CheckCommand,
WheelCommand.name: WheelCommand,
}

Expand All @@ -38,6 +40,7 @@
FreezeCommand,
ListCommand,
ShowCommand,
CheckCommand,
SearchCommand,
WheelCommand,
HashCommand,
Expand Down
37 changes: 37 additions & 0 deletions pip/commands/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import logging

from pip.basecommand import Command
from pip.operations.check import (
check_requirements, get_installed_distributions)


logger = logging.getLogger(__name__)


class CheckCommand(Command):
"""Verify installed packages have compatible dependencies."""
name = 'check'
usage = """
%prog [options]"""
summary = 'Verify installed packages have compatible dependencies.'

def run(self, options, args):
installed = get_installed_distributions(skip=())
missing_reqs_dict, incompatible_reqs_dict = check_requirements()

for dist in installed:
key = '%s==%s' % (dist.project_name, dist.version)

for requirement in missing_reqs_dict.get(key, []):
logger.info(
"%s %s requires %s, which is not installed.",
dist.project_name, dist.version, requirement.project_name)

for requirement, actual in incompatible_reqs_dict.get(key, []):
logger.info(
"%s %s has requirement %s, but you have %s %s.",
dist.project_name, dist.version, requirement,
actual.project_name, actual.version)

if missing_reqs_dict or incompatible_reqs_dict:
return 1
52 changes: 52 additions & 0 deletions pip/operations/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from pip.utils import get_installed_distributions


def check_requirements():
installed = get_installed_distributions(skip=())
missing_reqs_dict = {}
incompatible_reqs_dict = {}

for dist in installed:
key = '%s==%s' % (dist.project_name, dist.version)

missing_reqs = list(get_missing_reqs(dist, installed))
if missing_reqs:
missing_reqs_dict[key] = missing_reqs

incompatible_reqs = list(get_incompatible_reqs(dist, installed))
if incompatible_reqs:
incompatible_reqs_dict[key] = incompatible_reqs

return (missing_reqs_dict, incompatible_reqs_dict)


def get_missing_reqs(dist, installed_dists):
"""Return all of the requirements of `dist` that aren't present in
`installed_dists`.
"""
installed_names = set(d.project_name.lower() for d in installed_dists)
missing_requirements = set()

for requirement in dist.requires():
if requirement.project_name.lower() not in installed_names:
missing_requirements.add(requirement)
yield requirement


def get_incompatible_reqs(dist, installed_dists):
"""Return all of the requirements of `dist` that are present in
`installed_dists`, but have incompatible versions.
"""
installed_dists_by_name = {}
for installed_dist in installed_dists:
installed_dists_by_name[installed_dist.project_name] = installed_dist

incompatible_requirements = set()
for requirement in dist.requires():
present_dist = installed_dists_by_name.get(requirement.project_name)

if present_dist and present_dist not in requirement:
incompatible_requirements.add((requirement, present_dist))
yield (requirement, present_dist)
52 changes: 52 additions & 0 deletions tests/functional/test_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
def test_check_clean(script):
"""On a clean environment, check shouldn't return anything.
"""
result = script.pip('check')
assert result.stdout == ""


def test_check_missing_dependency(script):
# this will also install ipython, a dependency
script.pip('install', 'ipdb==0.7')

# deliberately remove the dependency
script.pip('uninstall', 'ipython', '--yes')

result = script.pip('check', expect_error=True)

assert result.stdout == ("ipdb 0.7 requires ipython, "
"which is not installed.\n")
assert result.returncode == 1


def test_check_missing_dependency_normalize_case(script):
# Install some things
script.pip('install', 'devpi-web==2.2.2')
script.pip('install', 'pyramid==1.5.2')

# deliberately remove some dependencies
script.pip('uninstall', 'pygments', '--yes')
script.pip('uninstall', 'zope.deprecation', '--yes')

result = script.pip('check', expect_error=True)

assert ('devpi-web 2.2.2 requires pygments, '
'which is not installed.') in result.stdout
assert ('pyramid 1.5.2 requires zope.deprecation, '
'which is not installed.') in result.stdout
assert result.returncode == 1


def test_check_broken_dependency(script):
# this will also install a compatible version of jinja2
script.pip('install', 'flask==0.10.1')

# deliberately change dependency to a version that is too old
script.pip('install', 'jinja2==2.3')

result = script.pip('check', expect_error=True)

assert result.stdout == ("Flask 0.10.1 has requirement Jinja2>=2.4, "
"but you have Jinja2 2.3.\n")
assert result.returncode == 1

0 comments on commit d7444b7

Please sign in to comment.