Skip to content

Commit 349bb73

Browse files
committed
Move dist-related logic from misc into metadata
1 parent 7e36104 commit 349bb73

File tree

4 files changed

+85
-118
lines changed

4 files changed

+85
-118
lines changed

src/pip/_internal/metadata/base.py

+13
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ def from_paths(cls, paths):
4747

4848
def get_distribution(self, name):
4949
# type: (str) -> Optional[BaseDistribution]
50+
"""Given a requirement name, return the installed distributions."""
5051
raise NotImplementedError()
5152

5253
def iter_distributions(self):
5354
# type: () -> Iterator[BaseDistribution]
55+
"""Iterate through installed distributions."""
5456
raise NotImplementedError()
5557

5658
def iter_installed_distributions(
@@ -62,6 +64,17 @@ def iter_installed_distributions(
6264
user_only=False, # type: bool
6365
):
6466
# type: (...) -> Iterator[BaseDistribution]
67+
"""Return a list of installed distributions.
68+
69+
:param local_only: If True (default), only return installations
70+
local to the current virtualenv, if in a virtualenv.
71+
:param skip: An iterable of canonicalized project names to ignore;
72+
defaults to ``stdlib_pkgs``.
73+
:param include_editables: If False, don't report editables.
74+
:param editables_only: If True, only report editables.
75+
:param user_only: If True, only report installations in the user
76+
site directory.
77+
"""
6578
it = self.iter_distributions()
6679
if local_only:
6780
it = (d for d in it if d.local)

src/pip/_internal/metadata/pkg_resources.py

+35-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pip._vendor import pkg_resources
22
from pip._vendor.packaging.utils import canonicalize_name
33

4-
from pip._internal.utils.misc import dist_in_usersite, dist_is_editable, dist_is_local
4+
from pip._internal.utils import misc # TODO: Move definition here.
55
from pip._internal.utils.packaging import get_installer
66
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
77

@@ -29,17 +29,17 @@ def installer(self):
2929
@property
3030
def editable(self):
3131
# type: () -> bool
32-
return dist_is_editable(self._dist)
32+
return misc.dist_is_editable(self._dist)
3333

3434
@property
3535
def local(self):
3636
# type: () -> bool
37-
return dist_is_local(self._dist)
37+
return misc.dist_is_local(self._dist)
3838

3939
@property
4040
def in_usersite(self):
4141
# type: () -> bool
42-
return dist_in_usersite(self._dist)
42+
return misc.dist_in_usersite(self._dist)
4343

4444

4545
class Environment(BaseEnvironment):
@@ -57,14 +57,42 @@ def from_paths(cls, paths):
5757
# type: (List[str]) -> BaseEnvironment
5858
return cls(pkg_resources.WorkingSet(paths))
5959

60+
def _search_distribution(self, name):
61+
# type: (str) -> Optional[BaseDistribution]
62+
"""Find a distribution matching the ``name`` in the environment.
63+
64+
This searches from *all* distributions available in the environment, to
65+
match the behavior of ``pkg_resources.get_distribution()``.
66+
"""
67+
canonical_name = canonicalize_name(name)
68+
for dist in self.iter_distributions():
69+
if dist.canonical_name == canonical_name:
70+
return dist
71+
return None
72+
6073
def get_distribution(self, name):
6174
# type: (str) -> Optional[BaseDistribution]
62-
req = pkg_resources.Requirement(name)
75+
76+
# Search the distribution by looking through the working set.
77+
dist = self._search_distribution(name)
78+
if dist:
79+
return dist
80+
81+
# If distribution could not be found, call working_set.require to
82+
# update the working set, and try to find the distribution again.
83+
# This might happen for e.g. when you install a package twice, once
84+
# using setup.py develop and again using setup.py install. Now when
85+
# running pip uninstall twice, the package gets removed from the
86+
# working set in the first uninstall, so we have to populate the
87+
# working set again so that pip knows about it and the packages gets
88+
# picked up and is successfully uninstalled the second time too.
6389
try:
64-
dist = self._ws.find(req)
90+
# We didn't pass in any version specifiers, so this can never
91+
# raise pkg_resources.VersionConflict.
92+
self._ws.require(name)
6593
except pkg_resources.DistributionNotFound:
6694
return None
67-
return Distribution(dist)
95+
return self._search_distribution(name)
6896

6997
def iter_distributions(self):
7098
# type: () -> Iterator[BaseDistribution]

src/pip/_internal/utils/misc.py

+21-99
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from itertools import filterfalse, tee, zip_longest
1919

2020
from pip._vendor import pkg_resources
21-
from pip._vendor.packaging.utils import canonicalize_name
2221

2322
# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
2423
# why we ignore the type on this import.
@@ -402,86 +401,20 @@ def get_installed_distributions(
402401
paths=None # type: Optional[List[str]]
403402
):
404403
# type: (...) -> List[Distribution]
405-
"""
406-
Return a list of installed Distribution objects.
407-
408-
If ``local_only`` is True (default), only return installations
409-
local to the current virtualenv, if in a virtualenv.
410-
411-
``skip`` argument is an iterable of lower-case project names to
412-
ignore; defaults to stdlib_pkgs
413-
414-
If ``include_editables`` is False, don't report editables.
415-
416-
If ``editables_only`` is True , only report editables.
417-
418-
If ``user_only`` is True , only report installations in the user
419-
site directory.
420-
421-
If ``paths`` is set, only report the distributions present at the
422-
specified list of locations.
423-
"""
424-
if paths:
425-
working_set = pkg_resources.WorkingSet(paths)
426-
else:
427-
working_set = pkg_resources.working_set
428-
429-
if local_only:
430-
local_test = dist_is_local
431-
else:
432-
def local_test(d):
433-
return True
434-
435-
if include_editables:
436-
def editable_test(d):
437-
return True
438-
else:
439-
def editable_test(d):
440-
return not dist_is_editable(d)
441-
442-
if editables_only:
443-
def editables_only_test(d):
444-
return dist_is_editable(d)
445-
else:
446-
def editables_only_test(d):
447-
return True
448-
449-
if user_only:
450-
user_test = dist_in_usersite
451-
else:
452-
def user_test(d):
453-
return True
454-
455-
return [d for d in working_set
456-
if local_test(d) and
457-
d.key not in skip and
458-
editable_test(d) and
459-
editables_only_test(d) and
460-
user_test(d)
461-
]
462-
463-
464-
def _search_distribution(req_name):
465-
# type: (str) -> Optional[Distribution]
466-
"""Find a distribution matching the ``req_name`` in the environment.
467-
468-
This searches from *all* distributions available in the environment, to
469-
match the behavior of ``pkg_resources.get_distribution()``.
470-
"""
471-
# Canonicalize the name before searching in the list of
472-
# installed distributions and also while creating the package
473-
# dictionary to get the Distribution object
474-
req_name = canonicalize_name(req_name)
475-
packages = get_installed_distributions(
476-
local_only=False,
477-
skip=(),
478-
include_editables=True,
479-
editables_only=False,
480-
user_only=False,
481-
paths=None,
404+
"""Return a list of installed Distribution objects.
405+
406+
Left for compatibility until direct pkg_resources uses are refactored out.
407+
"""
408+
from pip._internal.metadata import get_environment
409+
from pip._internal.metadata.pkg_resources import Distribution as _Dist
410+
dists = get_environment(paths).iter_installed_distributions(
411+
local_only=local_only,
412+
skip=skip,
413+
include_editables=include_editables,
414+
editables_only=editables_only,
415+
user_only=user_only,
482416
)
483-
pkg_dict = {canonicalize_name(p.key): p for p in packages}
484-
return pkg_dict.get(req_name)
417+
return [cast(_Dist, dist)._dist for dist in dists]
485418

486419

487420
def get_distribution(req_name):
@@ -490,26 +423,15 @@ def get_distribution(req_name):
490423
491424
This searches from *all* distributions available in the environment, to
492425
match the behavior of ``pkg_resources.get_distribution()``.
493-
"""
494426
495-
# Search the distribution by looking through the working set
496-
dist = _search_distribution(req_name)
497-
498-
# If distribution could not be found, call working_set.require
499-
# to update the working set, and try to find the distribution
500-
# again.
501-
# This might happen for e.g. when you install a package
502-
# twice, once using setup.py develop and again using setup.py install.
503-
# Now when run pip uninstall twice, the package gets removed
504-
# from the working set in the first uninstall, so we have to populate
505-
# the working set again so that pip knows about it and the packages
506-
# gets picked up and is successfully uninstalled the second time too.
507-
if not dist:
508-
try:
509-
pkg_resources.working_set.require(req_name)
510-
except pkg_resources.DistributionNotFound:
511-
return None
512-
return _search_distribution(req_name)
427+
Left for compatibility until direct pkg_resources uses are refactored out.
428+
"""
429+
from pip._internal.metadata import get_environment
430+
from pip._internal.metadata.pkg_resources import Distribution as _Dist
431+
dist = get_environment().get_distribution(req_name)
432+
if dist is None:
433+
return None
434+
return cast(_Dist, dist)._dist
513435

514436

515437
def egg_link_path(dist):

tests/unit/test_utils.py

+16-12
Original file line numberDiff line numberDiff line change
@@ -196,21 +196,21 @@ def require(self, name):
196196
pass
197197

198198
workingset = MockWorkingSet((
199-
Mock(test_name="global", key="global"),
200-
Mock(test_name="editable", key="editable"),
201-
Mock(test_name="normal", key="normal"),
202-
Mock(test_name="user", key="user"),
199+
Mock(test_name="global", project_name="global"),
200+
Mock(test_name="editable", project_name="editable"),
201+
Mock(test_name="normal", project_name="normal"),
202+
Mock(test_name="user", project_name="user"),
203203
))
204204

205205
workingset_stdlib = MockWorkingSet((
206-
Mock(test_name='normal', key='argparse'),
207-
Mock(test_name='normal', key='wsgiref')
206+
Mock(test_name='normal', project_name='argparse'),
207+
Mock(test_name='normal', project_name='wsgiref')
208208
))
209209

210210
workingset_freeze = MockWorkingSet((
211-
Mock(test_name='normal', key='pip'),
212-
Mock(test_name='normal', key='setuptools'),
213-
Mock(test_name='normal', key='distribute')
211+
Mock(test_name='normal', project_name='pip'),
212+
Mock(test_name='normal', project_name='setuptools'),
213+
Mock(test_name='normal', project_name='distribute')
214214
))
215215

216216
def dist_is_editable(self, dist):
@@ -290,9 +290,13 @@ def test_freeze_excludes(self, mock_dist_is_editable,
290290
@pytest.mark.parametrize(
291291
"working_set, req_name",
292292
itertools.chain(
293-
itertools.product([workingset], (d.key for d in workingset)),
294293
itertools.product(
295-
[workingset_stdlib], (d.key for d in workingset_stdlib),
294+
[workingset],
295+
(d.project_name for d in workingset),
296+
),
297+
itertools.product(
298+
[workingset_stdlib],
299+
(d.project_name for d in workingset_stdlib),
296300
),
297301
),
298302
)
@@ -312,7 +316,7 @@ def test_get_distribution(
312316
with patch("pip._vendor.pkg_resources.working_set", working_set):
313317
dist = get_distribution(req_name)
314318
assert dist is not None
315-
assert dist.key == req_name
319+
assert dist.project_name == req_name
316320

317321
@patch('pip._vendor.pkg_resources.working_set', workingset)
318322
def test_get_distribution_nonexist(

0 commit comments

Comments
 (0)