Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Added deps command to resolve package dependencies without installing #826

Closed
wants to merge 8 commits into from

1 participant

@ricobl

I've created a deps command to resolve package dependencies and its versions.
It's useful for creating a requirements.txt file for later use with the --no-deps parameter.

The usage and the solution are very similar to the install command using the --no-install parameter, collecting InstallRequirements objects and printing the package and version information.

Tests are included.

@ricobl ricobl closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
3  pip/commands/__init__.py
@@ -6,6 +6,7 @@
from pip.commands.bundle import BundleCommand
from pip.commands.completion import CompletionCommand
from pip.commands.freeze import FreezeCommand
+from pip.commands.deps import DependenciesCommand
from pip.commands.help import HelpCommand
from pip.commands.list import ListCommand
from pip.commands.search import SearchCommand
@@ -20,6 +21,7 @@
BundleCommand.name: BundleCommand,
CompletionCommand.name: CompletionCommand,
FreezeCommand.name: FreezeCommand,
+ DependenciesCommand.name: DependenciesCommand,
HelpCommand.name: HelpCommand,
SearchCommand.name: SearchCommand,
ShowCommand.name: ShowCommand,
@@ -35,6 +37,7 @@
InstallCommand,
UninstallCommand,
FreezeCommand,
+ DependenciesCommand,
ListCommand,
ShowCommand,
SearchCommand,
View
149 pip/commands/deps.py
@@ -0,0 +1,149 @@
+import os
+import sys
+from pip.req import InstallRequirement, RequirementSet, parse_requirements
+from pip.log import logger
+from pip.locations import build_prefix, src_prefix
+from pip.basecommand import Command, ERROR, SUCCESS
+from pip.index import PackageFinder
+from pip.cmdoptions import make_option_group, index_group
+
+
+class DependenciesCommand(Command):
+ """
+ Resolve package dependencies from:
+
+ - PyPI (and other indexes) using requirement specifiers.
+ - VCS project urls.
+ - Local project directories.
+ - Local or remote source archives.
+
+ pip also supports resolving from "requirements files", which provide
+ an easy way to specify a whole environment to be installed.
+
+ See http://www.pip-installer.org for details on VCS url formats and
+ requirements files.
+ """
+ name = 'deps'
+
+ usage = """
+ %prog [options] <requirement specifier> ...
+ %prog [options] -r <requirements file> ...
+ %prog [options] [-e] <vcs project url> ...
+ %prog [options] [-e] <local project path> ...
+ %prog [options] <archive url/path> ..."""
+
+ summary = 'Resolve dependencies for packages.'
+ bundle = False
+
+ def __init__(self, *args, **kw):
+ super(DependenciesCommand, self).__init__(*args, **kw)
+
+ cmd_opts = self.cmd_opts
+
+ cmd_opts.add_option(
+ '-e', '--editable',
+ dest='editables',
+ action='append',
+ default=[],
+ metavar='path/url',
+ help='Install a project in editable mode (i.e. setuptools "develop mode") from a local project path or a VCS url.')
+
+ cmd_opts.add_option(
+ '-r', '--requirement',
+ dest='requirements',
+ action='append',
+ default=[],
+ metavar='file',
+ help='Install from the given requirements file. '
+ 'This option can be used multiple times.')
+
+ index_opts = make_option_group(index_group, self.parser)
+
+ self.parser.insert_option_group(0, index_opts)
+ self.parser.insert_option_group(0, cmd_opts)
+
+ def _build_package_finder(self, options, index_urls):
+ """
+ Create a package finder appropriate to this install command.
+ This method is meant to be overridden by subclasses, not
+ called directly.
+ """
+ return PackageFinder(find_links=options.find_links,
+ index_urls=index_urls,
+ use_mirrors=options.use_mirrors,
+ mirrors=options.mirrors)
+
+ def _requirement_line(self, req):
+ line = ''
+
+ if req.dependency_links:
+ line += '\n'.join(
+ ['--find-links {}'.format(l) for l in req.dependency_links])
+ line += '\n'
+
+ if req.editable:
+ line += '-e '
+
+ if req.url:
+ line += req.url
+ else:
+ line += '%s==%s' % (req.name, req.installed_version)
+
+ return line
+
+ def run(self, options, args):
+ options.no_install = True
+
+ options.build_dir = os.path.abspath(build_prefix)
+ options.src_dir = os.path.abspath(src_prefix)
+ index_urls = [options.index_url] + options.extra_index_urls
+ if options.no_index:
+ logger.notify('Ignoring indexes: %s' % ','.join(index_urls))
+ index_urls = []
+
+ finder = self._build_package_finder(options, index_urls)
+
+ requirement_set = RequirementSet(
+ build_dir=options.build_dir,
+ src_dir=options.src_dir,
+ download_dir=None,
+ ignore_installed=True,
+ force_reinstall=True)
+
+ for name in args:
+ requirement_set.add_requirement(
+ InstallRequirement.from_line(name, None))
+ for name in options.editables:
+ requirement_set.add_requirement(
+ InstallRequirement.from_editable(name, default_vcs=options.default_vcs))
+ for filename in options.requirements:
+ for req in parse_requirements(filename, finder=finder, options=options):
+ requirement_set.add_requirement(req)
+
+ if not requirement_set.has_requirements:
+ opts = {'name': self.name}
+ if options.find_links:
+ msg = ('You must give at least one requirement to %(name)s '
+ '(maybe you meant "pip %(name)s %(links)s"?)' %
+ dict(opts, links=' '.join(options.find_links)))
+ else:
+ msg = ('You must give at least one requirement '
+ 'to %(name)s (see "pip help %(name)s")' % opts)
+ logger.warn(msg)
+ return ERROR
+
+ requirement_set.prepare_files(finder)
+
+ requirements = '\n'.join(
+ [self._requirement_line(req) for req in
+ requirement_set.successfully_downloaded])
+
+ if requirements:
+ requirement_set.cleanup_files()
+ sys.stdout.write(requirements + '\n')
+
+ return SUCCESS
+
+ def setup_logging(self):
+ # Avoid download progress on stdout
+ logger.move_stdout_to_stderr()
View
BIN  tests/packages/deps/dependant-1.0.tar.gz
Binary file not shown
View
BIN  tests/packages/deps/dependency-1.0.tar.gz
Binary file not shown
View
BIN  tests/packages/deps/dependency-links-1.0.tar.gz
Binary file not shown
View
92 tests/test_deps.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+import os
+from unittest import TestCase
+from tests.test_pip import reset_env, run_pip, here
+from pip.basecommand import ERROR, SUCCESS
+
+
+def _run_local_deps(*args, **kwargs):
+ find_links = 'file://' + os.path.join(here, 'packages/deps')
+ return _run_deps('-f', find_links, *args, **kwargs)
+
+def _run_deps(*args, **kwargs):
+ kwargs['expect_stderr'] = True
+ return run_pip('deps', '--no-index', *args, **kwargs)
+
+
+class TestDepsCommandWithASinglePackage(TestCase):
+
+ @classmethod
+ def setupClass(cls):
+ reset_env()
+ cls.result = _run_local_deps('dependency')
+
+ def test_exits_with_success(self):
+ self.assertEqual(self.result.returncode, SUCCESS)
+
+ def test_returns_version_info(self):
+ self.assertTrue('dependency==1.0' in self.result.stdout)
+
+ def test_redirects_downloading_messages_to_stderr(self):
+ self.assertTrue('Downloading/unpacking' in self.result.stderr)
+
+ def test_removes_downloaded_files(self):
+ self.assertFalse(self.result.files_created)
+
+
+class TestDepsCommandWithDependencies(TestCase):
+
+ @classmethod
+ def setupClass(cls):
+ reset_env()
+ cls.result = _run_local_deps('dependant')
+
+ def test_returns_version_info(self):
+ assert 'dependant==1.0' in self.result.stdout
+ assert 'dependency==1.0' in self.result.stdout
+
+ def test_exits_with_success(self):
+ self.assertEqual(self.result.returncode, SUCCESS)
+
+
+class TestDepsCommandWithNonExistentPackage(TestCase):
+
+ @classmethod
+ def setupClass(cls):
+ reset_env()
+ cls.result = _run_local_deps('non-existent', expect_error=True)
+
+ def test_exits_with_error(self):
+ self.assertEqual(self.result.returncode, ERROR)
+
+
+class TestDepsWithURL(TestCase):
+
+ @classmethod
+ def setupClass(cls):
+ reset_env()
+ path = os.path.join(here, 'packages/deps/dependency-1.0.tar.gz')
+ cls.url = 'file://' + path
+ cls.result = _run_deps(cls.url)
+
+ def test_returns_dependency_url(self):
+ assert self.url in self.result.stdout
+
+ def test_exits_with_success(self):
+ self.assertEqual(self.result.returncode, SUCCESS)
+
+
+class TestDepsWithDependencyLinks(TestCase):
+
+ @classmethod
+ def setupClass(cls):
+ reset_env()
+ cls.result = _run_local_deps('dependency-links')
+
+ def test_returns_dependency_links(self):
+ assert 'dependency-links==1.0' in self.result.stdout
+ assert '--find-links http://pypi.python.org/simple' in self.result.stdout
+
+ def test_exits_with_success(self):
+ self.assertEqual(self.result.returncode, SUCCESS)
Something went wrong with that request. Please try again.