Skip to content

Commit

Permalink
Abstract pkg_resources from uninstall operation
Browse files Browse the repository at this point in the history
  • Loading branch information
uranusjr committed Sep 28, 2021
1 parent 0442875 commit 915115d
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 111 deletions.
2 changes: 1 addition & 1 deletion src/pip/_internal/commands/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]:
return None
paths = (p for p in text.splitlines(keepends=False) if p)
root = dist.location
info = dist.info_directory
info = dist.info_location
if root is None or info is None:
return paths
try:
Expand Down
75 changes: 69 additions & 6 deletions src/pip/_internal/metadata/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import email.message
import json
import logging
import pathlib
import re
import zipfile
from typing import (
Expand Down Expand Up @@ -36,6 +37,8 @@

DistributionVersion = Union[LegacyVersion, Version]

InfoPath = Union[str, pathlib.PurePosixPath]

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -97,8 +100,8 @@ def editable_project_location(self) -> Optional[str]:
return None

@property
def info_directory(self) -> Optional[str]:
"""Location of the .[egg|dist]-info directory.
def info_location(self) -> Optional[str]:
"""Location of the .[egg|dist]-info directory or file.
Similarly to ``location``, a string value is not necessarily a
filesystem path. ``None`` means the distribution is created in-memory.
Expand All @@ -112,6 +115,57 @@ def info_directory(self) -> Optional[str]:
"""
raise NotImplementedError()

@property
def installed_by_legacy_distutils(self) -> bool:
"""Whether this distribution is installed with legacy distutils format.
A distribution installed with "raw" distutils not patched by setuptools
uses one single file at ``info_location`` to store metadata. We need to
treat this specially on uninstallation.
"""
info_location = self.info_location
return info_location and pathlib.Path(info_location).is_file()

@property
def installed_as_egg(self) -> bool:
"""Whether this distribution is installed as an egg.
This usually indicates the distribution was installed by (older versions
of) easy_install.
"""
info_location = self.info_location
return info_location and info_location.endswith(".egg")

@property
def installed_with_setuptools_egg_info(self) -> bool:
"""Whether this distribution is installed with the ``.egg-info`` format.
This usually indicates the distribution was installed with setuptools
with an old pip version or with ``single-version-externally-managed``.
"""
info_location = self.info_location
return (
info_location
and info_location.endswith(".egg-info")
and pathlib.Path(info_location).is_dir()
)

@property
def installed_with_dist_info(self) -> bool:
"""Whether this distribution is installed with the "modern format".
This indicates a "modern" installation, e.g. storing metadata in the
``.dist-info`` directory. This applies to installations made by
setuptools (but through pip, not directly), or anything using the
standardized build backend interface (PEP 517).
"""
info_location = self.info_location
return (
info_location
and info_location.endswith(".dist-info")
and pathlib.Path(info_location).is_dir()
)

@property
def canonical_name(self) -> NormalizedName:
raise NotImplementedError()
Expand Down Expand Up @@ -166,11 +220,20 @@ def in_usersite(self) -> bool:
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.
def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
"""Iterate through a directory in the info directory.
Each item yielded would be a path relative to the info directory.
:raise FileNotFoundError: If ``name`` does not exist in the directory.
:raise NotADirectoryError: If ``name`` does not point to a directory.
"""
raise NotImplementedError()

def read_text(self, path: InfoPath) -> str:
"""Read a file in the info directory.
Should raise ``FileNotFoundError`` if ``name`` does not exist in the
metadata directory.
:raise FileNotFoundError: If ``name`` does not exist in the directory.
"""
raise NotImplementedError()

Expand Down
26 changes: 24 additions & 2 deletions src/pip/_internal/metadata/pkg_resources.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import email.message
import logging
import pathlib
from typing import Collection, Iterable, Iterator, List, NamedTuple, Optional

from pip._vendor import pkg_resources
Expand All @@ -16,6 +17,7 @@
BaseEntryPoint,
BaseEnvironment,
DistributionVersion,
InfoPath,
Wheel,
)

Expand Down Expand Up @@ -43,9 +45,19 @@ def location(self) -> Optional[str]:
return self._dist.location

@property
def info_directory(self) -> Optional[str]:
def info_location(self) -> Optional[str]:
return self._dist.egg_info

@property
def installed_by_distutils(self) -> bool:
# A distutils-installed distribution is provided by FileMetadata. This
# provider has a "path" attribute not present anywhere else. Not the
# best introspection logic, but pip has been doing this for a long time.
try:
return bool(self._dist._provider.path)
except AttributeError:
return False

@property
def canonical_name(self) -> NormalizedName:
return canonicalize_name(self._dist.project_name)
Expand All @@ -70,7 +82,17 @@ def in_usersite(self) -> bool:
def in_site_packages(self) -> bool:
return misc.dist_in_site_packages(self._dist)

def read_text(self, name: str) -> str:
def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
name = str(path)
if not self._dist.has_metadata(name):
raise FileNotFoundError(name)
if not self._dist.isdir(name):
raise NotADirectoryError(name)
for child in self._dist.metadata_listdir(name):
yield pathlib.PurePosixPath(path, child)

def read_text(self, path: InfoPath) -> str:
name = str(path)
if not self._dist.has_metadata(name):
raise FileNotFoundError(name)
return self._dist.get_metadata(name)
Expand Down
3 changes: 2 additions & 1 deletion src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
from pip._internal.exceptions import InstallationError
from pip._internal.locations import get_scheme
from pip._internal.metadata import get_default_environment
from pip._internal.models.link import Link
from pip._internal.operations.build.metadata import generate_metadata
from pip._internal.operations.build.metadata_legacy import (
Expand Down Expand Up @@ -617,7 +618,7 @@ def uninstall(
"""
assert self.req
dist = get_distribution(self.req.name)
dist = get_default_environment().get_distribution(self.req.name)
if not dist:
logger.warning("Skipping %s as it is not installed.", self.name)
return None
Expand Down
Loading

0 comments on commit 915115d

Please sign in to comment.