basecommand.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. """Base Command class, and related routines"""
  2. from __future__ import absolute_import
  3. import logging
  4. import logging.config
  5. import optparse
  6. import os
  7. import sys
  8. from pip._internal import cmdoptions
  9. from pip._internal.baseparser import (
  10. ConfigOptionParser, UpdatingDefaultsHelpFormatter,
  11. )
  12. from pip._internal.download import PipSession
  13. from pip._internal.exceptions import (
  14. BadCommand, CommandError, InstallationError, PreviousBuildDirError,
  15. UninstallationError,
  16. )
  17. from pip._internal.index import PackageFinder
  18. from pip._internal.locations import running_under_virtualenv
  19. from pip._internal.req.req_file import parse_requirements
  20. from pip._internal.req.req_install import InstallRequirement
  21. from pip._internal.status_codes import (
  22. ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
  23. VIRTUALENV_NOT_FOUND,
  24. )
  25. from pip._internal.utils.logging import setup_logging
  26. from pip._internal.utils.misc import get_prog, normalize_path
  27. from pip._internal.utils.outdated import pip_version_check
  28. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  29. if MYPY_CHECK_RUNNING:
  30. from typing import Optional # noqa: F401
  31. __all__ = ['Command']
  32. logger = logging.getLogger(__name__)
  33. class Command(object):
  34. name = None # type: Optional[str]
  35. usage = None # type: Optional[str]
  36. hidden = False # type: bool
  37. ignore_require_venv = False # type: bool
  38. def __init__(self, isolated=False):
  39. parser_kw = {
  40. 'usage': self.usage,
  41. 'prog': '%s %s' % (get_prog(), self.name),
  42. 'formatter': UpdatingDefaultsHelpFormatter(),
  43. 'add_help_option': False,
  44. 'name': self.name,
  45. 'description': self.__doc__,
  46. 'isolated': isolated,
  47. }
  48. self.parser = ConfigOptionParser(**parser_kw)
  49. # Commands should add options to this option group
  50. optgroup_name = '%s Options' % self.name.capitalize()
  51. self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
  52. # Add the general options
  53. gen_opts = cmdoptions.make_option_group(
  54. cmdoptions.general_group,
  55. self.parser,
  56. )
  57. self.parser.add_option_group(gen_opts)
  58. def _build_session(self, options, retries=None, timeout=None):
  59. session = PipSession(
  60. cache=(
  61. normalize_path(os.path.join(options.cache_dir, "http"))
  62. if options.cache_dir else None
  63. ),
  64. retries=retries if retries is not None else options.retries,
  65. insecure_hosts=options.trusted_hosts,
  66. )
  67. # Handle custom ca-bundles from the user
  68. if options.cert:
  69. session.verify = options.cert
  70. # Handle SSL client certificate
  71. if options.client_cert:
  72. session.cert = options.client_cert
  73. # Handle timeouts
  74. if options.timeout or timeout:
  75. session.timeout = (
  76. timeout if timeout is not None else options.timeout
  77. )
  78. # Handle configured proxies
  79. if options.proxy:
  80. session.proxies = {
  81. "http": options.proxy,
  82. "https": options.proxy,
  83. }
  84. # Determine if we can prompt the user for authentication or not
  85. session.auth.prompting = not options.no_input
  86. return session
  87. def parse_args(self, args):
  88. # factored out for testability
  89. return self.parser.parse_args(args)
  90. def main(self, args):
  91. options, args = self.parse_args(args)
  92. # Set verbosity so that it can be used elsewhere.
  93. self.verbosity = options.verbose - options.quiet
  94. setup_logging(
  95. verbosity=self.verbosity,
  96. no_color=options.no_color,
  97. user_log_file=options.log,
  98. )
  99. # TODO: Try to get these passing down from the command?
  100. # without resorting to os.environ to hold these.
  101. # This also affects isolated builds and it should.
  102. if options.no_input:
  103. os.environ['PIP_NO_INPUT'] = '1'
  104. if options.exists_action:
  105. os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
  106. if options.require_venv and not self.ignore_require_venv:
  107. # If a venv is required check if it can really be found
  108. if not running_under_virtualenv():
  109. logger.critical(
  110. 'Could not find an activated virtualenv (required).'
  111. )
  112. sys.exit(VIRTUALENV_NOT_FOUND)
  113. try:
  114. status = self.run(options, args)
  115. # FIXME: all commands should return an exit status
  116. # and when it is done, isinstance is not needed anymore
  117. if isinstance(status, int):
  118. return status
  119. except PreviousBuildDirError as exc:
  120. logger.critical(str(exc))
  121. logger.debug('Exception information:', exc_info=True)
  122. return PREVIOUS_BUILD_DIR_ERROR
  123. except (InstallationError, UninstallationError, BadCommand) as exc:
  124. logger.critical(str(exc))
  125. logger.debug('Exception information:', exc_info=True)
  126. return ERROR
  127. except CommandError as exc:
  128. logger.critical('ERROR: %s', exc)
  129. logger.debug('Exception information:', exc_info=True)
  130. return ERROR
  131. except KeyboardInterrupt:
  132. logger.critical('Operation cancelled by user')
  133. logger.debug('Exception information:', exc_info=True)
  134. return ERROR
  135. except BaseException:
  136. logger.critical('Exception:', exc_info=True)
  137. return UNKNOWN_ERROR
  138. finally:
  139. # Check if we're using the latest version of pip available
  140. skip_version_check = (
  141. options.disable_pip_version_check or
  142. getattr(options, "no_index", False)
  143. )
  144. if not skip_version_check:
  145. session = self._build_session(
  146. options,
  147. retries=0,
  148. timeout=min(5, options.timeout)
  149. )
  150. with session:
  151. pip_version_check(session, options)
  152. # Shutdown the logging module
  153. logging.shutdown()
  154. return SUCCESS
  155. class RequirementCommand(Command):
  156. @staticmethod
  157. def populate_requirement_set(requirement_set, args, options, finder,
  158. session, name, wheel_cache):
  159. """
  160. Marshal cmd line args into a requirement set.
  161. """
  162. # NOTE: As a side-effect, options.require_hashes and
  163. # requirement_set.require_hashes may be updated
  164. for filename in options.constraints:
  165. for req_to_add in parse_requirements(
  166. filename,
  167. constraint=True, finder=finder, options=options,
  168. session=session, wheel_cache=wheel_cache):
  169. req_to_add.is_direct = True
  170. requirement_set.add_requirement(req_to_add)
  171. for req in args:
  172. req_to_add = InstallRequirement.from_line(
  173. req, None, isolated=options.isolated_mode,
  174. wheel_cache=wheel_cache
  175. )
  176. req_to_add.is_direct = True
  177. requirement_set.add_requirement(req_to_add)
  178. for req in options.editables:
  179. req_to_add = InstallRequirement.from_editable(
  180. req,
  181. isolated=options.isolated_mode,
  182. wheel_cache=wheel_cache
  183. )
  184. req_to_add.is_direct = True
  185. requirement_set.add_requirement(req_to_add)
  186. for filename in options.requirements:
  187. for req_to_add in parse_requirements(
  188. filename,
  189. finder=finder, options=options, session=session,
  190. wheel_cache=wheel_cache):
  191. req_to_add.is_direct = True
  192. requirement_set.add_requirement(req_to_add)
  193. # If --require-hashes was a line in a requirements file, tell
  194. # RequirementSet about it:
  195. requirement_set.require_hashes = options.require_hashes
  196. if not (args or options.editables or options.requirements):
  197. opts = {'name': name}
  198. if options.find_links:
  199. raise CommandError(
  200. 'You must give at least one requirement to %(name)s '
  201. '(maybe you meant "pip %(name)s %(links)s"?)' %
  202. dict(opts, links=' '.join(options.find_links)))
  203. else:
  204. raise CommandError(
  205. 'You must give at least one requirement to %(name)s '
  206. '(see "pip help %(name)s")' % opts)
  207. def _build_package_finder(self, options, session,
  208. platform=None, python_versions=None,
  209. abi=None, implementation=None):
  210. """
  211. Create a package finder appropriate to this requirement command.
  212. """
  213. index_urls = [options.index_url] + options.extra_index_urls
  214. if options.no_index:
  215. logger.debug('Ignoring indexes: %s', ','.join(index_urls))
  216. index_urls = []
  217. return PackageFinder(
  218. find_links=options.find_links,
  219. format_control=options.format_control,
  220. index_urls=index_urls,
  221. trusted_hosts=options.trusted_hosts,
  222. allow_all_prereleases=options.pre,
  223. process_dependency_links=options.process_dependency_links,
  224. session=session,
  225. platform=platform,
  226. versions=python_versions,
  227. abi=abi,
  228. implementation=implementation,
  229. prefer_binary=options.prefer_binary,
  230. )