base_command.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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.logging import BrokenStdoutLoggingError, setup_logging
  32. from pip._internal.utils.misc import get_prog
  33. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  34. from pip._internal.utils.virtualenv import running_under_virtualenv
  35. if MYPY_CHECK_RUNNING:
  36. from typing import List, Tuple, Any
  37. from optparse import Values
  38. __all__ = ['Command']
  39. logger = logging.getLogger(__name__)
  40. class Command(CommandContextMixIn):
  41. usage = None # type: str
  42. ignore_require_venv = False # type: bool
  43. def __init__(self, name, summary, isolated=False):
  44. # type: (str, str, bool) -> None
  45. super(Command, self).__init__()
  46. parser_kw = {
  47. 'usage': self.usage,
  48. 'prog': '%s %s' % (get_prog(), name),
  49. 'formatter': UpdatingDefaultsHelpFormatter(),
  50. 'add_help_option': False,
  51. 'name': name,
  52. 'description': self.__doc__,
  53. 'isolated': isolated,
  54. }
  55. self.name = name
  56. self.summary = summary
  57. self.parser = ConfigOptionParser(**parser_kw)
  58. # Commands should add options to this option group
  59. optgroup_name = '%s Options' % self.name.capitalize()
  60. self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
  61. # Add the general options
  62. gen_opts = cmdoptions.make_option_group(
  63. cmdoptions.general_group,
  64. self.parser,
  65. )
  66. self.parser.add_option_group(gen_opts)
  67. def handle_pip_version_check(self, options):
  68. # type: (Values) -> None
  69. """
  70. This is a no-op so that commands by default do not do the pip version
  71. check.
  72. """
  73. # Make sure we do the pip version check if the index_group options
  74. # are present.
  75. assert not hasattr(options, 'no_index')
  76. def run(self, options, args):
  77. # type: (Values, List[Any]) -> Any
  78. raise NotImplementedError
  79. def parse_args(self, args):
  80. # type: (List[str]) -> Tuple
  81. # factored out for testability
  82. return self.parser.parse_args(args)
  83. def main(self, args):
  84. # type: (List[str]) -> int
  85. try:
  86. with self.main_context():
  87. return self._main(args)
  88. finally:
  89. logging.shutdown()
  90. def _main(self, args):
  91. # type: (List[str]) -> int
  92. options, args = self.parse_args(args)
  93. # Set verbosity so that it can be used elsewhere.
  94. self.verbosity = options.verbose - options.quiet
  95. level_number = setup_logging(
  96. verbosity=self.verbosity,
  97. no_color=options.no_color,
  98. user_log_file=options.log,
  99. )
  100. if sys.version_info[:2] == (2, 7):
  101. message = (
  102. "A future version of pip will drop support for Python 2.7. "
  103. "More details about Python 2 support in pip, can be found at "
  104. "https://pip.pypa.io/en/latest/development/release-process/#python-2-support" # noqa
  105. )
  106. if platform.python_implementation() == "CPython":
  107. message = (
  108. "Python 2.7 will reach the end of its life on January "
  109. "1st, 2020. Please upgrade your Python as Python 2.7 "
  110. "won't be maintained after that date. "
  111. ) + message
  112. deprecated(message, replacement=None, gone_in=None)
  113. # TODO: Try to get these passing down from the command?
  114. # without resorting to os.environ to hold these.
  115. # This also affects isolated builds and it should.
  116. if options.no_input:
  117. os.environ['PIP_NO_INPUT'] = '1'
  118. if options.exists_action:
  119. os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
  120. if options.require_venv and not self.ignore_require_venv:
  121. # If a venv is required check if it can really be found
  122. if not running_under_virtualenv():
  123. logger.critical(
  124. 'Could not find an activated virtualenv (required).'
  125. )
  126. sys.exit(VIRTUALENV_NOT_FOUND)
  127. try:
  128. status = self.run(options, args)
  129. # FIXME: all commands should return an exit status
  130. # and when it is done, isinstance is not needed anymore
  131. if isinstance(status, int):
  132. return status
  133. except PreviousBuildDirError as exc:
  134. logger.critical(str(exc))
  135. logger.debug('Exception information:', exc_info=True)
  136. return PREVIOUS_BUILD_DIR_ERROR
  137. except (InstallationError, UninstallationError, BadCommand) as exc:
  138. logger.critical(str(exc))
  139. logger.debug('Exception information:', exc_info=True)
  140. return ERROR
  141. except CommandError as exc:
  142. logger.critical('%s', exc)
  143. logger.debug('Exception information:', exc_info=True)
  144. return ERROR
  145. except BrokenStdoutLoggingError:
  146. # Bypass our logger and write any remaining messages to stderr
  147. # because stdout no longer works.
  148. print('ERROR: Pipe to stdout was broken', file=sys.stderr)
  149. if level_number <= logging.DEBUG:
  150. traceback.print_exc(file=sys.stderr)
  151. return ERROR
  152. except KeyboardInterrupt:
  153. logger.critical('Operation cancelled by user')
  154. logger.debug('Exception information:', exc_info=True)
  155. return ERROR
  156. except BaseException:
  157. logger.critical('Exception:', exc_info=True)
  158. return UNKNOWN_ERROR
  159. finally:
  160. self.handle_pip_version_check(options)
  161. return SUCCESS