Skip to content

Commit 334f06e

Browse files
authored
Merge pull request #8054 from deveshks/correct-package-name-while-install
Canonicalize req name while doing pre-install package search
2 parents 161ee7f + 6c1030c commit 334f06e

File tree

8 files changed

+79
-17
lines changed

8 files changed

+79
-17
lines changed

news/5021.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use canonical package names while looking up already installed packages.

src/pip/_internal/commands/search.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from pip._internal.network.xmlrpc import PipXmlrpcTransport
2020
from pip._internal.utils.compat import get_terminal_size
2121
from pip._internal.utils.logging import indent_log
22-
from pip._internal.utils.misc import write_output
22+
from pip._internal.utils.misc import get_distribution, write_output
2323
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2424

2525
if MYPY_CHECK_RUNNING:
@@ -139,7 +139,7 @@ def print_results(hits, name_column_width=None, terminal_width=None):
139139
try:
140140
write_output(line)
141141
if name in installed_packages:
142-
dist = pkg_resources.get_distribution(name)
142+
dist = get_distribution(name)
143143
with indent_log():
144144
if dist.version == latest:
145145
write_output('INSTALLED: %s (latest)', dist.version)

src/pip/_internal/req/req_install.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
display_path,
4242
dist_in_site_packages,
4343
dist_in_usersite,
44+
get_distribution,
4445
get_installed_version,
4546
hide_url,
4647
redact_auth_from_url,
@@ -434,12 +435,17 @@ def check_if_exists(self, use_user_site):
434435
# evaluate it.
435436
no_marker = Requirement(str(self.req))
436437
no_marker.marker = None
438+
439+
# pkg_resources uses the canonical name to look up packages, but
440+
# the name passed passed to get_distribution is not canonicalized
441+
# so we have to explicitly convert it to a canonical name
442+
no_marker.name = canonicalize_name(no_marker.name)
437443
try:
438444
self.satisfied_by = pkg_resources.get_distribution(str(no_marker))
439445
except pkg_resources.DistributionNotFound:
440446
return
441447
except pkg_resources.VersionConflict:
442-
existing_dist = pkg_resources.get_distribution(
448+
existing_dist = get_distribution(
443449
self.req.name
444450
)
445451
if use_user_site:
@@ -679,13 +685,11 @@ def uninstall(self, auto_confirm=False, verbose=False):
679685
680686
"""
681687
assert self.req
682-
try:
683-
dist = pkg_resources.get_distribution(self.req.name)
684-
except pkg_resources.DistributionNotFound:
688+
dist = get_distribution(self.req.name)
689+
if not dist:
685690
logger.warning("Skipping %s as it is not installed.", self.name)
686691
return None
687-
else:
688-
logger.info('Found existing installation: %s', dist)
692+
logger.info('Found existing installation: %s', dist)
689693

690694
uninstalled_pathset = UninstallPathSet.from_dist(dist)
691695
uninstalled_pathset.remove(auto_confirm, verbose)

src/pip/_internal/self_outdated_check.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import os.path
88
import sys
99

10-
from pip._vendor import pkg_resources
1110
from pip._vendor.packaging import version as packaging_version
1211
from pip._vendor.six import ensure_binary
1312

@@ -19,7 +18,11 @@
1918
check_path_owner,
2019
replace,
2120
)
22-
from pip._internal.utils.misc import ensure_dir, get_installed_version
21+
from pip._internal.utils.misc import (
22+
ensure_dir,
23+
get_distribution,
24+
get_installed_version,
25+
)
2326
from pip._internal.utils.packaging import get_installer
2427
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2528

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

119121

120122
def pip_self_version_check(session, options):

src/pip/_internal/utils/misc.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from collections import deque
1919

2020
from pip._vendor import pkg_resources
21+
from pip._vendor.packaging.utils import canonicalize_name
2122
# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
2223
# why we ignore the type on this import.
2324
from pip._vendor.retrying import retry # type: ignore
@@ -480,6 +481,40 @@ def user_test(d):
480481
]
481482

482483

484+
def search_distribution(req_name):
485+
486+
# Canonicalize the name before searching in the list of
487+
# installed distributions and also while creating the package
488+
# dictionary to get the Distribution object
489+
req_name = canonicalize_name(req_name)
490+
packages = get_installed_distributions(skip=())
491+
pkg_dict = {canonicalize_name(p.key): p for p in packages}
492+
return pkg_dict.get(req_name)
493+
494+
495+
def get_distribution(req_name):
496+
"""Given a requirement name, return the installed Distribution object"""
497+
498+
# Search the distribution by looking through the working set
499+
dist = search_distribution(req_name)
500+
501+
# If distribution could not be found, call working_set.require
502+
# to update the working set, and try to find the distribution
503+
# again.
504+
# This might happen for e.g. when you install a package
505+
# twice, once using setup.py develop and again using setup.py install.
506+
# Now when run pip uninstall twice, the package gets removed
507+
# from the working set in the first uninstall, so we have to populate
508+
# the working set again so that pip knows about it and the packages
509+
# gets picked up and is successfully uninstalled the second time too.
510+
if not dist:
511+
try:
512+
pkg_resources.working_set.require(req_name)
513+
except pkg_resources.DistributionNotFound:
514+
return None
515+
return search_distribution(req_name)
516+
517+
483518
def egg_link_path(dist):
484519
# type: (Distribution) -> Optional[str]
485520
"""

tests/functional/test_install.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,3 +1882,23 @@ def test_install_skip_work_dir_pkg(script, data):
18821882

18831883
assert 'Requirement already satisfied: simple' not in result.stdout
18841884
assert 'Successfully installed simple' in result.stdout
1885+
1886+
1887+
@pytest.mark.parametrize('package_name', ('simple-package', 'simple_package',
1888+
'simple.package'))
1889+
def test_install_verify_package_name_normalization(script, package_name):
1890+
1891+
"""
1892+
Test that install of a package again using a name which
1893+
normalizes to the original package name, is a no-op
1894+
since the package is already installed
1895+
"""
1896+
pkg_path = create_test_package_with_setup(
1897+
script, name='simple-package', version='1.0')
1898+
result = script.pip('install', '-e', '.',
1899+
expect_stderr=True, cwd=pkg_path)
1900+
assert 'Successfully installed simple-package' in result.stdout
1901+
1902+
result = script.pip('install', package_name)
1903+
assert 'Requirement already satisfied: {}'.format(
1904+
package_name) in result.stdout

tests/functional/test_search.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ def test_latest_prerelease_install_message(caplog, monkeypatch):
168168

169169
dist = pretend.stub(version="1.0.0")
170170
get_dist = pretend.call_recorder(lambda x: dist)
171-
monkeypatch.setattr("pip._vendor.pkg_resources.get_distribution", get_dist)
171+
monkeypatch.setattr("pip._internal.commands.search.get_distribution",
172+
get_dist)
172173
with caplog.at_level(logging.INFO):
173174
print_results(hits)
174175

tests/unit/test_self_check_outdated.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import freezegun
77
import pretend
88
import pytest
9-
from pip._vendor import pkg_resources
109

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

104103
fake_state = pretend.stub(

0 commit comments

Comments
 (0)