base_command.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. """Base Command class, and related routines"""
  2. from __future__ import absolute_import, print_function
  3. import logging
  4. import logging.config
  5. import optparse
  6. import os
  7. import platform
  8. import sys
  9. import traceback
  10. from pip._internal.cli import cmdoptions
  11. from pip._internal.cli.command_context import CommandContextMixIn
  12. from pip._internal.cli.parser import (
  13. ConfigOptionParser,
  14. UpdatingDefaultsHelpFormatter,
  15. )
  16. from pip._internal.cli.status_codes import (
  17. ERROR,
  18. PREVIOUS_BUILD_DIR_ERROR,
  19. SUCCESS,
  20. UNKNOWN_ERROR,
  21. VIRTUALENV_NOT_FOUND,
  22. )
  23. from pip._internal.exceptions import (
  24. BadCommand,
  25. CommandError,
  26. InstallationError,
  27. PreviousBuildDirError,
  28. UninstallationError,
  29. )
  30. from pip._internal.utils.deprecation import deprecated
  31. from pip._internal.utils.filesystem import check_path_owner
  32. from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
  33. from pip._internal.utils.misc import get_prog, normalize_path
  34. from pip._internal.utils.temp_dir import (
  35. global_tempdir_manager,
  36. tempdir_registry,
  37. )
  38. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  39. from pip._internal.utils.virtualenv import running_under_virtualenv
  40. if MYPY_CHECK_RUNNING:
  41. from typing import List, Optional, Tuple, Any
  42. from optparse import Values
  43. from pip._internal.utils.temp_dir import (
  44. TempDirectoryTypeRegistry as TempDirRegistry
  45. )
  46. __all__ = ['Command']
  47. logger = logging.getLogger(__name__)
  48. class Command(CommandContextMixIn):
  49. usage = None # type: str
  50. ignore_require_venv = False # type: bool
  51. def __init__(self, name, summary, isolated=False):
  52. # type: (str, str, bool) -> None
  53. super(Command, self).__init__()
  54. parser_kw = {
  55. 'usage': self.usage,
  56. 'prog': '{} {}'.format(get_prog(), name),
  57. 'formatter': UpdatingDefaultsHelpFormatter(),
  58. 'add_help_option': False,
  59. 'name': name,
  60. 'description': self.__doc__,
  61. 'isolated': isolated,
  62. }
  63. self.name = name
  64. self.summary = summary
  65. self.parser = ConfigOptionParser(**parser_kw)
  66. self.tempdir_registry = None # type: Optional[TempDirRegistry]
  67. # Commands should add options to this option group
  68. optgroup_name = '{} Options'.format(self.name.capitalize())
  69. self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
  70. # Add the general options
  71. gen_opts = cmdoptions.make_option_group(
  72. cmdoptions.general_group,
  73. self.parser,
  74. )
  75. self.parser.add_option_group(gen_opts)
  76. def handle_pip_version_check(self, options):
  77. # type: (Values) -> None
  78. """
  79. This is a no-op so that commands by default do not do the pip version
  80. check.
  81. """
  82. # Make sure we do the pip version check if the index_group options
  83. # are present.
  84. assert not hasattr(options, 'no_index')
  85. def run(self, options, args):
  86. # type: (Values, List[Any]) -> Any
  87. raise NotImplementedError
  88. def parse_args(self, args):
  89. # type: (List[str]) -> Tuple[Any, Any]
  90. # factored out for testability
  91. return self.parser.parse_args(args)
  92. def main(self, args):
  93. # type: (List[str]) -> int
  94. try:
  95. with self.main_context():
  96. return self._main(args)
  97. finally:
  98. logging.shutdown()
  99. def _main(self, args):
  100. # type: (List[str]) -> int
  101. # We must initialize this before the tempdir manager, otherwise the
  102. # configuration would not be accessible by the time we clean up the
  103. # tempdir manager.
  104. self.tempdir_registry = self.enter_context(tempdir_registry())
  105. # Intentionally set as early as possible so globally-managed temporary
  106. # directories are available to the rest of the code.
  107. self.enter_context(global_tempdir_manager())
  108. options, args = self.parse_args(args)
  109. # Set verbosity so that it can be used elsewhere.
  110. self.verbosity = options.verbose - options.quiet
  111. level_number = setup_logging(
  112. verbosity=self.verbosity,
  113. no_color=options.no_color,
  114. user_log_file=options.log,
  115. )
  116. if (
  117. sys.version_info[:2] == (2, 7) and
  118. not options.no_python_version_warning
  119. ):
  120. message = (
  121. "pip 21.0 will drop support for Python 2.7 in January 2021. "
  122. "More details about Python 2 support in pip, can be found at "
  123. "https://pip.pypa.io/en/latest/development/release-process/#python-2-support" # noqa
  124. )
  125. if platform.python_implementation() == "CPython":
  126. message = (
  127. "Python 2.7 reached the end of its life on January "
  128. "1st, 2020. Please upgrade your Python as Python 2.7 "
  129. "is no longer maintained. "
  130. ) + message
  131. deprecated(message, replacement=None, gone_in=None)
  132. # TODO: Try to get these passing down from the command?
  133. # without resorting to os.environ to hold these.
  134. # This also affects isolated builds and it should.
  135. if options.no_input:
  136. os.environ['PIP_NO_INPUT'] = '1'
  137. if options.exists_action:
  138. os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
  139. if options.require_venv and not self.ignore_require_venv:
  140. # If a venv is required check if it can really be found
  141. if not running_under_virtualenv():
  142. logger.critical(
  143. 'Could not find an activated virtualenv (required).'
  144. )
  145. sys.exit(VIRTUALENV_NOT_FOUND)
  146. if options.cache_dir:
  147. options.cache_dir = normalize_path(options.cache_dir)
  148. if not check_path_owner(options.cache_dir):
  149. logger.warning(
  150. "The directory '%s' or its parent directory is not owned "
  151. "or is not writable by the current user. The cache "
  152. "has been disabled. Check the permissions and owner of "
  153. "that directory. If executing pip with sudo, you may want "
  154. "sudo's -H flag.",
  155. options.cache_dir,
  156. )
  157. options.cache_dir = None
  158. try:
  159. status = self.run(options, args)
  160. # FIXME: all commands should return an exit status
  161. # and when it is done, isinstance is not needed anymore
  162. if isinstance(status, int):
  163. return status
  164. except PreviousBuildDirError as exc:
  165. logger.critical(str(exc))
  166. logger.debug('Exception information:', exc_info=True)
  167. return PREVIOUS_BUILD_DIR_ERROR
  168. except (InstallationError, UninstallationError, BadCommand) as exc:
  169. logger.critical(str(exc))
  170. logger.debug('Exception information:', exc_info=True)
  171. return ERROR
  172. except CommandError as exc:
  173. logger.critical('%s', exc)
  174. logger.debug('Exception information:', exc_info=True)
  175. return ERROR
  176. except BrokenStdoutLoggingError:
  177. # Bypass our logger and write any remaining messages to stderr
  178. # because stdout no longer works.
  179. print('ERROR: Pipe to stdout was broken', file=sys.stderr)
  180. if level_number <= logging.DEBUG:
  181. traceback.print_exc(file=sys.stderr)
  182. return ERROR
  183. except KeyboardInterrupt:
  184. logger.critical('Operation cancelled by user')
  185. logger.debug('Exception information:', exc_info=True)
  186. return ERROR
  187. except BaseException:
  188. logger.critical('Exception:', exc_info=True)
  189. return UNKNOWN_ERROR
  190. finally:
  191. self.handle_pip_version_check(options)
  192. return SUCCESS