From 46762cde7ea3da9466fceb5843c07a0fc3acdd92 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Tue, 13 Jul 2021 10:05:25 +0800 Subject: [PATCH] Remove pkg_resources usages from 'pip show' --- src/pip/_internal/commands/show.py | 122 +++++++++----------- src/pip/_internal/metadata/pkg_resources.py | 2 + 2 files changed, 59 insertions(+), 65 deletions(-) diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py index 24e855a80d8..100e8f78f15 100644 --- a/src/pip/_internal/commands/show.py +++ b/src/pip/_internal/commands/show.py @@ -1,14 +1,14 @@ +import csv import logging import os -from email.parser import FeedParser from optparse import Values -from typing import Dict, Iterator, List +from typing import Dict, Iterator, List, Optional -from pip._vendor import pkg_resources from pip._vendor.packaging.utils import canonicalize_name from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.metadata import BaseDistribution, get_default_environment from pip._internal.utils.misc import write_output logger = logging.getLogger(__name__) @@ -58,10 +58,12 @@ def search_packages_info(query): pip generated 'installed-files.txt' in the distributions '.egg-info' directory. """ - installed = {} - for p in pkg_resources.working_set: - installed[canonicalize_name(p.project_name)] = p + env = get_default_environment() + installed = { + dist.canonical_name: dist + for dist in env.iter_distributions() + } query_names = [canonicalize_name(name) for name in query] missing = sorted( [name for name, pkg in zip(query, query_names) if pkg not in installed] @@ -69,74 +71,64 @@ def search_packages_info(query): if missing: logger.warning('Package(s) not found: %s', ', '.join(missing)) - def get_requiring_packages(package_name): + def get_requiring_packages(canonical_name): # type: (str) -> List[str] - canonical_name = canonicalize_name(package_name) return [ - pkg.project_name for pkg in pkg_resources.working_set - if canonical_name in - [canonicalize_name(required.name) for required in - pkg.requires()] + dist.canonical_name + for dist in env.iter_distributions() + if canonical_name in { + canonicalize_name(d.name) for d in dist.iter_dependencies() + } ] - for dist in [installed[pkg] for pkg in query_names if pkg in installed]: + def _files_from_record(dist: BaseDistribution) -> Optional[Iterator[str]]: + try: + text = dist.read_text('RECORD') + except FileNotFoundError: + return None + return (row[0] for row in csv.reader(text.splitlines())) + + def _files_from_installed_files(dist: BaseDistribution) -> Optional[Iterator[str]]: + try: + text = dist.read_text('installed-files.txt') + except FileNotFoundError: + return None + return (p for p in text.splitlines(keepends=False) if p) + + for query_name in query_names: + try: + dist = installed[query_name] + except KeyError: + continue package = { 'name': dist.project_name, 'version': dist.version, 'location': dist.location, - 'requires': [dep.project_name for dep in dist.requires()], - 'required_by': get_requiring_packages(dist.project_name) + 'requires': [req.nameroject_name for req in dist.iter_dependencies()], + 'required_by': get_requiring_packages(dist.canonical_name), + 'installer': dist.installer, + 'metadata-version': dist.metadata_version, + 'classifiers': dist.metadata.get_all('Classifiers'), } - file_list = None - metadata = '' - if isinstance(dist, pkg_resources.DistInfoDistribution): - # RECORDs should be part of .dist-info metadatas - if dist.has_metadata('RECORD'): - lines = dist.get_metadata_lines('RECORD') - paths = [line.split(',')[0] for line in lines] - paths = [os.path.join(dist.location, p) for p in paths] - file_list = [os.path.relpath(p, dist.location) for p in paths] - - if dist.has_metadata('METADATA'): - metadata = dist.get_metadata('METADATA') - else: - # Otherwise use pip's log for .egg-info's - if dist.has_metadata('installed-files.txt'): - paths = dist.get_metadata_lines('installed-files.txt') - paths = [os.path.join(dist.egg_info, p) for p in paths] - file_list = [os.path.relpath(p, dist.location) for p in paths] - - if dist.has_metadata('PKG-INFO'): - metadata = dist.get_metadata('PKG-INFO') - - if dist.has_metadata('entry_points.txt'): - entry_points = dist.get_metadata_lines('entry_points.txt') - package['entry_points'] = entry_points - - if dist.has_metadata('INSTALLER'): - for line in dist.get_metadata_lines('INSTALLER'): - if line.strip(): - package['installer'] = line.strip() - break - - # @todo: Should pkg_resources.Distribution have a - # `get_pkg_info` method? - feed_parser = FeedParser() - feed_parser.feed(metadata) - pkg_info_dict = feed_parser.close() - for key in ('metadata-version', 'summary', - 'home-page', 'author', 'author-email', 'license'): - package[key] = pkg_info_dict.get(key) - - # It looks like FeedParser cannot deal with repeated headers - classifiers = [] - for line in metadata.splitlines(): - if line.startswith('Classifier: '): - classifiers.append(line[len('Classifier: '):]) - package['classifiers'] = classifiers - - if file_list: - package['files'] = sorted(file_list) + + for key in ('Summary', 'Home-page', 'Author', 'Author-email', 'License'): + package[key] = dist.metadata[key] + + try: + entry_points_text = dist.read_text('entry_points.txt') + package['entry_points'] = entry_points_text.splitlines(keepends=False) + except FileNotFoundError: + pass + if dist.installer: + package['installer'] = dist.installer + + file_iterator = ( + _files_from_record(dist) or + _files_from_installed_files(dist.read_text('installed-files.txt')) + ) + if file_iterator: + package['files'] = sorted(file_iterator) + yield package diff --git a/src/pip/_internal/metadata/pkg_resources.py b/src/pip/_internal/metadata/pkg_resources.py index bab64fa3757..17974347908 100644 --- a/src/pip/_internal/metadata/pkg_resources.py +++ b/src/pip/_internal/metadata/pkg_resources.py @@ -68,6 +68,8 @@ def in_usersite(self) -> bool: return misc.dist_in_usersite(self._dist) def read_text(self, name: str) -> str: + if not self._dist.has_metadata(name): + raise FileNotFoundError(name) return self._dist.get_metadata(name) def iter_entry_points(self) -> Iterable[BaseEntryPoint]: