图片解析应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

225 lines
7.8 KiB

  1. import os
  2. import textwrap
  3. from optparse import Values
  4. from typing import Any, List
  5. from pip._internal.cli.base_command import Command
  6. from pip._internal.cli.status_codes import ERROR, SUCCESS
  7. from pip._internal.exceptions import CommandError, PipError
  8. from pip._internal.utils import filesystem
  9. from pip._internal.utils.logging import getLogger
  10. logger = getLogger(__name__)
  11. class CacheCommand(Command):
  12. """
  13. Inspect and manage pip's wheel cache.
  14. Subcommands:
  15. - dir: Show the cache directory.
  16. - info: Show information about the cache.
  17. - list: List filenames of packages stored in the cache.
  18. - remove: Remove one or more package from the cache.
  19. - purge: Remove all items from the cache.
  20. ``<pattern>`` can be a glob expression or a package name.
  21. """
  22. ignore_require_venv = True
  23. usage = """
  24. %prog dir
  25. %prog info
  26. %prog list [<pattern>] [--format=[human, abspath]]
  27. %prog remove <pattern>
  28. %prog purge
  29. """
  30. def add_options(self) -> None:
  31. self.cmd_opts.add_option(
  32. "--format",
  33. action="store",
  34. dest="list_format",
  35. default="human",
  36. choices=("human", "abspath"),
  37. help="Select the output format among: human (default) or abspath",
  38. )
  39. self.parser.insert_option_group(0, self.cmd_opts)
  40. def run(self, options: Values, args: List[str]) -> int:
  41. handlers = {
  42. "dir": self.get_cache_dir,
  43. "info": self.get_cache_info,
  44. "list": self.list_cache_items,
  45. "remove": self.remove_cache_items,
  46. "purge": self.purge_cache,
  47. }
  48. if not options.cache_dir:
  49. logger.error("pip cache commands can not function since cache is disabled.")
  50. return ERROR
  51. # Determine action
  52. if not args or args[0] not in handlers:
  53. logger.error(
  54. "Need an action (%s) to perform.",
  55. ", ".join(sorted(handlers)),
  56. )
  57. return ERROR
  58. action = args[0]
  59. # Error handling happens here, not in the action-handlers.
  60. try:
  61. handlers[action](options, args[1:])
  62. except PipError as e:
  63. logger.error(e.args[0])
  64. return ERROR
  65. return SUCCESS
  66. def get_cache_dir(self, options: Values, args: List[Any]) -> None:
  67. if args:
  68. raise CommandError("Too many arguments")
  69. logger.info(options.cache_dir)
  70. def get_cache_info(self, options: Values, args: List[Any]) -> None:
  71. if args:
  72. raise CommandError("Too many arguments")
  73. num_http_files = len(self._find_http_files(options))
  74. num_packages = len(self._find_wheels(options, "*"))
  75. http_cache_location = self._cache_dir(options, "http-v2")
  76. old_http_cache_location = self._cache_dir(options, "http")
  77. wheels_cache_location = self._cache_dir(options, "wheels")
  78. http_cache_size = filesystem.format_size(
  79. filesystem.directory_size(http_cache_location)
  80. + filesystem.directory_size(old_http_cache_location)
  81. )
  82. wheels_cache_size = filesystem.format_directory_size(wheels_cache_location)
  83. message = (
  84. textwrap.dedent(
  85. """
  86. Package index page cache location (pip v23.3+): {http_cache_location}
  87. Package index page cache location (older pips): {old_http_cache_location}
  88. Package index page cache size: {http_cache_size}
  89. Number of HTTP files: {num_http_files}
  90. Locally built wheels location: {wheels_cache_location}
  91. Locally built wheels size: {wheels_cache_size}
  92. Number of locally built wheels: {package_count}
  93. """ # noqa: E501
  94. )
  95. .format(
  96. http_cache_location=http_cache_location,
  97. old_http_cache_location=old_http_cache_location,
  98. http_cache_size=http_cache_size,
  99. num_http_files=num_http_files,
  100. wheels_cache_location=wheels_cache_location,
  101. package_count=num_packages,
  102. wheels_cache_size=wheels_cache_size,
  103. )
  104. .strip()
  105. )
  106. logger.info(message)
  107. def list_cache_items(self, options: Values, args: List[Any]) -> None:
  108. if len(args) > 1:
  109. raise CommandError("Too many arguments")
  110. if args:
  111. pattern = args[0]
  112. else:
  113. pattern = "*"
  114. files = self._find_wheels(options, pattern)
  115. if options.list_format == "human":
  116. self.format_for_human(files)
  117. else:
  118. self.format_for_abspath(files)
  119. def format_for_human(self, files: List[str]) -> None:
  120. if not files:
  121. logger.info("No locally built wheels cached.")
  122. return
  123. results = []
  124. for filename in files:
  125. wheel = os.path.basename(filename)
  126. size = filesystem.format_file_size(filename)
  127. results.append(f" - {wheel} ({size})")
  128. logger.info("Cache contents:\n")
  129. logger.info("\n".join(sorted(results)))
  130. def format_for_abspath(self, files: List[str]) -> None:
  131. if files:
  132. logger.info("\n".join(sorted(files)))
  133. def remove_cache_items(self, options: Values, args: List[Any]) -> None:
  134. if len(args) > 1:
  135. raise CommandError("Too many arguments")
  136. if not args:
  137. raise CommandError("Please provide a pattern")
  138. files = self._find_wheels(options, args[0])
  139. no_matching_msg = "No matching packages"
  140. if args[0] == "*":
  141. # Only fetch http files if no specific pattern given
  142. files += self._find_http_files(options)
  143. else:
  144. # Add the pattern to the log message
  145. no_matching_msg += f' for pattern "{args[0]}"'
  146. if not files:
  147. logger.warning(no_matching_msg)
  148. for filename in files:
  149. os.unlink(filename)
  150. logger.verbose("Removed %s", filename)
  151. logger.info("Files removed: %s", len(files))
  152. def purge_cache(self, options: Values, args: List[Any]) -> None:
  153. if args:
  154. raise CommandError("Too many arguments")
  155. return self.remove_cache_items(options, ["*"])
  156. def _cache_dir(self, options: Values, subdir: str) -> str:
  157. return os.path.join(options.cache_dir, subdir)
  158. def _find_http_files(self, options: Values) -> List[str]:
  159. old_http_dir = self._cache_dir(options, "http")
  160. new_http_dir = self._cache_dir(options, "http-v2")
  161. return filesystem.find_files(old_http_dir, "*") + filesystem.find_files(
  162. new_http_dir, "*"
  163. )
  164. def _find_wheels(self, options: Values, pattern: str) -> List[str]:
  165. wheel_dir = self._cache_dir(options, "wheels")
  166. # The wheel filename format, as specified in PEP 427, is:
  167. # {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
  168. #
  169. # Additionally, non-alphanumeric values in the distribution are
  170. # normalized to underscores (_), meaning hyphens can never occur
  171. # before `-{version}`.
  172. #
  173. # Given that information:
  174. # - If the pattern we're given contains a hyphen (-), the user is
  175. # providing at least the version. Thus, we can just append `*.whl`
  176. # to match the rest of it.
  177. # - If the pattern we're given doesn't contain a hyphen (-), the
  178. # user is only providing the name. Thus, we append `-*.whl` to
  179. # match the hyphen before the version, followed by anything else.
  180. #
  181. # PEP 427: https://www.python.org/dev/peps/pep-0427/
  182. pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl")
  183. return filesystem.find_files(wheel_dir, pattern)