basecommand.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. """Base Command class, and related routines"""
  2. from __future__ import absolute_import
  3. import logging
  4. import os
  5. import sys
  6. import optparse
  7. import warnings
  8. from pip import cmdoptions
  9. from pip.index import PackageFinder
  10. from pip.locations import running_under_virtualenv
  11. from pip.download import PipSession
  12. from pip.exceptions import (BadCommand, InstallationError, UninstallationError,
  13. CommandError, PreviousBuildDirError)
  14. from pip.compat import logging_dictConfig
  15. from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
  16. from pip.req import InstallRequirement, parse_requirements
  17. from pip.status_codes import (
  18. SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND,
  19. PREVIOUS_BUILD_DIR_ERROR,
  20. )
  21. from pip.utils import deprecation, get_prog, normalize_path
  22. from pip.utils.logging import IndentingFormatter
  23. from pip.utils.outdated import pip_version_check
  24. __all__ = ['Command']
  25. logger = logging.getLogger(__name__)
  26. class Command(object):
  27. name = None
  28. usage = None
  29. hidden = False
  30. log_streams = ("ext://sys.stdout", "ext://sys.stderr")
  31. def __init__(self, isolated=False):
  32. parser_kw = {
  33. 'usage': self.usage,
  34. 'prog': '%s %s' % (get_prog(), self.name),
  35. 'formatter': UpdatingDefaultsHelpFormatter(),
  36. 'add_help_option': False,
  37. 'name': self.name,
  38. 'description': self.__doc__,
  39. 'isolated': isolated,
  40. }
  41. self.parser = ConfigOptionParser(**parser_kw)
  42. # Commands should add options to this option group
  43. optgroup_name = '%s Options' % self.name.capitalize()
  44. self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
  45. # Add the general options
  46. gen_opts = cmdoptions.make_option_group(
  47. cmdoptions.general_group,
  48. self.parser,
  49. )
  50. self.parser.add_option_group(gen_opts)
  51. def _build_session(self, options, retries=None, timeout=None):
  52. session = PipSession(
  53. cache=(
  54. normalize_path(os.path.join(options.cache_dir, "http"))
  55. if options.cache_dir else None
  56. ),
  57. retries=retries if retries is not None else options.retries,
  58. insecure_hosts=options.trusted_hosts,
  59. )
  60. # Handle custom ca-bundles from the user
  61. if options.cert:
  62. session.verify = options.cert
  63. # Handle SSL client certificate
  64. if options.client_cert:
  65. session.cert = options.client_cert
  66. # Handle timeouts
  67. if options.timeout or timeout:
  68. session.timeout = (
  69. timeout if timeout is not None else options.timeout
  70. )
  71. # Handle configured proxies
  72. if options.proxy:
  73. session.proxies = {
  74. "http": options.proxy,
  75. "https": options.proxy,
  76. }
  77. # Determine if we can prompt the user for authentication or not
  78. session.auth.prompting = not options.no_input
  79. return session
  80. def parse_args(self, args):
  81. # factored out for testability
  82. return self.parser.parse_args(args)
  83. def main(self, args):
  84. options, args = self.parse_args(args)
  85. if options.quiet:
  86. if options.quiet == 1:
  87. level = "WARNING"
  88. if options.quiet == 2:
  89. level = "ERROR"
  90. else:
  91. level = "CRITICAL"
  92. elif options.verbose:
  93. level = "DEBUG"
  94. else:
  95. level = "INFO"
  96. # The root logger should match the "console" level *unless* we
  97. # specified "--log" to send debug logs to a file.
  98. root_level = level
  99. if options.log:
  100. root_level = "DEBUG"
  101. logging_dictConfig({
  102. "version": 1,
  103. "disable_existing_loggers": False,
  104. "filters": {
  105. "exclude_warnings": {
  106. "()": "pip.utils.logging.MaxLevelFilter",
  107. "level": logging.WARNING,
  108. },
  109. },
  110. "formatters": {
  111. "indent": {
  112. "()": IndentingFormatter,
  113. "format": "%(message)s",
  114. },
  115. },
  116. "handlers": {
  117. "console": {
  118. "level": level,
  119. "class": "pip.utils.logging.ColorizedStreamHandler",
  120. "stream": self.log_streams[0],
  121. "filters": ["exclude_warnings"],
  122. "formatter": "indent",
  123. },
  124. "console_errors": {
  125. "level": "WARNING",
  126. "class": "pip.utils.logging.ColorizedStreamHandler",
  127. "stream": self.log_streams[1],
  128. "formatter": "indent",
  129. },
  130. "user_log": {
  131. "level": "DEBUG",
  132. "class": "pip.utils.logging.BetterRotatingFileHandler",
  133. "filename": options.log or "/dev/null",
  134. "delay": True,
  135. "formatter": "indent",
  136. },
  137. },
  138. "root": {
  139. "level": root_level,
  140. "handlers": list(filter(None, [
  141. "console",
  142. "console_errors",
  143. "user_log" if options.log else None,
  144. ])),
  145. },
  146. # Disable any logging besides WARNING unless we have DEBUG level
  147. # logging enabled. These use both pip._vendor and the bare names
  148. # for the case where someone unbundles our libraries.
  149. "loggers": dict(
  150. (
  151. name,
  152. {
  153. "level": (
  154. "WARNING"
  155. if level in ["INFO", "ERROR"]
  156. else "DEBUG"
  157. ),
  158. },
  159. )
  160. for name in ["pip._vendor", "distlib", "requests", "urllib3"]
  161. ),
  162. })
  163. if sys.version_info[:2] == (2, 6):
  164. warnings.warn(
  165. "Python 2.6 is no longer supported by the Python core team, "
  166. "please upgrade your Python. A future version of pip will "
  167. "drop support for Python 2.6",
  168. deprecation.Python26DeprecationWarning
  169. )
  170. # TODO: try to get these passing down from the command?
  171. # without resorting to os.environ to hold these.
  172. if options.no_input:
  173. os.environ['PIP_NO_INPUT'] = '1'
  174. if options.exists_action:
  175. os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
  176. if options.require_venv:
  177. # If a venv is required check if it can really be found
  178. if not running_under_virtualenv():
  179. logger.critical(
  180. 'Could not find an activated virtualenv (required).'
  181. )
  182. sys.exit(VIRTUALENV_NOT_FOUND)
  183. try:
  184. status = self.run(options, args)
  185. # FIXME: all commands should return an exit status
  186. # and when it is done, isinstance is not needed anymore
  187. if isinstance(status, int):
  188. return status
  189. except PreviousBuildDirError as exc:
  190. logger.critical(str(exc))
  191. logger.debug('Exception information:', exc_info=True)
  192. return PREVIOUS_BUILD_DIR_ERROR
  193. except (InstallationError, UninstallationError, BadCommand) as exc:
  194. logger.critical(str(exc))
  195. logger.debug('Exception information:', exc_info=True)
  196. return ERROR
  197. except CommandError as exc:
  198. logger.critical('ERROR: %s', exc)
  199. logger.debug('Exception information:', exc_info=True)
  200. return ERROR
  201. except KeyboardInterrupt:
  202. logger.critical('Operation cancelled by user')
  203. logger.debug('Exception information:', exc_info=True)
  204. return ERROR
  205. except:
  206. logger.critical('Exception:', exc_info=True)
  207. return UNKNOWN_ERROR
  208. finally:
  209. # Check if we're using the latest version of pip available
  210. if (not options.disable_pip_version_check and not
  211. getattr(options, "no_index", False)):
  212. with self._build_session(
  213. options,
  214. retries=0,
  215. timeout=min(5, options.timeout)) as session:
  216. pip_version_check(session)
  217. return SUCCESS
  218. class RequirementCommand(Command):
  219. @staticmethod
  220. def populate_requirement_set(requirement_set, args, options, finder,
  221. session, name, wheel_cache):
  222. """
  223. Marshal cmd line args into a requirement set.
  224. """
  225. for filename in options.constraints:
  226. for req in parse_requirements(
  227. filename,
  228. constraint=True, finder=finder, options=options,
  229. session=session, wheel_cache=wheel_cache):
  230. requirement_set.add_requirement(req)
  231. for req in args:
  232. requirement_set.add_requirement(
  233. InstallRequirement.from_line(
  234. req, None, isolated=options.isolated_mode,
  235. wheel_cache=wheel_cache
  236. )
  237. )
  238. for req in options.editables:
  239. requirement_set.add_requirement(
  240. InstallRequirement.from_editable(
  241. req,
  242. default_vcs=options.default_vcs,
  243. isolated=options.isolated_mode,
  244. wheel_cache=wheel_cache
  245. )
  246. )
  247. found_req_in_file = False
  248. for filename in options.requirements:
  249. for req in parse_requirements(
  250. filename,
  251. finder=finder, options=options, session=session,
  252. wheel_cache=wheel_cache):
  253. found_req_in_file = True
  254. requirement_set.add_requirement(req)
  255. # If --require-hashes was a line in a requirements file, tell
  256. # RequirementSet about it:
  257. requirement_set.require_hashes = options.require_hashes
  258. if not (args or options.editables or found_req_in_file):
  259. opts = {'name': name}
  260. if options.find_links:
  261. msg = ('You must give at least one requirement to '
  262. '%(name)s (maybe you meant "pip %(name)s '
  263. '%(links)s"?)' %
  264. dict(opts, links=' '.join(options.find_links)))
  265. else:
  266. msg = ('You must give at least one requirement '
  267. 'to %(name)s (see "pip help %(name)s")' % opts)
  268. logger.warning(msg)
  269. def _build_package_finder(self, options, session,
  270. platform=None, python_versions=None,
  271. abi=None, implementation=None):
  272. """
  273. Create a package finder appropriate to this requirement command.
  274. """
  275. index_urls = [options.index_url] + options.extra_index_urls
  276. if options.no_index:
  277. logger.debug('Ignoring indexes: %s', ','.join(index_urls))
  278. index_urls = []
  279. return PackageFinder(
  280. find_links=options.find_links,
  281. format_control=options.format_control,
  282. index_urls=index_urls,
  283. trusted_hosts=options.trusted_hosts,
  284. allow_all_prereleases=options.pre,
  285. process_dependency_links=options.process_dependency_links,
  286. session=session,
  287. platform=platform,
  288. versions=python_versions,
  289. abi=abi,
  290. implementation=implementation,
  291. )