show.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import logging
  2. from optparse import Values
  3. from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional
  4. from pip._vendor.packaging.utils import canonicalize_name
  5. from pip._internal.cli.base_command import Command
  6. from pip._internal.cli.status_codes import ERROR, SUCCESS
  7. from pip._internal.metadata import BaseDistribution, get_default_environment
  8. from pip._internal.utils.misc import write_output
  9. logger = logging.getLogger(__name__)
  10. class ShowCommand(Command):
  11. """
  12. Show information about one or more installed packages.
  13. The output is in RFC-compliant mail header format.
  14. """
  15. usage = """
  16. %prog [options] <package> ..."""
  17. ignore_require_venv = True
  18. def add_options(self) -> None:
  19. self.cmd_opts.add_option(
  20. "-f",
  21. "--files",
  22. dest="files",
  23. action="store_true",
  24. default=False,
  25. help="Show the full list of installed files for each package.",
  26. )
  27. self.parser.insert_option_group(0, self.cmd_opts)
  28. def run(self, options: Values, args: List[str]) -> int:
  29. if not args:
  30. logger.warning("ERROR: Please provide a package name or names.")
  31. return ERROR
  32. query = args
  33. results = search_packages_info(query)
  34. if not print_results(
  35. results, list_files=options.files, verbose=options.verbose
  36. ):
  37. return ERROR
  38. return SUCCESS
  39. class _PackageInfo(NamedTuple):
  40. name: str
  41. version: str
  42. location: str
  43. requires: List[str]
  44. required_by: List[str]
  45. installer: str
  46. metadata_version: str
  47. classifiers: List[str]
  48. summary: str
  49. homepage: str
  50. project_urls: List[str]
  51. author: str
  52. author_email: str
  53. license: str
  54. entry_points: List[str]
  55. files: Optional[List[str]]
  56. def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]:
  57. """
  58. Gather details from installed distributions. Print distribution name,
  59. version, location, and installed files. Installed files requires a
  60. pip generated 'installed-files.txt' in the distributions '.egg-info'
  61. directory.
  62. """
  63. env = get_default_environment()
  64. installed = {dist.canonical_name: dist for dist in env.iter_all_distributions()}
  65. query_names = [canonicalize_name(name) for name in query]
  66. missing = sorted(
  67. [name for name, pkg in zip(query, query_names) if pkg not in installed]
  68. )
  69. if missing:
  70. logger.warning("Package(s) not found: %s", ", ".join(missing))
  71. def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]:
  72. return (
  73. dist.metadata["Name"] or "UNKNOWN"
  74. for dist in installed.values()
  75. if current_dist.canonical_name
  76. in {canonicalize_name(d.name) for d in dist.iter_dependencies()}
  77. )
  78. for query_name in query_names:
  79. try:
  80. dist = installed[query_name]
  81. except KeyError:
  82. continue
  83. requires = sorted((req.name for req in dist.iter_dependencies()), key=str.lower)
  84. required_by = sorted(_get_requiring_packages(dist), key=str.lower)
  85. try:
  86. entry_points_text = dist.read_text("entry_points.txt")
  87. entry_points = entry_points_text.splitlines(keepends=False)
  88. except FileNotFoundError:
  89. entry_points = []
  90. files_iter = dist.iter_declared_entries()
  91. if files_iter is None:
  92. files: Optional[List[str]] = None
  93. else:
  94. files = sorted(files_iter)
  95. metadata = dist.metadata
  96. yield _PackageInfo(
  97. name=dist.raw_name,
  98. version=str(dist.version),
  99. location=dist.location or "",
  100. requires=requires,
  101. required_by=required_by,
  102. installer=dist.installer,
  103. metadata_version=dist.metadata_version or "",
  104. classifiers=metadata.get_all("Classifier", []),
  105. summary=metadata.get("Summary", ""),
  106. homepage=metadata.get("Home-page", ""),
  107. project_urls=metadata.get_all("Project-URL", []),
  108. author=metadata.get("Author", ""),
  109. author_email=metadata.get("Author-email", ""),
  110. license=metadata.get("License", ""),
  111. entry_points=entry_points,
  112. files=files,
  113. )
  114. def print_results(
  115. distributions: Iterable[_PackageInfo],
  116. list_files: bool,
  117. verbose: bool,
  118. ) -> bool:
  119. """
  120. Print the information from installed distributions found.
  121. """
  122. results_printed = False
  123. for i, dist in enumerate(distributions):
  124. results_printed = True
  125. if i > 0:
  126. write_output("---")
  127. write_output("Name: %s", dist.name)
  128. write_output("Version: %s", dist.version)
  129. write_output("Summary: %s", dist.summary)
  130. write_output("Home-page: %s", dist.homepage)
  131. write_output("Author: %s", dist.author)
  132. write_output("Author-email: %s", dist.author_email)
  133. write_output("License: %s", dist.license)
  134. write_output("Location: %s", dist.location)
  135. write_output("Requires: %s", ", ".join(dist.requires))
  136. write_output("Required-by: %s", ", ".join(dist.required_by))
  137. if verbose:
  138. write_output("Metadata-Version: %s", dist.metadata_version)
  139. write_output("Installer: %s", dist.installer)
  140. write_output("Classifiers:")
  141. for classifier in dist.classifiers:
  142. write_output(" %s", classifier)
  143. write_output("Entry-points:")
  144. for entry in dist.entry_points:
  145. write_output(" %s", entry.strip())
  146. write_output("Project-URLs:")
  147. for project_url in dist.project_urls:
  148. write_output(" %s", project_url)
  149. if list_files:
  150. write_output("Files:")
  151. if dist.files is None:
  152. write_output("Cannot locate RECORD or installed-files.txt")
  153. else:
  154. for line in dist.files:
  155. write_output(" %s", line.strip())
  156. return results_printed