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:
parent
956ed04098
commit
d4d2445350
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
""")
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
""")
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue