pip/src/pip/_internal/cli/base_command.py

205 lines
6.7 KiB
Python
Raw Normal View History

"""Base Command class, and related routines"""
2018-10-23 10:55:07 +02:00
from __future__ import absolute_import, print_function
import logging
import logging.config
2017-05-16 12:16:30 +02:00
import optparse
import os
import platform
import sys
2018-10-23 10:55:07 +02:00
import traceback
2018-07-23 09:31:05 +02:00
from pip._internal.cli import cmdoptions
from pip._internal.cli.command_context import CommandContextMixIn
2018-07-30 06:05:08 +02:00
from pip._internal.cli.parser import (
2019-07-22 06:45:27 +02:00
ConfigOptionParser,
UpdatingDefaultsHelpFormatter,
)
2018-07-30 06:10:59 +02:00
from pip._internal.cli.status_codes import (
2019-07-22 06:45:27 +02:00
ERROR,
PREVIOUS_BUILD_DIR_ERROR,
SUCCESS,
UNKNOWN_ERROR,
2018-07-30 06:10:59 +02:00
VIRTUALENV_NOT_FOUND,
)
from pip._internal.exceptions import (
2019-07-22 06:45:27 +02:00
BadCommand,
CommandError,
InstallationError,
PreviousBuildDirError,
2017-11-21 08:50:32 +01:00
UninstallationError,
2017-05-16 12:16:30 +02:00
)
2019-01-11 11:14:25 +01:00
from pip._internal.utils.deprecation import deprecated
2018-10-23 10:55:07 +02:00
from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
from pip._internal.utils.misc import get_prog
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.virtualenv import running_under_virtualenv
if MYPY_CHECK_RUNNING:
from typing import List, Tuple, Any
2019-02-22 12:17:07 +01:00
from optparse import Values
2017-06-15 21:51:33 +02:00
__all__ = ['Command']
logger = logging.getLogger(__name__)
class Command(CommandContextMixIn):
usage = None # type: str
2017-06-15 21:51:33 +02:00
ignore_require_venv = False # type: bool
def __init__(self, name, summary, isolated=False):
# type: (str, str, bool) -> None
super(Command, self).__init__()
parser_kw = {
'usage': self.usage,
'prog': '%s %s' % (get_prog(), name),
'formatter': UpdatingDefaultsHelpFormatter(),
'add_help_option': False,
'name': name,
'description': self.__doc__,
'isolated': isolated,
}
self.name = name
self.summary = summary
self.parser = ConfigOptionParser(**parser_kw)
# Commands should add options to this option group
optgroup_name = '%s Options' % self.name.capitalize()
self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
# Add the general options
gen_opts = cmdoptions.make_option_group(
cmdoptions.general_group,
self.parser,
)
self.parser.add_option_group(gen_opts)
def handle_pip_version_check(self, options):
# type: (Values) -> None
"""
This is a no-op so that commands by default do not do the pip version
check.
"""
# Make sure we do the pip version check if the index_group options
# are present.
assert not hasattr(options, 'no_index')
def run(self, options, args):
# type: (Values, List[Any]) -> Any
raise NotImplementedError
def parse_args(self, args):
2019-11-12 19:08:48 +01:00
# type: (List[str]) -> Tuple[Any, Any]
# factored out for testability
return self.parser.parse_args(args)
def main(self, args):
# type: (List[str]) -> int
try:
with self.main_context():
return self._main(args)
finally:
logging.shutdown()
def _main(self, args):
# type: (List[str]) -> int
options, args = self.parse_args(args)
# Set verbosity so that it can be used elsewhere.
self.verbosity = options.verbose - options.quiet
level_number = setup_logging(
verbosity=self.verbosity,
no_color=options.no_color,
user_log_file=options.log,
)
2019-07-07 10:45:16 +02:00
if sys.version_info[:2] == (2, 7):
message = (
"A future version of pip will drop support for Python 2.7. "
"More details about Python 2 support in pip, can be found at "
2019-07-18 11:50:45 +02:00
"https://pip.pypa.io/en/latest/development/release-process/#python-2-support" # noqa
2019-01-19 20:48:55 +01:00
)
if platform.python_implementation() == "CPython":
message = (
"Python 2.7 reached the end of its life on January "
"1st, 2020. Please upgrade your Python as Python 2.7 "
"is no longer maintained. "
) + message
deprecated(message, replacement=None, gone_in=None)
2019-01-11 11:14:25 +01:00
2019-11-05 00:00:42 +01:00
if options.skip_requirements_regex:
deprecated(
"--skip-requirements-regex is unsupported and will be removed",
replacement=(
"manage requirements/constraints files explicitly, "
"possibly generating them from metadata"
),
gone_in="20.1",
issue=7297,
)
# TODO: Try to get these passing down from the command?
# without resorting to os.environ to hold these.
# This also affects isolated builds and it should.
2013-02-19 04:38:26 +01:00
2011-10-04 16:06:34 +02:00
if options.no_input:
os.environ['PIP_NO_INPUT'] = '1'
2011-10-04 16:10:46 +02:00
if options.exists_action:
os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
if options.require_venv and not self.ignore_require_venv:
# If a venv is required check if it can really be found
if not running_under_virtualenv():
logger.critical(
'Could not find an activated virtualenv (required).'
)
sys.exit(VIRTUALENV_NOT_FOUND)
try:
status = self.run(options, args)
# FIXME: all commands should return an exit status
# and when it is done, isinstance is not needed anymore
if isinstance(status, int):
return status
except PreviousBuildDirError as exc:
logger.critical(str(exc))
logger.debug('Exception information:', exc_info=True)
return PREVIOUS_BUILD_DIR_ERROR
except (InstallationError, UninstallationError, BadCommand) as exc:
logger.critical(str(exc))
logger.debug('Exception information:', exc_info=True)
return ERROR
except CommandError as exc:
2019-01-26 23:20:14 +01:00
logger.critical('%s', exc)
logger.debug('Exception information:', exc_info=True)
2018-10-23 10:55:07 +02:00
return ERROR
except BrokenStdoutLoggingError:
# Bypass our logger and write any remaining messages to stderr
# because stdout no longer works.
print('ERROR: Pipe to stdout was broken', file=sys.stderr)
if level_number <= logging.DEBUG:
2018-10-23 10:55:07 +02:00
traceback.print_exc(file=sys.stderr)
return ERROR
except KeyboardInterrupt:
logger.critical('Operation cancelled by user')
logger.debug('Exception information:', exc_info=True)
return ERROR
2018-06-27 10:07:18 +02:00
except BaseException:
logger.critical('Exception:', exc_info=True)
return UNKNOWN_ERROR
finally:
self.handle_pip_version_check(options)
return SUCCESS