1
1
Fork 0
mirror of https://github.com/pypa/pip synced 2023-12-13 21:30:23 +01:00

Remove get_installed_distributions usages

The function itself is kept for now because it's currently used to test
the pip.metadata subpackage...
This commit is contained in:
Tzu-ping Chung 2021-07-22 15:55:20 +08:00
parent 956ed04098
commit d4d2445350
11 changed files with 87 additions and 95 deletions

View file

@ -9,7 +9,7 @@ from typing import Any, Iterable, List, Optional
from pip._internal.cli.main_parser import create_main_parser
from pip._internal.commands import commands_dict, create_command
from pip._internal.utils.misc import get_installed_distributions
from pip._internal.metadata import get_default_environment
def autocomplete() -> None:
@ -45,11 +45,13 @@ def autocomplete() -> None:
"uninstall",
]
if should_list_installed:
env = get_default_environment()
lc = current.lower()
installed = [
dist.key
for dist in get_installed_distributions(local_only=True)
if dist.key.startswith(lc) and dist.key not in cwords[1:]
dist.canonical_name
for dist in env.iter_installed_distributions(local_only=True)
if dist.canonical_name.startswith(lc)
and dist.canonical_name not in cwords[1:]
]
# if there are no dists installed, fall back to option completion
if installed:

View file

@ -199,7 +199,7 @@ class ListCommand(IndexGroupCommand):
dep_keys = {
canonicalize_name(dep.name)
for dist in packages
for dep in dist.iter_dependencies()
for dep in (dist.iter_dependencies() or ())
}
# Create a set to remove duplicate packages, and cast it to a list

View file

@ -25,6 +25,8 @@ from pip._internal.utils.misc import stdlib_pkgs # TODO: Move definition here.
if TYPE_CHECKING:
from typing import Protocol
from pip._vendor.packaging.utils import NormalizedName
else:
Protocol = object
@ -59,7 +61,7 @@ class BaseDistribution(Protocol):
raise NotImplementedError()
@property
def canonical_name(self) -> str:
def canonical_name(self) -> "NormalizedName":
raise NotImplementedError()
@property
@ -108,6 +110,10 @@ class BaseDistribution(Protocol):
def in_usersite(self) -> bool:
raise NotImplementedError()
@property
def in_site_packages(self) -> bool:
raise NotImplementedError()
def read_text(self, name: str) -> str:
"""Read a file in the .dist-info (or .egg-info) directory.

View file

@ -1,7 +1,15 @@
import email.message
import logging
import zipfile
from typing import Collection, Iterable, Iterator, List, NamedTuple, Optional
from typing import (
TYPE_CHECKING,
Collection,
Iterable,
Iterator,
List,
NamedTuple,
Optional,
)
from pip._vendor import pkg_resources
from pip._vendor.packaging.requirements import Requirement
@ -14,6 +22,9 @@ from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel
from .base import BaseDistribution, BaseEntryPoint, BaseEnvironment, DistributionVersion
if TYPE_CHECKING:
from pip._vendor.packaging.utils import NormalizedName
logger = logging.getLogger(__name__)
@ -38,7 +49,7 @@ class Distribution(BaseDistribution):
return self._dist.location
@property
def canonical_name(self) -> str:
def canonical_name(self) -> "NormalizedName":
return canonicalize_name(self._dist.project_name)
@property
@ -61,6 +72,10 @@ class Distribution(BaseDistribution):
def in_usersite(self) -> bool:
return misc.dist_in_usersite(self._dist)
@property
def in_site_packages(self) -> bool:
return misc.dist_in_site_packages(self._dist)
def read_text(self, name: str) -> str:
if not self._dist.has_metadata(name):
raise FileNotFoundError(name)

View file

@ -2,49 +2,50 @@
"""
import logging
from collections import namedtuple
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple
from typing import TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Set, Tuple
from pip._vendor.packaging.requirements import Requirement
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.pkg_resources import RequirementParseError
from pip._internal.distributions import make_distribution_for_install_requirement
from pip._internal.metadata import get_default_environment
from pip._internal.metadata.base import DistributionVersion
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.misc import get_installed_distributions
if TYPE_CHECKING:
from pip._vendor.packaging.utils import NormalizedName
logger = logging.getLogger(__name__)
class PackageDetails(NamedTuple):
version: DistributionVersion
dependencies: List[Requirement]
# Shorthands
PackageSet = Dict['NormalizedName', 'PackageDetails']
Missing = Tuple[str, Any]
Conflicting = Tuple[str, str, Any]
PackageSet = Dict['NormalizedName', PackageDetails]
Missing = Tuple['NormalizedName', Requirement]
Conflicting = Tuple['NormalizedName', DistributionVersion, Requirement]
MissingDict = Dict['NormalizedName', List[Missing]]
ConflictingDict = Dict['NormalizedName', List[Conflicting]]
CheckResult = Tuple[MissingDict, ConflictingDict]
ConflictDetails = Tuple[PackageSet, CheckResult]
PackageDetails = namedtuple('PackageDetails', ['version', 'requires'])
def create_package_set_from_installed(**kwargs: Any) -> Tuple["PackageSet", bool]:
"""Converts a list of distributions into a PackageSet.
"""
# Default to using all packages installed on the system
if kwargs == {}:
kwargs = {"local_only": False, "skip": ()}
def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
"""Converts a list of distributions into a PackageSet."""
package_set = {}
problems = False
for dist in get_installed_distributions(**kwargs):
name = canonicalize_name(dist.project_name)
env = get_default_environment()
for dist in env.iter_installed_distributions(local_only=False, skip=()):
name = dist.canonical_name
try:
package_set[name] = PackageDetails(dist.version, dist.requires())
except (OSError, RequirementParseError) as e:
# Don't crash on unreadable or broken metadata
dependencies = list(dist.iter_dependencies())
package_set[name] = PackageDetails(dist.version, dependencies)
except (OSError, ValueError) as e:
# Don't crash on unreadable or broken metadata.
logger.warning("Error parsing requirements for %s: %s", name, e)
problems = True
return package_set, problems
@ -69,8 +70,8 @@ def check_package_set(package_set, should_ignore=None):
if should_ignore and should_ignore(package_name):
continue
for req in package_detail.requires:
name = canonicalize_name(req.project_name)
for req in package_detail.dependencies:
name = canonicalize_name(req.name)
# Check if it's missing
if name not in package_set:
@ -82,7 +83,7 @@ def check_package_set(package_set, should_ignore=None):
continue
# Check if there's a conflict
version = package_set[name].version # type: str
version = package_set[name].version
if not req.specifier.contains(version, prereleases=True):
conflicting_deps.add((name, version, req))
@ -119,7 +120,6 @@ def _simulate_installation_of(to_install, package_set):
# type: (List[InstallRequirement], PackageSet) -> Set[NormalizedName]
"""Computes the version of packages after installing to_install.
"""
# Keep track of packages that were installed
installed = set()
@ -129,8 +129,8 @@ def _simulate_installation_of(to_install, package_set):
dist = abstract_dist.get_pkg_resources_distribution()
assert dist is not None
name = canonicalize_name(dist.key)
package_set[name] = PackageDetails(dist.version, dist.requires())
name = canonicalize_name(dist.project_name)
package_set[name] = PackageDetails(dist.parsed_version, dist.requires())
installed.add(name)
@ -145,7 +145,7 @@ def _create_whitelist(would_be_installed, package_set):
if package_name in packages_affected:
continue
for req in package_set[package_name].requires:
for req in package_set[package_name].dependencies:
if canonicalize_name(req.name) in packages_affected:
packages_affected.add(package_name)
break

