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

355 lines
13 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.cmdoptions import make_search_scope
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.download import PipSession
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
)
from pip._internal.index import PackageFinder
2019-06-28 20:14:55 +02:00
from pip._internal.models.selection_prefs import SelectionPreferences
2019-06-09 22:11:16 +02:00
from pip._internal.models.target_python import TargetPython
from pip._internal.req.constructors import (
2019-07-22 06:45:27 +02:00
install_req_from_editable,
install_req_from_line,
)
from pip._internal.req.req_file import parse_requirements
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, normalize_path
from pip._internal.utils.outdated import pip_version_check
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.virtualenv import running_under_virtualenv
if MYPY_CHECK_RUNNING:
2019-02-22 12:17:07 +01:00
from typing import Optional, List, Tuple, Any
from optparse import Values
from pip._internal.cache import WheelCache
from pip._internal.req.req_set import RequirementSet
2017-06-15 21:51:33 +02:00
__all__ = ['Command']
logger = logging.getLogger(__name__)
class Command(object):
2017-06-15 21:51:33 +02:00
name = None # type: Optional[str]
usage = None # type: Optional[str]
ignore_require_venv = False # type: bool
def __init__(self, isolated=False):
# type: (bool) -> None
parser_kw = {
'usage': self.usage,
'prog': '%s %s' % (get_prog(), self.name),
'formatter': UpdatingDefaultsHelpFormatter(),
'add_help_option': False,
'name': self.name,
'description': self.__doc__,
'isolated': isolated,
}
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 run(self, options, args):
# type: (Values, List[Any]) -> Any
raise NotImplementedError
@classmethod
def _get_index_urls(cls, options):
"""Return a list of index urls from user-provided options."""
index_urls = []
if not getattr(options, "no_index", False):
url = getattr(options, "index_url", None)
if url:
index_urls.append(url)
urls = getattr(options, "extra_index_urls", None)
if urls:
index_urls.extend(urls)
# Return None rather than an empty list
return index_urls or None
def _build_session(self, options, retries=None, timeout=None):
# type: (Values, Optional[int], Optional[int]) -> PipSession
session = PipSession(
2014-08-31 22:01:01 +02:00
cache=(
normalize_path(os.path.join(options.cache_dir, "http"))
if options.cache_dir else None
),
retries=retries if retries is not None else options.retries,
insecure_hosts=options.trusted_hosts,
index_urls=self._get_index_urls(options),
)
2013-08-16 14:04:27 +02:00
# Handle custom ca-bundles from the user
if options.cert:
session.verify = options.cert
# Handle SSL client certificate
if options.client_cert:
session.cert = options.client_cert
2013-08-16 14:04:27 +02:00
# Handle timeouts
if options.timeout or timeout:
session.timeout = (
timeout if timeout is not None else options.timeout
)
2013-08-16 14:04:27 +02:00
# Handle configured proxies
if options.proxy:
session.proxies = {
"http": options.proxy,
"https": options.proxy,
}
# Determine if we can prompt the user for authentication or not
session.auth.prompting = not options.no_input
return session
def parse_args(self, args):
# type: (List[str]) -> Tuple
# factored out for testability
return self.parser.parse_args(args)
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 will reach the end of its life on January "
"1st, 2020. Please upgrade your Python as Python 2.7 "
"won't be maintained after that date. "
) + message
deprecated(message, replacement=None, gone_in=None)
2019-01-11 11:14:25 +01:00
# 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:
allow_version_check = (
# Does this command have the index_group options?
hasattr(options, "no_index") and
# Is this command allowed to perform this check?
not (options.disable_pip_version_check or options.no_index)
)
# Check if we're using the latest version of pip available
if allow_version_check:
session = self._build_session(
options,
retries=0,
timeout=min(5, options.timeout)
)
with session:
pip_version_check(session, options)
# Shutdown the logging module
logging.shutdown()
return SUCCESS
class RequirementCommand(Command):
@staticmethod
def populate_requirement_set(requirement_set, # type: RequirementSet
args, # type: List[str]
options, # type: Values
finder, # type: PackageFinder
session, # type: PipSession
name, # type: str
wheel_cache # type: Optional[WheelCache]
):
# type: (...) -> None
"""
Marshal cmd line args into a requirement set.
"""
# NOTE: As a side-effect, options.require_hashes and
# requirement_set.require_hashes may be updated
for filename in options.constraints:
for req_to_add in parse_requirements(
filename,
constraint=True, finder=finder, options=options,
session=session, wheel_cache=wheel_cache):
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
for req in args:
req_to_add = install_req_from_line(
req, None, isolated=options.isolated_mode,
use_pep517=options.use_pep517,
wheel_cache=wheel_cache
)
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
for req in options.editables:
req_to_add = install_req_from_editable(
req,
isolated=options.isolated_mode,
use_pep517=options.use_pep517,
wheel_cache=wheel_cache
)
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
for filename in options.requirements:
for req_to_add in parse_requirements(
filename,
finder=finder, options=options, session=session,
wheel_cache=wheel_cache,
use_pep517=options.use_pep517):
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
# If --require-hashes was a line in a requirements file, tell
# RequirementSet about it:
requirement_set.require_hashes = options.require_hashes
if not (args or options.editables or options.requirements):
opts = {'name': name}
if options.find_links:
raise CommandError(
'You must give at least one requirement to %(name)s '
'(maybe you meant "pip %(name)s %(links)s"?)' %
dict(opts, links=' '.join(options.find_links)))
else:
raise CommandError(
'You must give at least one requirement to %(name)s '
'(see "pip help %(name)s")' % opts)
def _build_package_finder(
self,
options, # type: Values
session, # type: PipSession
target_python=None, # type: Optional[TargetPython]
ignore_requires_python=None, # type: Optional[bool]
):
# type: (...) -> PackageFinder
"""
Create a package finder appropriate to this requirement command.
:param ignore_requires_python: Whether to ignore incompatible
"Requires-Python" values in links. Defaults to False.
"""
search_scope = make_search_scope(options)
2019-06-28 20:14:55 +02:00
selection_prefs = SelectionPreferences(
allow_yanked=True,
format_control=options.format_control,
allow_all_prereleases=options.pre,
prefer_binary=options.prefer_binary,
ignore_requires_python=ignore_requires_python,
)
return PackageFinder.create(
search_scope=search_scope,
2019-06-28 20:14:55 +02:00
selection_prefs=selection_prefs,
trusted_hosts=options.trusted_hosts,
session=session,
2019-06-09 22:11:16 +02:00
target_python=target_python,
)