1
1
Fork 0
mirror of https://github.com/pypa/pip synced 2023-12-13 21:30:23 +01:00

Merge pull request #3085 from patricklaw/add-pip-download-command

Add `pip download` command and deprecate `pip install --download`.
This commit is contained in:
Xavier Fernandez 2015-09-15 23:27:47 +02:00
commit 0b1acf9726
13 changed files with 407 additions and 40 deletions

View file

@ -23,6 +23,9 @@
* Allow repository URLs with secure transports to count as trusted. (E.g.,
"git+ssh" is okay.) :issue:`2811`.
* Implement a top-level ``pip download`` command and deprecate
``pip install --download``.
**7.1.2 (2015-08-22)**

View file

@ -7,6 +7,7 @@ Reference Guide
pip
pip_install
pip_download
pip_uninstall
pip_freeze
pip_list

View file

@ -0,0 +1,53 @@
.. _`pip download`:
pip download
------------
.. contents::
Usage
*****
.. pip-command-usage:: download
Description
***********
.. pip-command-description:: download
Overview
++++++++
``pip download`` replaces the ``--download`` option to ``pip install``,
which is now deprecated and will be removed in pip 10.
``pip download`` does the same resolution and downloading as ``pip install``,
but instead of installing the dependencies, it collects the downloaded
distributions into the directory provided (defaulting to ``./pip_downloads``).
This directory can later be passed as the value to
``pip install --find-links`` to facilitate offline or locked down package
installation.
Options
*******
.. pip-command-options:: download
.. pip-index-options::
Examples
********
1. Download a package and all of its dependencies
::
$ pip download -d ./pip_downloads SomePackage
$ pip download SomePackage # equivalent to above
$ pip download --no-index --find-links=/tmp/wheelhouse -d /tmp/otherwheelhouse SomePackage

View file

@ -7,10 +7,12 @@ import sys
import optparse
from pip import cmdoptions
from pip.index import PackageFinder
from pip.locations import running_under_virtualenv
from pip.download import PipSession
from pip.exceptions import (BadCommand, InstallationError, UninstallationError,
CommandError, PreviousBuildDirError)
from pip.compat import logging_dictConfig
from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
from pip.req import InstallRequirement, parse_requirements
@ -290,3 +292,22 @@ class RequirementCommand(Command):
msg = ('You must give at least one requirement '
'to %(name)s (see "pip help %(name)s")' % opts)
logger.warning(msg)
def _build_package_finder(self, options, session):
"""
Create a package finder appropriate to this requirement command.
"""
index_urls = [options.index_url] + options.extra_index_urls
if options.no_index:
logger.info('Ignoring indexes: %s', ','.join(index_urls))
index_urls = []
return PackageFinder(
find_links=options.find_links,
format_control=options.format_control,
index_urls=index_urls,
trusted_hosts=options.trusted_hosts,
allow_all_prereleases=options.pre,
process_dependency_links=options.process_dependency_links,
session=session,
)

View file

