diff options
Diffstat (limited to 'src/pip/_internal/commands/show.py')
-rw-r--r-- | src/pip/_internal/commands/show.py | 78 |
1 files changed, 16 insertions, 62 deletions
diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py index 872292a2b..3f10701f6 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 @@ -55,6 +53,7 @@ class _PackageInfo(NamedTuple): name: str version: str location: str + editable_project_location: Optional[str] requires: List[str] required_by: List[str] installer: str @@ -62,6 +61,7 @@ class _PackageInfo(NamedTuple): classifiers: List[str] summary: str homepage: str + project_urls: List[str] author: str author_email: str license: str @@ -69,34 +69,7 @@ class _PackageInfo(NamedTuple): files: Optional[List[str]] -def _convert_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 @@ -105,7 +78,7 @@ 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] @@ -121,34 +94,6 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: 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 ( - _convert_legacy_entry(pathlib.Path(p).parts, info_rel.parts) for p in paths - ) - for query_name in query_names: try: dist = installed[query_name] @@ -164,7 +109,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: 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: @@ -176,6 +121,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: name=dist.raw_name, version=str(dist.version), location=dist.location or "", + editable_project_location=dist.editable_project_location, requires=requires, required_by=required_by, installer=dist.installer, @@ -183,6 +129,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: 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", ""), @@ -192,7 +139,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: @@ -213,6 +160,10 @@ def print_results( write_output("Author-email: %s", dist.author_email) write_output("License: %s", dist.license) write_output("Location: %s", dist.location) + if dist.editable_project_location is not None: + write_output( + "Editable project location: %s", dist.editable_project_location + ) write_output("Requires: %s", ", ".join(dist.requires)) write_output("Required-by: %s", ", ".join(dist.required_by)) @@ -225,6 +176,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: |