|
import logging |
|
from optparse import Values |
|
from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional |
|
|
|
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__) |
|
|
|
|
|
class ShowCommand(Command): |
|
""" |
|
Show information about one or more installed packages. |
|
|
|
The output is in RFC-compliant mail header format. |
|
""" |
|
|
|
usage = """ |
|
%prog [options] <package> ...""" |
|
ignore_require_venv = True |
|
|
|
def add_options(self) -> None: |
|
self.cmd_opts.add_option( |
|
"-f", |
|
"--files", |
|
dest="files", |
|
action="store_true", |
|
default=False, |
|
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.") |
|
return ERROR |
|
query = args |
|
|
|
results = search_packages_info(query) |
|
if not print_results( |
|
results, list_files=options.files, verbose=options.verbose |
|
): |
|
return ERROR |
|
return SUCCESS |
|
|
|
|
|
class _PackageInfo(NamedTuple): |
|
name: str |
|
version: str |
|
location: str |
|
editable_project_location: Optional[str] |
|
requires: List[str] |
|
required_by: List[str] |
|
installer: str |
|
metadata_version: str |
|
classifiers: List[str] |
|
summary: str |
|
homepage: str |
|
project_urls: List[str] |
|
author: str |
|
author_email: str |
|
license: str |
|
entry_points: List[str] |
|
files: Optional[List[str]] |
|
|
|
|
|
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 |
|
pip generated 'installed-files.txt' in the distributions '.egg-info' |
|
directory. |
|
""" |
|
env = get_default_environment() |
|
|
|
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)) |
|
|
|
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()} |
|
) |
|
|
|
for query_name in query_names: |
|
try: |
|
dist = installed[query_name] |
|
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 = entry_points_text.splitlines(keepends=False) |
|
except FileNotFoundError: |
|
entry_points = [] |
|
|
|
files_iter = dist.iter_declared_entries() |
|
if files_iter is None: |
|
files: Optional[List[str]] = None |
|
else: |
|
files = sorted(files_iter) |
|
|
|
metadata = dist.metadata |
|
|
|
yield _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, |
|
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", ""), |
|
entry_points=entry_points, |
|
files=files, |
|
) |
|
|
|
|
|
def print_results( |
|
distributions: Iterable[_PackageInfo], |
|
list_files: bool, |
|
verbose: bool, |
|
) -> bool: |
|
""" |
|
Print the information from installed distributions found. |
|
""" |
|
results_printed = False |
|
for i, dist in enumerate(distributions): |
|
results_printed = True |
|
if i > 0: |
|
write_output("---") |
|
|
|
write_output("Name: %s", dist.name) |
|
write_output("Version: %s", dist.version) |
|
write_output("Summary: %s", dist.summary) |
|
write_output("Home-page: %s", dist.homepage) |
|
write_output("Author: %s", dist.author) |
|
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)) |
|
|
|
if verbose: |
|
write_output("Metadata-Version: %s", dist.metadata_version) |
|
write_output("Installer: %s", dist.installer) |
|
write_output("Classifiers:") |
|
for classifier in dist.classifiers: |
|
write_output(" %s", classifier) |
|
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: |
|
write_output("Cannot locate RECORD or installed-files.txt") |
|
else: |
|
for line in dist.files: |
|
write_output(" %s", line.strip()) |
|
return results_printed |
|
|