cache.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. from __future__ import absolute_import
  2. import logging
  3. import os
  4. import textwrap
  5. import pip._internal.utils.filesystem as filesystem
  6. from pip._internal.cli.base_command import Command
  7. from pip._internal.cli.status_codes import ERROR, SUCCESS
  8. from pip._internal.exceptions import CommandError, PipError
  9. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  10. if MYPY_CHECK_RUNNING:
  11. from optparse import Values
  12. from typing import Any, List
  13. logger = logging.getLogger(__name__)
  14. class CacheCommand(Command):
  15. """
  16. Inspect and manage pip's wheel cache.
  17. Subcommands:
  18. - dir: Show the cache directory.
  19. - info: Show information about the cache.
  20. - list: List filenames of packages stored in the cache.
  21. - remove: Remove one or more package from the cache.
  22. - purge: Remove all items from the cache.
  23. ``<pattern>`` can be a glob expression or a package name.
  24. """
  25. ignore_require_venv = True
  26. usage = """
  27. %prog dir
  28. %prog info
  29. %prog list [<pattern>]
  30. %prog remove <pattern>
  31. %prog purge
  32. """
  33. def run(self, options, args):
  34. # type: (Values, List[Any]) -> int
  35. handlers = {
  36. "dir": self.get_cache_dir,
  37. "info": self.get_cache_info,
  38. "list": self.list_cache_items,
  39. "remove": self.remove_cache_items,
  40. "purge": self.purge_cache,
  41. }
  42. if not options.cache_dir:
  43. logger.error("pip cache commands can not "
  44. "function since cache is disabled.")
  45. return ERROR
  46. # Determine action
  47. if not args or args[0] not in handlers:
  48. logger.error(
  49. "Need an action (%s) to perform.",
  50. ", ".join(sorted(handlers)),
  51. )
  52. return ERROR
  53. action = args[0]
  54. # Error handling happens here, not in the action-handlers.
  55. try:
  56. handlers[action](options, args[1:])
  57. except PipError as e:
  58. logger.error(e.args[0])
  59. return ERROR
  60. return SUCCESS
  61. def get_cache_dir(self, options, args):
  62. # type: (Values, List[Any]) -> None
  63. if args:
  64. raise CommandError('Too many arguments')
  65. logger.info(options.cache_dir)
  66. def get_cache_info(self, options, args):
  67. # type: (Values, List[Any]) -> None
  68. if args:
  69. raise CommandError('Too many arguments')
  70. num_packages = len(self._find_wheels(options, '*'))
  71. cache_location = self._wheels_cache_dir(options)
  72. cache_size = filesystem.format_directory_size(cache_location)
  73. message = textwrap.dedent("""
  74. Location: {location}
  75. Size: {size}
  76. Number of wheels: {package_count}
  77. """).format(
  78. location=cache_location,
  79. package_count=num_packages,
  80. size=cache_size,
  81. ).strip()
  82. logger.info(message)
  83. def list_cache_items(self, options, args):
  84. # type: (Values, List[Any]) -> None
  85. if len(args) > 1:
  86. raise CommandError('Too many arguments')
  87. if args:
  88. pattern = args[0]
  89. else:
  90. pattern = '*'
  91. files = self._find_wheels(options, pattern)
  92. if not files:
  93. logger.info('Nothing cached.')
  94. return
  95. results = []
  96. for filename in files:
  97. wheel = os.path.basename(filename)
  98. size = filesystem.format_file_size(filename)
  99. results.append(' - {} ({})'.format(wheel, size))
  100. logger.info('Cache contents:\n')
  101. logger.info('\n'.join(sorted(results)))
  102. def remove_cache_items(self, options, args):
  103. # type: (Values, List[Any]) -> None
  104. if len(args) > 1:
  105. raise CommandError('Too many arguments')
  106. if not args:
  107. raise CommandError('Please provide a pattern')
  108. files = self._find_wheels(options, args[0])
  109. if not files:
  110. raise CommandError('No matching packages')
  111. for filename in files:
  112. os.unlink(filename)
  113. logger.debug('Removed %s', filename)
  114. logger.info('Files removed: %s', len(files))
  115. def purge_cache(self, options, args):
  116. # type: (Values, List[Any]) -> None
  117. if args:
  118. raise CommandError('Too many arguments')
  119. return self.remove_cache_items(options, ['*'])
  120. def _wheels_cache_dir(self, options):
  121. # type: (Values) -> str
  122. return os.path.join(options.cache_dir, 'wheels')
  123. def _find_wheels(self, options, pattern):
  124. # type: (Values, str) -> List[str]
  125. wheel_dir = self._wheels_cache_dir(options)
  126. # The wheel filename format, as specified in PEP 427, is:
  127. # {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
  128. #
  129. # Additionally, non-alphanumeric values in the distribution are
  130. # normalized to underscores (_), meaning hyphens can never occur
  131. # before `-{version}`.
  132. #
  133. # Given that information:
  134. # - If the pattern we're given contains a hyphen (-), the user is
  135. # providing at least the version. Thus, we can just append `*.whl`
  136. # to match the rest of it.
  137. # - If the pattern we're given doesn't contain a hyphen (-), the
  138. # user is only providing the name. Thus, we append `-*.whl` to
  139. # match the hyphen before the version, followed by anything else.
  140. #
  141. # PEP 427: https://www.python.org/dev/peps/pep-0427/
  142. pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl")
  143. return filesystem.find_files(wheel_dir, pattern)