diff options
Diffstat (limited to 'src/pip/_internal/commands/show.py')
-rw-r--r-- | src/pip/_internal/commands/show.py | 115 |
1 files changed, 32 insertions, 83 deletions
diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py index 5b2de39e5..212167c9d 100644 --- a/src/pip/_internal/commands/show.py +++ b/src/pip/_internal/commands/show.py @@ -1,8 +1,6 @@ -import csv import logging -import pathlib from optparse import Values -from typing import Iterator, List, NamedTuple, Optional, Tuple +from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional from pip._vendor.packaging.utils import canonicalize_name @@ -27,23 +25,26 @@ class ShowCommand(Command): def add_options(self) -> None: self.cmd_opts.add_option( - '-f', '--files', - dest='files', - action='store_true', + "-f", + "--files", + dest="files", + action="store_true", default=False, - help='Show the full list of installed files for each package.') + help="Show the full list of installed files for each package.", + ) self.parser.insert_option_group(0, self.cmd_opts) def run(self, options: Values, args: List[str]) -> int: if not args: - logger.warning('ERROR: Please provide a package name or names.') + logger.warning("ERROR: Please provide a package name or names.") return ERROR query = args results = search_packages_info(query) if not print_results( - results, list_files=options.files, verbose=options.verbose): + results, list_files=options.files, verbose=options.verbose + ): return ERROR return SUCCESS @@ -59,6 +60,7 @@ class _PackageInfo(NamedTuple): classifiers: List[str] summary: str homepage: str + project_urls: List[str] author: str author_email: str license: str @@ -66,34 +68,7 @@ class _PackageInfo(NamedTuple): files: Optional[List[str]] -def _covert_legacy_entry(entry: Tuple[str, ...], info: Tuple[str, ...]) -> str: - """Convert a legacy installed-files.txt path into modern RECORD path. - - The legacy format stores paths relative to the info directory, while the - modern format stores paths relative to the package root, e.g. the - site-packages directory. - - :param entry: Path parts of the installed-files.txt entry. - :param info: Path parts of the egg-info directory relative to package root. - :returns: The converted entry. - - For best compatibility with symlinks, this does not use ``abspath()`` or - ``Path.resolve()``, but tries to work with path parts: - - 1. While ``entry`` starts with ``..``, remove the equal amounts of parts - from ``info``; if ``info`` is empty, start appending ``..`` instead. - 2. Join the two directly. - """ - while entry and entry[0] == "..": - if not info or info[-1] == "..": - info += ("..",) - else: - info = info[:-1] - entry = entry[1:] - return str(pathlib.Path(*info, *entry)) - - -def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: +def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]: """ Gather details from installed distributions. Print distribution name, version, location, and installed files. Installed files requires a @@ -102,53 +77,20 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: """ env = get_default_environment() - installed = { - dist.canonical_name: dist - for dist in env.iter_distributions() - } + installed = {dist.canonical_name: dist for dist in env.iter_all_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] ) if missing: - logger.warning('Package(s) not found: %s', ', '.join(missing)) + logger.warning("Package(s) not found: %s", ", ".join(missing)) - def _get_requiring_packages(current_dist: BaseDistribution) -> List[str]: - return [ + def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]: + return ( dist.metadata["Name"] or "UNKNOWN" for dist in installed.values() - if current_dist.canonical_name in { - canonicalize_name(d.name) for d in dist.iter_dependencies() - } - ] - - def _files_from_record(dist: BaseDistribution) -> Optional[Iterator[str]]: - try: - text = dist.read_text('RECORD') - except FileNotFoundError: - return None - # This extra Path-str cast normalizes entries. - return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines())) - - def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]: - try: - text = dist.read_text('installed-files.txt') - except FileNotFoundError: - return None - paths = (p for p in text.splitlines(keepends=False) if p) - root = dist.location - info = dist.info_directory - if root is None or info is None: - return paths - try: - info_rel = pathlib.Path(info).relative_to(root) - except ValueError: # info is not relative to root. - return paths - if not info_rel.parts: # info *is* root. - return paths - return ( - _covert_legacy_entry(pathlib.Path(p).parts, info_rel.parts) - for p in paths + if current_dist.canonical_name + in {canonicalize_name(d.name) for d in dist.iter_dependencies()} ) for query_name in query_names: @@ -157,13 +99,16 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: except KeyError: continue + requires = sorted((req.name for req in dist.iter_dependencies()), key=str.lower) + required_by = sorted(_get_requiring_packages(dist), key=str.lower) + try: - entry_points_text = dist.read_text('entry_points.txt') + entry_points_text = dist.read_text("entry_points.txt") entry_points = entry_points_text.splitlines(keepends=False) except FileNotFoundError: entry_points = [] - files_iter = _files_from_record(dist) or _files_from_legacy(dist) + files_iter = dist.iter_declared_entries() if files_iter is None: files: Optional[List[str]] = None else: @@ -175,13 +120,14 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: name=dist.raw_name, version=str(dist.version), location=dist.location or "", - requires=[req.name for req in dist.iter_dependencies()], - required_by=_get_requiring_packages(dist), + requires=requires, + required_by=required_by, installer=dist.installer, metadata_version=dist.metadata_version or "", classifiers=metadata.get_all("Classifier", []), summary=metadata.get("Summary", ""), homepage=metadata.get("Home-page", ""), + project_urls=metadata.get_all("Project-URL", []), author=metadata.get("Author", ""), author_email=metadata.get("Author-email", ""), license=metadata.get("License", ""), @@ -191,7 +137,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: def print_results( - distributions: Iterator[_PackageInfo], + distributions: Iterable[_PackageInfo], list_files: bool, verbose: bool, ) -> bool: @@ -212,8 +158,8 @@ def print_results( write_output("Author-email: %s", dist.author_email) write_output("License: %s", dist.license) write_output("Location: %s", dist.location) - write_output("Requires: %s", ', '.join(dist.requires)) - write_output("Required-by: %s", ', '.join(dist.required_by)) + write_output("Requires: %s", ", ".join(dist.requires)) + write_output("Required-by: %s", ", ".join(dist.required_by)) if verbose: write_output("Metadata-Version: %s", dist.metadata_version) @@ -224,6 +170,9 @@ def print_results( write_output("Entry-points:") for entry in dist.entry_points: write_output(" %s", entry.strip()) + write_output("Project-URLs:") + for project_url in dist.project_urls: + write_output(" %s", project_url) if list_files: write_output("Files:") if dist.files is None: |