Skip to content
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

Canonicalize req name while doing pre-install package search #8054

Merged
merged 6 commits into from
Jul 6, 2020
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/5021.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use canonical package names while looking up already installed packages.
4 changes: 2 additions & 2 deletions src/pip/_internal/commands/search.py
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@
from pip._internal.network.xmlrpc import PipXmlrpcTransport
from pip._internal.utils.compat import get_terminal_size
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import write_output
from pip._internal.utils.misc import get_distribution, write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

if MYPY_CHECK_RUNNING:
@@ -139,7 +139,7 @@ def print_results(hits, name_column_width=None, terminal_width=None):
try:
write_output(line)
if name in installed_packages:
dist = pkg_resources.get_distribution(name)
dist = get_distribution(name)
with indent_log():
if dist.version == latest:
write_output('INSTALLED: %s (latest)', dist.version)
16 changes: 10 additions & 6 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@
display_path,
dist_in_site_packages,
dist_in_usersite,
get_distribution,
get_installed_version,
hide_url,
redact_auth_from_url,
@@ -434,12 +435,17 @@ def check_if_exists(self, use_user_site):
# evaluate it.
no_marker = Requirement(str(self.req))
no_marker.marker = None

# pkg_resources uses the canonical name to look up packages, but
# the name passed passed to get_distribution is not canonicalized
# so we have to explicitly convert it to a canonical name
no_marker.name = canonicalize_name(no_marker.name)
try:
self.satisfied_by = pkg_resources.get_distribution(str(no_marker))
except pkg_resources.DistributionNotFound:
return
except pkg_resources.VersionConflict:
existing_dist = pkg_resources.get_distribution(
existing_dist = get_distribution(
self.req.name
)
if use_user_site:
@@ -679,13 +685,11 @@ def uninstall(self, auto_confirm=False, verbose=False):
"""
assert self.req
try:
dist = pkg_resources.get_distribution(self.req.name)
except pkg_resources.DistributionNotFound:
dist = get_distribution(self.req.name)
if not dist:
logger.warning("Skipping %s as it is not installed.", self.name)
return None
else:
logger.info('Found existing installation: %s', dist)
logger.info('Found existing installation: %s', dist)

uninstalled_pathset = UninstallPathSet.from_dist(dist)
uninstalled_pathset.remove(auto_confirm, verbose)
14 changes: 8 additions & 6 deletions src/pip/_internal/self_outdated_check.py
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@
import os.path
import sys

from pip._vendor import pkg_resources
from pip._vendor.packaging import version as packaging_version
from pip._vendor.six import ensure_binary

@@ -19,7 +18,11 @@
check_path_owner,
replace,
)
from pip._internal.utils.misc import ensure_dir, get_installed_version
from pip._internal.utils.misc import (
ensure_dir,
get_distribution,
get_installed_version,
)
from pip._internal.utils.packaging import get_installer
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

@@ -110,11 +113,10 @@ def was_installed_by_pip(pkg):
This is used not to display the upgrade message when pip is in fact
installed by system package manager, such as dnf on Fedora.
"""
try:
dist = pkg_resources.get_distribution(pkg)
return "pip" == get_installer(dist)
except pkg_resources.DistributionNotFound:
dist = get_distribution(pkg)
if not dist:
return False
return "pip" == get_installer(dist)


def pip_self_version_check(session, options):
35 changes: 35 additions & 0 deletions src/pip/_internal/utils/misc.py
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
from pip._vendor import pkg_resources
# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
# why we ignore the type on this import.
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.retrying import retry # type: ignore
from pip._vendor.six import PY2, text_type
from pip._vendor.six.moves import input, map, zip_longest
@@ -480,6 +481,40 @@ def user_test(d):
]


def search_distribution(req_name):

# Canonicalize the name before searching in the list of
# installed distributions and also while creating the package
# dictionary to get the Distribution object
req_name = canonicalize_name(req_name)
packages = get_installed_distributions(skip=())
pkg_dict = {canonicalize_name(p.key): p for p in packages}
return pkg_dict.get(req_name)


def get_distribution(req_name):
"""Given a requirement name, return the installed Distribution object"""

# Search the distribution by looking through the working set
dist = search_distribution(req_name)

# If distribution could not be found, call working_set.require
# to update the working set, and try to find the distribution
# again.
# This might happen for e.g. when you install a package
# twice, once using setup.py develop and again using setup.py install.
# Now when run pip uninstall twice, the package gets removed
# from the working set in the first uninstall, so we have to populate
# the working set again so that pip knows about it and the packages
# gets picked up and is successfully uninstalled the second time too.
if not dist:
try:
pkg_resources.working_set.require(req_name)
except pkg_resources.DistributionNotFound:
return None
return search_distribution(req_name)


def egg_link_path(dist):
# type: (Distribution) -> Optional[str]
"""
20 changes: 20 additions & 0 deletions tests/functional/test_install.py
Original file line number Diff line number Diff line change
@@ -1882,3 +1882,23 @@ def test_install_skip_work_dir_pkg(script, data):

assert 'Requirement already satisfied: simple' not in result.stdout
assert 'Successfully installed simple' in result.stdout


@pytest.mark.parametrize('package_name', ('simple-package', 'simple_package',
'simple.package'))
def test_install_verify_package_name_normalization(script, package_name):

"""
Test that install of a package again using a name which
normalizes to the original package name, is a no-op
since the package is already installed
"""
pkg_path = create_test_package_with_setup(
script, name='simple-package', version='1.0')
result = script.pip('install', '-e', '.',
expect_stderr=True, cwd=pkg_path)
assert 'Successfully installed simple-package' in result.stdout

result = script.pip('install', package_name)
assert 'Requirement already satisfied: {}'.format(
package_name) in result.stdout
3 changes: 2 additions & 1 deletion tests/functional/test_search.py
Original file line number Diff line number Diff line change
@@ -168,7 +168,8 @@ def test_latest_prerelease_install_message(caplog, monkeypatch):

dist = pretend.stub(version="1.0.0")
get_dist = pretend.call_recorder(lambda x: dist)
monkeypatch.setattr("pip._vendor.pkg_resources.get_distribution", get_dist)
monkeypatch.setattr("pip._internal.commands.search.get_distribution",
get_dist)
with caplog.at_level(logging.INFO):
print_results(hits)

3 changes: 1 addition & 2 deletions tests/unit/test_self_check_outdated.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@
import freezegun
import pretend
import pytest
from pip._vendor import pkg_resources

from pip._internal import self_outdated_check
from pip._internal.models.candidate import InstallationCandidate
@@ -98,7 +97,7 @@ def test_pip_self_version_check(monkeypatch, stored_time, installed_ver,
pretend.call_recorder(lambda *a, **kw: None))
monkeypatch.setattr(logger, 'debug',
pretend.call_recorder(lambda s, exc_info=None: None))
monkeypatch.setattr(pkg_resources, 'get_distribution',
monkeypatch.setattr(self_outdated_check, 'get_distribution',
lambda name: MockDistribution(installer))

fake_state = pretend.stub(