-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Implement a "pip upgrade" command #3194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
39e0f27
2de71f0
ac1cfee
d62535b
9e57de7
a18c32a
f030eeb
7b32fab
4ee0fc3
f6619df
90a50ff
476cfd3
3cc01f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,31 +28,16 @@ | |
logger = logging.getLogger(__name__) | ||
|
||
|
||
class InstallCommand(RequirementCommand): | ||
""" | ||
Install packages from: | ||
|
||
- PyPI (and other indexes) using requirement specifiers. | ||
- VCS project urls. | ||
- Local project directories. | ||
- Local or remote source archives. | ||
|
||
pip also supports installing from "requirements files", which provide | ||
an easy way to specify a whole environment to be installed. | ||
""" | ||
name = 'install' | ||
|
||
class InstallBase(RequirementCommand): | ||
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 = 'Install packages.' | ||
|
||
def __init__(self, *args, **kw): | ||
super(InstallCommand, self).__init__(*args, **kw) | ||
super(InstallBase, self).__init__(*args, **kw) | ||
|
||
cmd_opts = self.cmd_opts | ||
|
||
|
@@ -83,14 +68,15 @@ def __init__(self, *args, **kw): | |
|
||
cmd_opts.add_option(cmdoptions.src()) | ||
|
||
cmd_opts.add_option( | ||
'-U', '--upgrade', | ||
dest='upgrade', | ||
action='store_true', | ||
help='Upgrade all specified packages to the newest available ' | ||
'version. This process is recursive regardless of whether ' | ||
'a dependency is already satisfied.' | ||
) | ||
if self.upgrade_option: | ||
cmd_opts.add_option( | ||
'-U', '--upgrade', | ||
dest='upgrade', | ||
action='store_true', | ||
help='Upgrade all specified packages to the newest available ' | ||
'version. This process is recursive regardless of ' | ||
'whether a dependency is already satisfied.' | ||
) | ||
|
||
cmd_opts.add_option( | ||
'--force-reinstall', | ||
|
@@ -255,7 +241,8 @@ def run(self, options, args): | |
build_dir=build_dir, | ||
src_dir=options.src_dir, | ||
download_dir=options.download_dir, | ||
upgrade=options.upgrade, | ||
upgrade=self.upgrade_option and options.upgrade, | ||
upgrade_direct=self.upgrade_direct, | ||
as_egg=options.as_egg, | ||
ignore_installed=options.ignore_installed, | ||
ignore_dependencies=options.ignore_dependencies, | ||
|
@@ -369,3 +356,31 @@ def run(self, options, args): | |
) | ||
shutil.rmtree(temp_target_dir) | ||
return requirement_set | ||
|
||
|
||
class InstallCommand(InstallBase): | ||
""" | ||
Install packages from: | ||
|
||
- PyPI (and other indexes) using requirement specifiers. | ||
- VCS project urls. | ||
- Local project directories. | ||
- Local or remote source archives. | ||
|
||
pip also supports installing from "requirements files", which provide | ||
an easy way to specify a whole environment to be installed. | ||
""" | ||
name = 'install' | ||
summary = 'Install packages.' | ||
upgrade_option = True | ||
upgrade_direct = False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this may be bikeshedding, but this "upgrade_direct" phrase is pretty meaningless to me. I'd prefer "upgrade_recursive", which will be True for the InstallCommand, and False for the UpgradeCommand |
||
|
||
|
||
class UpgradeCommand(InstallBase): | ||
""" | ||
Upgrade packages, with minimal upgrades of dependencies. | ||
""" | ||
name = 'upgrade' | ||
summary = 'Upgrade packages.' | ||
upgrade_option = False | ||
upgrade_direct = True |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -137,6 +137,7 @@ def prep_for_dist(self): | |
class RequirementSet(object): | ||
|
||
def __init__(self, build_dir, src_dir, download_dir, upgrade=False, | ||
upgrade_direct=False, | ||
ignore_installed=False, as_egg=False, target_dir=None, | ||
ignore_dependencies=False, force_reinstall=False, | ||
use_user_site=False, session=None, pycompile=True, | ||
|
@@ -167,6 +168,7 @@ def __init__(self, build_dir, src_dir, download_dir, upgrade=False, | |
# the wheelhouse output by 'pip wheel'. | ||
self.download_dir = download_dir | ||
self.upgrade = upgrade | ||
self.upgrade_direct = upgrade_direct | ||
self.ignore_installed = ignore_installed | ||
self.force_reinstall = force_reinstall | ||
self.requirements = Requirements() | ||
|
@@ -227,6 +229,7 @@ def add_requirement(self, install_req, parent_req_name=None): | |
install_req.use_user_site = self.use_user_site | ||
install_req.target_dir = self.target_dir | ||
install_req.pycompile = self.pycompile | ||
install_req.direct = (parent_req_name is None) | ||
if not name: | ||
# url or path requirement w/o an egg fragment | ||
self.unnamed_requirements.append(install_req) | ||
|
@@ -366,14 +369,16 @@ def _check_skip_installed(self, req_to_install, finder): | |
req_to_install.check_if_exists() | ||
if req_to_install.satisfied_by: | ||
skip_reason = 'satisfied (use --upgrade to upgrade)' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should say |
||
if self.upgrade: | ||
upgrade = self.upgrade or ( | ||
self.upgrade_direct and req_to_install.direct) | ||
if upgrade: | ||
best_installed = False | ||
# For link based requirements we have to pull the | ||
# tree down and inspect to assess the version #, so | ||
# its handled way down. | ||
if not (self.force_reinstall or req_to_install.link): | ||
try: | ||
finder.find_requirement(req_to_install, self.upgrade) | ||
finder.find_requirement(req_to_install, upgrade) | ||
except BestVersionAlreadyInstalled: | ||
skip_reason = 'up-to-date' | ||
best_installed = True | ||
|
@@ -408,6 +413,8 @@ def _prepare_file(self, finder, req_to_install): | |
return [] | ||
|
||
req_to_install.prepared = True | ||
upgrade = self.upgrade or ( | ||
self.upgrade_direct and req_to_install.direct) | ||
|
||
if req_to_install.editable: | ||
logger.info('Obtaining %s', req_to_install) | ||
|
@@ -469,7 +476,7 @@ def _prepare_file(self, finder, req_to_install): | |
"can delete this. Please delete it and try again." | ||
% (req_to_install, req_to_install.source_dir) | ||
) | ||
req_to_install.populate_link(finder, self.upgrade) | ||
req_to_install.populate_link(finder, upgrade) | ||
# We can't hit this spot and have populate_link return None. | ||
# req_to_install.satisfied_by is None here (because we're | ||
# guarded) and upgrade has no impact except when satisfied_by | ||
|
@@ -524,7 +531,7 @@ def _prepare_file(self, finder, req_to_install): | |
if not self.ignore_installed: | ||
req_to_install.check_if_exists() | ||
if req_to_install.satisfied_by: | ||
if self.upgrade or self.ignore_installed: | ||
if upgrade or self.ignore_installed: | ||
# don't uninstall conflict if user install and | ||
# conflict is not user install | ||
if not (self.use_user_site and not | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import os | ||
import pytest | ||
from tests.lib import pyversion | ||
|
||
|
||
# The package "requirement" has versions 1.5, 1.6 and 1.7 available. | ||
# The package "application" has versions 1.9, 2.0 and 2.1 available, | ||
# and 2.0 depends on requirement>=1.5 and 2.1 on requirement>=1.6. | ||
# This is intended to resemble the situation with numpy, where | ||
# several current packages depend on 1.6 or newer, leading | ||
# "pip install -U package" to attempt to install some newer version. | ||
|
||
@pytest.mark.network | ||
def test_upgrade_not_recursive(script, data): | ||
""" | ||
If requirement-1.6 is already installed, upgrading application | ||
should not upgrade requirement. | ||
""" | ||
script.pip('install', '--no-index', '-f', data.find_links, | ||
'requirement==1.6', 'application==2.0') | ||
result = script.pip('upgrade', '--no-index', '-f', data.find_links, | ||
'application') | ||
assert ( | ||
script.site_packages / 'application-2.0-py%s.egg-info' % pyversion | ||
in result.files_deleted | ||
) | ||
assert ( | ||
script.site_packages / 'application-2.1-py%s.egg-info' % pyversion | ||
in result.files_created | ||
) | ||
assert ( | ||
'requirement-1.6-py%s.egg-info' % pyversion | ||
in os.listdir(script.site_packages_path) | ||
) | ||
|
||
|
||
@pytest.mark.network | ||
def test_upgrade_recursive_when_needed(script, data): | ||
""" | ||
If only requirement-1.5 is installed, upgrading application | ||
should upgrade requirement to the newest available version. | ||
""" | ||
script.pip('install', '--no-index', '-f', data.find_links, | ||
'requirement==1.5', 'application==2.0') | ||
result = script.pip('upgrade', '--no-index', '-f', data.find_links, | ||
'application') | ||
assert ( | ||
script.site_packages / 'application-2.0-py%s.egg-info' % pyversion | ||
in result.files_deleted | ||
) | ||
assert ( | ||
script.site_packages / 'application-2.1-py%s.egg-info' % pyversion | ||
in result.files_created | ||
) | ||
assert ( | ||
script.site_packages / 'requirement-1.5-py%s.egg-info' % pyversion | ||
in result.files_deleted | ||
) | ||
assert ( | ||
script.site_packages / 'requirement-1.7-py%s.egg-info' % pyversion | ||
in result.files_created | ||
) | ||
|
||
|
||
@pytest.mark.network | ||
def test_upgrade_installs_when_needed(script, data): | ||
""" | ||
If requirement is not installed, upgrading application should | ||
install the newest available version. | ||
""" | ||
script.pip('install', '--no-index', '-f', data.find_links, | ||
'application==1.9') | ||
result = script.pip('upgrade', '--no-index', '-f', data.find_links, | ||
'application') | ||
assert ( | ||
script.site_packages / 'application-1.9-py%s.egg-info' % pyversion | ||
in result.files_deleted | ||
) | ||
assert ( | ||
script.site_packages / 'application-2.1-py%s.egg-info' % pyversion | ||
in result.files_created | ||
) | ||
assert ( | ||
script.site_packages / 'requirement-1.7-py%s.egg-info' % pyversion | ||
in result.files_created | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, maybe bikeshedding, here, but I want the RequirementSet properties to be meaningful for the long term
what we're doing here is keeping the general word "upgrade" to refer to the old style "recursive upgrade", and then tacking on a new property "upgrade_direct" for the new non-recursive type.
maybe that allows for a smaller PR diff, but I care more about readability.
to me the most readable properties for the Set would be:
upgrade
: whether it's an upgrade of any kindupgrade_recursive
: is it recursive or not