Skip to content

Commit

Permalink
Remove get_installed_distributions usages
Browse files Browse the repository at this point in the history
The function itself is kept for now because it's currently used to test
the pip.metadata subpackage...
  • Loading branch information
uranusjr committed Jul 22, 2021
1 parent 956ed04 commit 4f6978e
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 91 deletions.
10 changes: 6 additions & 4 deletions src/pip/_internal/cli/autocompletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

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:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_internal/commands/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def get_not_required(self, packages, options):
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
Expand Down
8 changes: 7 additions & 1 deletion src/pip/_internal/metadata/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

if TYPE_CHECKING:
from typing import Protocol

from pip._vendor.packaging.utils import NormalizedName
else:
Protocol = object

Expand Down Expand Up @@ -59,7 +61,7 @@ def location(self) -> Optional[str]:
raise NotImplementedError()

@property
def canonical_name(self) -> str:
def canonical_name(self) -> "NormalizedName":
raise NotImplementedError()

@property
Expand Down Expand Up @@ -108,6 +110,10 @@ def local(self) -> bool:
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.
Expand Down
19 changes: 17 additions & 2 deletions src/pip/_internal/metadata/pkg_resources.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,6 +22,9 @@

from .base import BaseDistribution, BaseEntryPoint, BaseEnvironment, DistributionVersion

if TYPE_CHECKING:
from pip._vendor.packaging.utils import NormalizedName

logger = logging.getLogger(__name__)


Expand All @@ -38,7 +49,7 @@ def location(self) -> Optional[str]:
return self._dist.location

@property
def canonical_name(self) -> str:
def canonical_name(self) -> "NormalizedName":
return canonicalize_name(self._dist.project_name)

@property
Expand All @@ -61,6 +72,10 @@ def local(self) -> bool:
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)
Expand Down
56 changes: 28 additions & 28 deletions src/pip/_internal/operations/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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))

Expand Down Expand Up @@ -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()

Expand All @@ -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)

Expand All @@ -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
Expand Down
39 changes: 18 additions & 21 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
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
Expand All @@ -35,6 +34,7 @@
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
Expand All @@ -46,11 +46,6 @@
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
Expand Down Expand Up @@ -122,9 +117,10 @@ def __init__(
] = {}

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 = {}
Expand Down Expand Up @@ -155,15 +151,18 @@ def _make_extras_candidate(

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)
Expand Down Expand Up @@ -520,7 +519,7 @@ def get_wheel_cache_entry(
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.
Expand All @@ -533,21 +532,19 @@ def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[Distribution]:
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(
Expand Down
6 changes: 2 additions & 4 deletions src/pip/_internal/resolution/resolvelib/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
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
Expand All @@ -22,7 +21,6 @@
)
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
Expand Down Expand Up @@ -119,10 +117,10 @@ def resolve(
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
Expand Down
6 changes: 3 additions & 3 deletions tests/functional/test_install_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
""")


Expand Down
Loading

0 comments on commit 4f6978e

Please sign in to comment.