Skip to content

Commit 0b1acf9

Browse files
committed
Merge pull request #3085 from patricklaw/add-pip-download-command
Add `pip download` command and deprecate `pip install --download`.
2 parents 11d96fb + 417f79d commit 0b1acf9

File tree

13 files changed

+407
-40
lines changed

13 files changed

+407
-40
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
* Allow repository URLs with secure transports to count as trusted. (E.g.,
2424
"git+ssh" is okay.) :issue:`2811`.
2525

26+
* Implement a top-level ``pip download`` command and deprecate
27+
``pip install --download``.
28+
2629

2730
**7.1.2 (2015-08-22)**
2831

docs/reference/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Reference Guide
77

88
pip
99
pip_install
10+
pip_download
1011
pip_uninstall
1112
pip_freeze
1213
pip_list

docs/reference/pip_download.rst

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
2+
.. _`pip download`:
3+
4+
pip download
5+
------------
6+
7+
.. contents::
8+
9+
Usage
10+
*****
11+
12+
.. pip-command-usage:: download
13+
14+
15+
Description
16+
***********
17+
18+
.. pip-command-description:: download
19+
20+
21+
Overview
22+
++++++++
23+
``pip download`` replaces the ``--download`` option to ``pip install``,
24+
which is now deprecated and will be removed in pip 10.
25+
26+
``pip download`` does the same resolution and downloading as ``pip install``,
27+
but instead of installing the dependencies, it collects the downloaded
28+
distributions into the directory provided (defaulting to ``./pip_downloads``).
29+
This directory can later be passed as the value to
30+
``pip install --find-links`` to facilitate offline or locked down package
31+
installation.
32+
33+
34+
Options
35+
*******
36+
37+
.. pip-command-options:: download
38+
39+
.. pip-index-options::
40+
41+
42+
Examples
43+
********
44+
45+
1. Download a package and all of its dependencies
46+
47+
::
48+
49+
$ pip download -d ./pip_downloads SomePackage
50+
$ pip download SomePackage # equivalent to above
51+
$ pip download --no-index --find-links=/tmp/wheelhouse -d /tmp/otherwheelhouse SomePackage
52+
53+

pip/basecommand.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import optparse
88

99
from pip import cmdoptions
10+
from pip.index import PackageFinder
1011
from pip.locations import running_under_virtualenv
1112
from pip.download import PipSession
1213
from pip.exceptions import (BadCommand, InstallationError, UninstallationError,
1314
CommandError, PreviousBuildDirError)
15+
1416
from pip.compat import logging_dictConfig
1517
from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
1618
from pip.req import InstallRequirement, parse_requirements
@@ -290,3 +292,22 @@ def populate_requirement_set(requirement_set, args, options, finder,
290292
msg = ('You must give at least one requirement '
291293
'to %(name)s (see "pip help %(name)s")' % opts)
292294
logger.warning(msg)
295+
296+
def _build_package_finder(self, options, session):
297+
"""
298+
Create a package finder appropriate to this requirement command.
299+
"""
300+
index_urls = [options.index_url] + options.extra_index_urls
301+
if options.no_index:
302+
logger.info('Ignoring indexes: %s', ','.join(index_urls))
303+
index_urls = []
304+
305+
return PackageFinder(
306+
find_links=options.find_links,
307+
format_control=options.format_control,
308+
index_urls=index_urls,
309+
trusted_hosts=options.trusted_hosts,
310+
allow_all_prereleases=options.pre,
311+
process_dependency_links=options.process_dependency_links,
312+
session=session,
313+
)

pip/cmdoptions.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,18 +553,24 @@ def only_binary():
553553
]
554554
}
555555

556-
index_group = {
556+
non_deprecated_index_group = {
557557
'name': 'Package Index Options',
558558
'options': [
559559
index_url,
560560
extra_index_url,
561561
no_index,
562562
find_links,
563+
process_dependency_links,
564+
]
565+
}
566+
567+
index_group = {
568+
'name': 'Package Index Options (including deprecated options)',
569+
'options': non_deprecated_index_group['options'] + [
563570
allow_external,
564571
allow_all_external,
565572
no_allow_external,
566573
allow_unsafe,
567574
no_allow_unsafe,
568-
process_dependency_links,
569575
]
570576
}

pip/commands/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import absolute_import
55

66
from pip.commands.completion import CompletionCommand
7+
from pip.commands.download import DownloadCommand
78
from pip.commands.freeze import FreezeCommand
89
from pip.commands.help import HelpCommand
910
from pip.commands.list import ListCommand
@@ -22,13 +23,15 @@
2223
ShowCommand.name: ShowCommand,
2324
InstallCommand.name: InstallCommand,
2425
UninstallCommand.name: UninstallCommand,
26+
DownloadCommand.name: DownloadCommand,
2527
ListCommand.name: ListCommand,
2628
WheelCommand.name: WheelCommand,
2729
}
2830

