You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

317 regels
11 KiB

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