View file

@ -22,7 +22,6 @@ from pip._vendor.packaging.requirements import InvalidRequirement
from pip._vendor.packaging.requirements import Requirement as PackagingRequirement
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._vendor.pkg_resources import Distribution
from pip._vendor.resolvelib import ResolutionImpossible
from pip._internal.cache import CacheEntry, WheelCache
@ -35,6 +34,7 @@ from pip._internal.exceptions import (
UnsupportedWheel,
)
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import BaseDistribution, get_default_environment
from pip._internal.models.link import Link
from pip._internal.models.wheel import Wheel
from pip._internal.operations.prepare import RequirementPreparer
@ -46,11 +46,6 @@ from pip._internal.req.req_install import (
from pip._internal.resolution.base import InstallRequirementProvider
from pip._internal.utils.compatibility_tags import get_supported
from pip._internal.utils.hashes import Hashes
from pip._internal.utils.misc import (
dist_in_site_packages,
dist_in_usersite,
get_installed_distributions,
)
from pip._internal.utils.virtualenv import running_under_virtualenv
from .base import Candidate, CandidateVersion, Constraint, Requirement
@ -122,9 +117,10 @@ class Factory:
] = {}
if not ignore_installed:
env = get_default_environment()
self._installed_dists = {
canonicalize_name(dist.project_name): dist
for dist in get_installed_distributions(local_only=False)
dist.canonical_name: dist
for dist in env.iter_installed_distributions(local_only=False)
}
else:
self._installed_dists = {}
@ -155,15 +151,18 @@ class Factory:
def _make_candidate_from_dist(
self,
dist: Distribution,
dist: BaseDistribution,
extras: FrozenSet[str],
template: InstallRequirement,
) -> Candidate:
try:
base = self._installed_candidate_cache[dist.key]
base = self._installed_candidate_cache[dist.canonical_name]
except KeyError:
base = AlreadyInstalledCandidate(dist, template, factory=self)
self._installed_candidate_cache[dist.key] = base
from pip._internal.metadata.pkg_resources import Distribution as _Dist
compat_dist = cast(_Dist, dist)._dist
base = AlreadyInstalledCandidate(compat_dist, template, factory=self)
self._installed_candidate_cache[dist.canonical_name] = base
if not extras:
return base
return self._make_extras_candidate(base, extras)
@ -520,7 +519,7 @@ class Factory:
supported_tags=get_supported(),
)
def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[Distribution]:
def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[BaseDistribution]:
# TODO: Are there more cases this needs to return True? Editable?
dist = self._installed_dists.get(candidate.project_name)
if dist is None: # Not installed, no uninstallation required.
@ -533,21 +532,19 @@ class Factory:
return dist
# We're installing into user site. Remove the user site installation.
if dist_in_usersite(dist):
if dist.in_usersite:
return dist
# We're installing into user site, but the installed incompatible
# package is in global site. We can't uninstall that, and would let
# the new user installation to "shadow" it. But shadowing won't work
# in virtual environments, so we error out.
if running_under_virtualenv() and dist_in_site_packages(dist):
raise InstallationError(
"Will not install to the user site because it will "
"lack sys.path precedence to {} in {}".format(
dist.project_name,
dist.location,
)
if running_under_virtualenv() and dist.in_site_packages:
message = (
f"Will not install to the user site because it will lack "
f"sys.path precedence to {dist.raw_name} in {dist.location}"
)
raise InstallationError(message)
return None
def _report_requires_python_error(

View file

@ -4,7 +4,6 @@ import os
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.version import parse as parse_version
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible
from pip._vendor.resolvelib import Resolver as RLResolver
from pip._vendor.resolvelib.structs import DirectedGraph
@ -22,7 +21,6 @@ from pip._internal.resolution.resolvelib.reporter import (
)
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.filetypes import is_archive_file
from pip._internal.utils.misc import dist_is_editable
from .base import Candidate, Requirement
from .factory import Factory
@ -119,10 +117,10 @@ class Resolver(BaseResolver):
elif self.factory.force_reinstall:
# The --force-reinstall flag is set -- reinstall.
ireq.should_reinstall = True
elif parse_version(installed_dist.version) != candidate.version:
elif installed_dist.version != candidate.version:
# The installation is different in version -- reinstall.
ireq.should_reinstall = True
elif candidate.is_editable or dist_is_editable(installed_dist):
elif candidate.is_editable or installed_dist.editable:
# The incoming distribution is editable, or different in
# editable-ness to installation -- reinstall.
ireq.should_reinstall = True

View file

@ -15,16 +15,16 @@ def _patch_dist_in_site_packages(virtualenv):
# Since the tests are run from a virtualenv, and to avoid the "Will not
# install to the usersite because it will lack sys.path precedence..."
# error: Monkey patch `pip._internal.req.req_install.dist_in_site_packages`
# and `pip._internal.resolution.resolvelib.factory.dist_in_site_packages`
# and `pip._internal.utils.misc.dist_in_site_packages`
# so it's possible to install a conflicting distribution in the user site.
virtualenv.sitecustomize = textwrap.dedent("""
def dist_in_site_packages(dist):
return False
from pip._internal.req import req_install
from pip._internal.resolution.resolvelib import factory
from pip._internal.utils import misc
req_install.dist_in_site_packages = dist_in_site_packages
factory.dist_in_site_packages = dist_in_site_packages
misc.dist_in_site_packages = dist_in_site_packages
""")

View file

@ -107,14 +107,14 @@ def test_new_resolver_install_user_in_virtualenv_with_conflict_fails(script):
def patch_dist_in_site_packages(virtualenv):
# Since the tests are run from a virtualenv, and to avoid the "Will not
# install to the usersite because it will lack sys.path precedence..."
# error: Monkey patch `dist_in_site_packages` in the resolver module so
# it's possible to install a conflicting distribution in the user site.
# error: Monkey patch `pip._internal.utils.misc.dist_in_site_packages`
# so it's possible to install a conflicting distribution in the user site.
virtualenv.sitecustomize = textwrap.dedent("""
def dist_in_site_packages(dist):
return False
from pip._internal.resolution.resolvelib import factory
factory.dist_in_site_packages = dist_in_site_packages
from pip._internal.utils import misc
misc.dist_in_site_packages = dist_in_site_packages
""")

View file

@ -1,26 +0,0 @@
"""Unit Tests for pip's dependency checking logic
"""
from unittest import mock
from pip._internal.operations import check
class TestInstalledDistributionsCall:
def test_passes_correct_default_kwargs(self, monkeypatch):
my_mock = mock.MagicMock(return_value=[])
monkeypatch.setattr(check, "get_installed_distributions", my_mock)
check.create_package_set_from_installed()
my_mock.assert_called_with(local_only=False, skip=())
def test_passes_any_given_kwargs(self, monkeypatch):
my_mock = mock.MagicMock(return_value=[])
monkeypatch.setattr(check, "get_installed_distributions", my_mock)
obj = object()
check.create_package_set_from_installed(hi=obj)
my_mock.assert_called_with(hi=obj)

View file

@ -1,8 +1,8 @@
import logging
from unittest.mock import patch
from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, ArchiveInfo
from pip._internal.metadata import BaseDistribution
from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, ArchiveInfo
@patch.object(BaseDistribution, "read_text", side_effect=FileNotFoundError)