2931

3032
commands_order = [
3133
InstallCommand,
34+
DownloadCommand,
3235
UninstallCommand,
3336
FreezeCommand,
3437
ListCommand,

pip/commands/download.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from __future__ import absolute_import
2+
3+
import logging
4+
import os
5+
6+
from pip.req import RequirementSet
7+
from pip.basecommand import RequirementCommand
8+
from pip import cmdoptions
9+
from pip.utils import ensure_dir, normalize_path
10+
from pip.utils.build import BuildDirectory
11+
from pip.utils.filesystem import check_path_owner
12+
13+
14+
DEFAULT_DOWNLOAD_DIR = os.path.join(normalize_path(os.curdir), 'pip_downloads')
15+
16+
17+
logger = logging.getLogger(__name__)
18+
19+
20+
class DownloadCommand(RequirementCommand):
21+
"""
22+
Download packages from:
23+
24+
- PyPI (and other indexes) using requirement specifiers.
25+
- VCS project urls.
26+
- Local project directories.
27+
- Local or remote source archives.
28+
29+
pip also supports downloading from "requirements files", which provide
30+
an easy way to specify a whole environment to be downloaded.
31+
"""
32+
name = 'download'
33+
34+
usage = """
35+
%prog [options] <requirement specifier> [package-index-options] ...
36+
%prog [options] -r <requirements file> [package-index-options] ...
37+
%prog [options] [-e] <vcs project url> ...
38+
%prog [options] [-e] <local project path> ...
39+
%prog [options] <archive url/path> ..."""
40+
41+
summary = 'Download packages.'
42+
43+
def __init__(self, *args, **kw):
44+
super(DownloadCommand, self).__init__(*args, **kw)
45+
46+
cmd_opts = self.cmd_opts
47+
48+
cmd_opts.add_option(cmdoptions.constraints())
49+
cmd_opts.add_option(cmdoptions.editable())
50+
cmd_opts.add_option(cmdoptions.requirements())
51+
cmd_opts.add_option(cmdoptions.build_dir())
52+
cmd_opts.add_option(cmdoptions.no_deps())
53+
cmd_opts.add_option(cmdoptions.global_options())
54+
cmd_opts.add_option(cmdoptions.no_binary())
55+
cmd_opts.add_option(cmdoptions.only_binary())
56+
cmd_opts.add_option(cmdoptions.src())
57+
cmd_opts.add_option(cmdoptions.no_clean())
58+
cmd_opts.add_option(cmdoptions.pre())
59+
60+
cmd_opts.add_option(
61+
'-d', '--dest', '--destination-dir', '--destination-directory',
62+
dest='download_dir',
63+
metavar='dir',
64+
default=DEFAULT_DOWNLOAD_DIR,
65+
help=("Download packages into <dir>."),
66+
)
67+
68+
index_opts = cmdoptions.make_option_group(
69+
cmdoptions.non_deprecated_index_group,
70+
self.parser,
71+
)
72+
73+
self.parser.insert_option_group(0, index_opts)
74+
self.parser.insert_option_group(0, cmd_opts)
75+
76+
def run(self, options, args):
77+
options.ignore_installed = True
78+
options.src_dir = os.path.abspath(options.src_dir)
79+
ensure_dir(options.download_dir)
80+
81+
with self._build_session(options) as session:
82+
83+
finder = self._build_package_finder(options, session)
84+
build_delete = (not (options.no_clean or options.build_dir))
85+
if options.cache_dir and not check_path_owner(options.cache_dir):
86+
logger.warning(
87+
"The directory '%s' or its parent directory is not owned "
88+
"by the current user and caching wheels has been "
89+
"disabled. check the permissions and owner of that "
90+
"directory. If executing pip with sudo, you may want "
91+
"sudo's -H flag.",
92+
options.cache_dir,
93+
)
94+
options.cache_dir = None
95+
96+
with BuildDirectory(options.build_dir,
97+
delete=build_delete) as build_dir:
98+
99+
requirement_set = RequirementSet(
100+
build_dir=build_dir,
101+
src_dir=options.src_dir,
102+
download_dir=options.download_dir,
103+
ignore_installed=True,
104+
ignore_dependencies=options.ignore_dependencies,
105+
session=session,
106+
isolated=options.isolated_mode,
107+
)
108+
self.populate_requirement_set(
109+
requirement_set,
110+
args,
111+
options,
112+
finder,
113+
session,
114+
self.name,
115+
None
116+
)
117+
118+
if not requirement_set.has_requirements:
119+
return
120+
121+
requirement_set.prepare_files(finder)
122+
123+
downloaded = ' '.join([
124+
req.name for req in requirement_set.successfully_downloaded
125+
])
126+
if downloaded:
127+
logger.info(
128+
'Successfully downloaded %s', downloaded
129+
)
130+
131+
# Clean up
132+
if not options.no_clean:
133+
requirement_set.cleanup_files()
134+
135+
return requirement_set

pip/commands/install.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from pip.req import RequirementSet
1515
from pip.basecommand import RequirementCommand
1616
from pip.locations import virtualenv_no_global, distutils_scheme
17-
from pip.index import PackageFinder
1817
from pip.exceptions import (
1918
InstallationError, CommandError, PreviousBuildDirError,
2019
)
@@ -168,22 +167,6 @@ def __init__(self, *args, **kw):
168167
self.parser.insert_option_group(0, index_opts)
169168
self.parser.insert_option_group(0, cmd_opts)
170169

171-
def _build_package_finder(self, options, index_urls, session):
172-
"""
173-
Create a package finder appropriate to this install command.
174-
This method is meant to be overridden by subclasses, not
175-
called directly.
176-
"""
177-
return PackageFinder(
178-
find_links=options.find_links,
179-
format_control=options.format_control,
180-
index_urls=index_urls,
181-
trusted_hosts=options.trusted_hosts,
182-
allow_all_prereleases=options.pre,
183-
process_dependency_links=options.process_dependency_links,
184-
session=session,
185-
)
186-
187170
def run(self, options, args):
188171
cmdoptions.resolve_wheel_no_use_binary(options)
189172
cmdoptions.check_install_build_global(options)
@@ -213,6 +196,12 @@ def run(self, options, args):
213196
)
214197