@ -553,18 +553,24 @@ general_group = {
]
}
index_group = {
non_deprecated_index_group = {
'name': 'Package Index Options',
'options': [
index_url,
extra_index_url,
no_index,
find_links,
process_dependency_links,
]
}
index_group = {
'name': 'Package Index Options (including deprecated options)',
'options': non_deprecated_index_group['options'] + [
allow_external,
allow_all_external,
no_allow_external,
allow_unsafe,
no_allow_unsafe,
process_dependency_links,
]
}

View file

@ -4,6 +4,7 @@ Package containing all pip commands
from __future__ import absolute_import
from pip.commands.completion import CompletionCommand
from pip.commands.download import DownloadCommand
from pip.commands.freeze import FreezeCommand
from pip.commands.help import HelpCommand
from pip.commands.list import ListCommand
@ -22,6 +23,7 @@ commands_dict = {
ShowCommand.name: ShowCommand,
InstallCommand.name: InstallCommand,
UninstallCommand.name: UninstallCommand,
DownloadCommand.name: DownloadCommand,
ListCommand.name: ListCommand,
WheelCommand.name: WheelCommand,
}
@ -29,6 +31,7 @@ commands_dict = {
commands_order = [
InstallCommand,
DownloadCommand,
UninstallCommand,
FreezeCommand,
ListCommand,

135
pip/commands/download.py Normal file
View file

@ -0,0 +1,135 @@
from __future__ import absolute_import
import logging
import os
from pip.req import RequirementSet
from pip.basecommand import RequirementCommand
from pip import cmdoptions
from pip.utils import ensure_dir, normalize_path
from pip.utils.build import BuildDirectory
from pip.utils.filesystem import check_path_owner
DEFAULT_DOWNLOAD_DIR = os.path.join(normalize_path(os.curdir), 'pip_downloads')
logger = logging.getLogger(__name__)
class DownloadCommand(RequirementCommand):
"""
Download packages from:
- PyPI (and other indexes) using requirement specifiers.
- VCS project urls.
- Local project directories.
- Local or remote source archives.
pip also supports downloading from "requirements files", which provide
an easy way to specify a whole environment to be downloaded.
"""
name = 'download'
usage = """
%prog [options] <requirement specifier> [package-index-options] ...
%prog [options] -r <requirements file> [package-index-options] ...
%prog [options] [-e] <vcs project url> ...
%prog [options] [-e] <local project path> ...
%prog [options] <archive url/path> ..."""
summary = 'Download packages.'
def __init__(self, *args, **kw):
super(DownloadCommand, self).__init__(*args, **kw)
cmd_opts = self.cmd_opts
cmd_opts.add_option(cmdoptions.constraints())
cmd_opts.add_option(cmdoptions.editable())
cmd_opts.add_option(cmdoptions.requirements())
cmd_opts.add_option(cmdoptions.build_dir())
cmd_opts.add_option(cmdoptions.no_deps())
cmd_opts.add_option(cmdoptions.global_options())
cmd_opts.add_option(cmdoptions.no_binary())
cmd_opts.add_option(cmdoptions.only_binary())
cmd_opts.add_option(cmdoptions.src())
cmd_opts.add_option(cmdoptions.no_clean())
cmd_opts.add_option(cmdoptions.pre())
cmd_opts.add_option(
'-d', '--dest', '--destination-dir', '--destination-directory',
dest='download_dir',
metavar='dir',
default=DEFAULT_DOWNLOAD_DIR,
help=("Download packages into <dir>."),
)
index_opts = cmdoptions.make_option_group(
cmdoptions.non_deprecated_index_group,
self.parser,
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
def run(self, options, args):
options.ignore_installed = True
options.src_dir = os.path.abspath(options.src_dir)
ensure_dir(options.download_dir)
with self._build_session(options) as session:
finder = self._build_package_finder(options, session)
build_delete = (not (options.no_clean or options.build_dir))
if options.cache_dir and not check_path_owner(options.cache_dir):
logger.warning(
"The directory '%s' or its parent directory is not owned "
"by the current user and caching wheels has been "
"disabled. check the permissions and owner of that "
"directory. If executing pip with sudo, you may want "
"sudo's -H flag.",
options.cache_dir,
)
options.cache_dir = None
with BuildDirectory(options.build_dir,
delete=build_delete) as build_dir:
requirement_set = RequirementSet(
build_dir=build_dir,
src_dir=options.src_dir,
download_dir=options.download_dir,
ignore_installed=True,
ignore_dependencies=options.ignore_dependencies,
session=session,
isolated=options.isolated_mode,
)
self.populate_requirement_set(
requirement_set,
args,
options,
finder,
session,
self.name,
None
)
if not requirement_set.has_requirements:
return
requirement_set.prepare_files(finder)
downloaded = ' '.join([
req.name for req in requirement_set.successfully_downloaded
])
if downloaded:
logger.info(
'Successfully downloaded %s', downloaded
)
# Clean up
if not options.no_clean:
requirement_set.cleanup_files()
return requirement_set

View file

@ -14,7 +14,6 @@ except ImportError:
from pip.req import RequirementSet
from pip.basecommand import RequirementCommand
from pip.locations import virtualenv_no_global, distutils_scheme
from pip.index import PackageFinder
from pip.exceptions import (
InstallationError, CommandError, PreviousBuildDirError,
)
@ -168,22 +167,6 @@ class InstallCommand(RequirementCommand):
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
def _build_package_finder(self, options, index_urls, session):
"""
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,
format_control=options.format_control,
index_urls=index_urls,
trusted_hosts=options.trusted_hosts,
allow_all_prereleases=options.pre,
process_dependency_links=options.process_dependency_links,
session=session,
)
def run(self, options, args):
cmdoptions.resolve_wheel_no_use_binary(options)
cmdoptions.check_install_build_global(options)
@ -213,6 +196,12 @@ class InstallCommand(RequirementCommand):
)
if options.download_dir:
warnings.warn(
"pip install --download has been deprecated and will be "
"removed in the future. Pip now has a download command that "
"should be used instead.",
RemovedInPip10Warning,
)
options.ignore_installed = True
if options.build_dir:
@ -243,14 +232,10 @@ class InstallCommand(RequirementCommand):
install_options.append('--home=' + temp_target_dir)
global_options = options.global_options or []
index_urls = [options.index_url] + options.extra_index_urls
if options.no_index:
logger.info('Ignoring indexes: %s', ','.join(index_urls))
index_urls = []
with self._build_session(options) as session:
finder = self._build_package_finder(options, index_urls, session)
finder = self._build_package_finder(options, session)
build_delete = (not (options.no_clean or options.build_dir))
wheel_cache = WheelCache(options.cache_dir, options.format_control)
if options.cache_dir and not check_path_owner(options.cache_dir):

View file

@ -6,7 +6,6 @@ import os
import warnings
from pip.basecommand import RequirementCommand
from pip.index import PackageFinder
from pip.exceptions import CommandError, PreviousBuildDirError
from pip.req import RequirementSet
from pip.utils import import_or_raise, normalize_path
@ -161,16 +160,7 @@ class WheelCommand(RequirementCommand):
with self._build_session(options) as session:
finder = PackageFinder(
find_links=options.find_links,
format_control=options.format_control,
index_urls=index_urls,
allow_all_prereleases=options.pre,
trusted_hosts=options.trusted_hosts,
process_dependency_links=options.process_dependency_links,
session=session,
)
finder = self._build_package_finder(options, session)
build_delete = (not (options.no_clean or options.build_dir))
wheel_cache = WheelCache(options.cache_dir, options.format_control)
with BuildDirectory(options.build_dir,

View file

@ -0,0 +1,162 @@
import os
import textwrap
import pytest
from tests.lib.path import Path
@pytest.mark.network
def test_download_if_requested(script):
"""
It should download (in the scratch path) and not install if requested.
"""
result = script.pip(
'download', '-d', '.', 'INITools==0.1', expect_error=True
)
assert Path('scratch') / 'INITools-0.1.tar.gz' in result.files_created
assert script.site_packages / 'initools' not in result.files_created
@pytest.mark.network
def test_download_setuptools(script):
"""
It should download (in the scratch path) and not install if requested.
"""
result = script.pip('download', 'setuptools')
setuptools_prefix = str(
Path('scratch') / 'pip_downloads' / 'setuptools'
)
assert any(
path.startswith(setuptools_prefix) for path in result.files_created
)
@pytest.mark.network
def test_download_wheel(script):
"""
Test using "pip download" to download a *.whl archive.
FIXME: this test could use a local --find-links dir, but -d with local
--find-links has a bug https://github.com/pypa/pip/issues/1111
"""
result = script.pip(
'download',
'-f', 'https://bitbucket.org/pypa/pip-test-package/downloads',
'-d', '.', 'pip-test-package'
)
assert (
Path('scratch') / 'pip_test_package-0.1.1-py2.py3-none-any.whl'
in result.files_created
)
assert script.site_packages / 'piptestpackage' not in result.files_created
@pytest.mark.network
def test_single_download_from_requirements_file(script):
"""
It should support download (in the scratch path) from PyPi from a
requirements file
"""
script.scratch_path.join("test-req.txt").write(textwrap.dedent("""
INITools==0.1
"""))
result = script.pip(
'download', '-r', script.scratch_path / 'test-req.txt', '-d', '.',
expect_error=True,
)
assert Path('scratch') / 'INITools-0.1.tar.gz' in result.files_created
assert script.site_packages / 'initools' not in result.files_created
@pytest.mark.network
def test_download_should_download_dependencies(script):
"""
It should download dependencies (in the scratch path)
"""
result = script.pip(
'download', 'Paste[openid]==1.7.5.1', '-d', '.', expect_error=True,
)
assert Path('scratch') / 'Paste-1.7.5.1.tar.gz' in result.files_created
openid_tarball_prefix = str(Path('scratch') / 'python-openid-')
assert any(
path.startswith(openid_tarball_prefix) for path in result.files_created
)
assert script.site_packages / 'openid' not in result.files_created
def test_download_wheel_archive(script, data):
"""
It should download a wheel archive path
"""
wheel_filename = 'colander-0.9.9-py2.py3-none-any.whl'
wheel_path = os.path.join(data.find_links, wheel_filename)
result = script.pip(
'download', wheel_path,
'-d', '.', '--no-deps'
)
assert Path('scratch') / wheel_filename in result.files_created
def test_download_should_download_wheel_deps(script, data):
"""
It should download dependencies for wheels(in the scratch path)
"""
wheel_filename = 'colander-0.9.9-py2.py3-none-any.whl'
dep_filename = 'translationstring-1.1.tar.gz'
wheel_path = os.path.join(data.find_links, wheel_filename)
result = script.pip(
'download', wheel_path,
'-d', '.', '--find-links', data.find_links, '--no-index'
)
assert Path('scratch') / wheel_filename in result.files_created
assert Path('scratch') / dep_filename in result.files_created
@pytest.mark.network
def test_download_should_skip_existing_files(script):
"""
It should not download files already existing in the scratch dir
"""
script.scratch_path.join("test-req.txt").write(textwrap.dedent("""
INITools==0.1
"""))
result = script.pip(
'download', '-r', script.scratch_path / 'test-req.txt', '-d', '.',
expect_error=True,
)
assert Path('scratch') / 'INITools-0.1.tar.gz' in result.files_created
assert script.site_packages / 'initools' not in result.files_created
# adding second package to test-req.txt
script.scratch_path.join("test-req.txt").write(textwrap.dedent("""
INITools==0.1
python-openid==2.2.5
"""))
# only the second package should be downloaded
result = script.pip(
'download', '-r', script.scratch_path / 'test-req.txt', '-d', '.',
expect_error=True,
)
openid_tarball_prefix = str(Path('scratch') / 'python-openid-')
assert any(
path.startswith(openid_tarball_prefix) for path in result.files_created
)
assert Path('scratch') / 'INITools-0.1.tar.gz' not in result.files_created
assert script.site_packages / 'initools' not in result.files_created
assert script.site_packages / 'openid' not in result.files_created
@pytest.mark.network
def test_download_vcs_link(script):
"""
It should allow -d flag for vcs links, regression test for issue #798.
"""
result = script.pip(
'download', '-d', '.', 'git+git://github.com/pypa/pip-test-package.git'
)
assert (
Path('scratch') / 'pip-test-package-0.1.1.zip'
in result.files_created
)
assert script.site_packages / 'piptestpackage' not in result.files_created

View file

@ -111,6 +111,7 @@ def test_download_editable_to_custom_path(script, tmpdir):
'customsrc',
'--download',
'customdl',
expect_stderr=True
)
customsrc = Path('scratch') / 'customsrc' / 'initools'
assert customsrc in result.files_created, (
@ -126,6 +127,9 @@ def test_download_editable_to_custom_path(script, tmpdir):
if filename.startswith(customdl)
]
assert customdl_files_created
assert ('DEPRECATION: pip install --download has been deprecated and will '
'be removed in the future. Pip now has a download command that '
'should be used instead.') in result.stderr
@pytest.mark.network

View file

@ -27,7 +27,8 @@ def test_download_wheel(script):
result = script.pip(
'install',
'-f', 'https://bitbucket.org/pypa/pip-test-package/downloads',
'-d', '.', 'pip-test-package'
'-d', '.', 'pip-test-package',
expect_stderr=True,
)
assert (
Path('scratch') / 'pip_test_package-0.1.1-py2.py3-none-any.whl'
@ -77,7 +78,8 @@ def test_download_wheel_archive(script, data):
wheel_path = os.path.join(data.find_links, wheel_filename)
result = script.pip(
'install', wheel_path,
'-d', '.', '--no-deps'
'-d', '.', '--no-deps',
expect_stderr=True,
)
assert Path('scratch') / wheel_filename in result.files_created
@ -91,7 +93,8 @@ def test_download_should_download_wheel_deps(script, data):
wheel_path = os.path.join(data.find_links, wheel_filename)
result = script.pip(
'install', wheel_path,
'-d', '.', '--find-links', data.find_links, '--no-index'
'-d', '.', '--find-links', data.find_links, '--no-index',
expect_stderr=True,
)
assert Path('scratch') / wheel_filename in result.files_created
assert Path('scratch') / dep_filename in result.files_created
@ -139,7 +142,8 @@ def test_download_vcs_link(script):
It should allow -d flag for vcs links, regression test for issue #798.
"""
result = script.pip(
'install', '-d', '.', 'git+git://github.com/pypa/pip-test-package.git'
'install', '-d', '.', 'git+git://github.com/pypa/pip-test-package.git',
expect_stderr=True,
)
assert (
Path('scratch') / 'pip-test-package-0.1.1.zip'

View file

@ -329,7 +329,7 @@ class PipTestEnvironment(scripttest.TestFileEnvironment):
# about the lack of an SSLContext. Expect it when running commands
# that will touch the outside world.
if (pyversion_tuple < (2, 7, 9) and
args and args[0] in ('search', 'install')):
args and args[0] in ('search', 'install', 'download')):
kwargs['expect_stderr'] = True
return self.run("pip", *args, **kwargs)