215198
if options.download_dir:
199+
warnings.warn(
200+
"pip install --download has been deprecated and will be "
201+
"removed in the future. Pip now has a download command that "
202+
"should be used instead.",
203+
RemovedInPip10Warning,
204+
)
216205
options.ignore_installed = True
217206

218207
if options.build_dir:
@@ -243,14 +232,10 @@ def run(self, options, args):
243232
install_options.append('--home=' + temp_target_dir)
244233

245234
global_options = options.global_options or []
246-
index_urls = [options.index_url] + options.extra_index_urls
247-
if options.no_index:
248-
logger.info('Ignoring indexes: %s', ','.join(index_urls))
249-
index_urls = []
250235

251236
with self._build_session(options) as session:
252237

253-
finder = self._build_package_finder(options, index_urls, session)
238+
finder = self._build_package_finder(options, session)
254239
build_delete = (not (options.no_clean or options.build_dir))
255240
wheel_cache = WheelCache(options.cache_dir, options.format_control)
256241
if options.cache_dir and not check_path_owner(options.cache_dir):

pip/commands/wheel.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import warnings
77

88
from pip.basecommand import RequirementCommand
9-
from pip.index import PackageFinder
109
from pip.exceptions import CommandError, PreviousBuildDirError
1110
from pip.req import RequirementSet
1211
from pip.utils import import_or_raise, normalize_path
@@ -161,16 +160,7 @@ def run(self, options, args):
161160

162161
with self._build_session(options) as session:
163162

164-
finder = PackageFinder(
165-
find_links=options.find_links,
166-
format_control=options.format_control,
167-
index_urls=index_urls,
168-
allow_all_prereleases=options.pre,
169-
trusted_hosts=options.trusted_hosts,
170-
process_dependency_links=options.process_dependency_links,
171-
session=session,
172-
)
173-
163+
finder = self._build_package_finder(options, session)
174164
build_delete = (not (options.no_clean or options.build_dir))
175165
wheel_cache = WheelCache(options.cache_dir, options.format_control)
176166
with BuildDirectory(options.build_dir,

0 commit comments

Comments
 (0)