configuration.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. # The following comment should be removed at some point in the future.
  2. # mypy: disallow-untyped-defs=False
  3. import logging
  4. import os
  5. import subprocess
  6. from pip._internal.cli.base_command import Command
  7. from pip._internal.cli.status_codes import ERROR, SUCCESS
  8. from pip._internal.configuration import (
  9. Configuration,
  10. get_configuration_files,
  11. kinds,
  12. )
  13. from pip._internal.exceptions import PipError
  14. from pip._internal.utils.misc import get_prog, write_output
  15. logger = logging.getLogger(__name__)
  16. class ConfigurationCommand(Command):
  17. """Manage local and global configuration.
  18. Subcommands:
  19. list: List the active configuration (or from the file specified)
  20. edit: Edit the configuration file in an editor
  21. get: Get the value associated with name
  22. set: Set the name=value
  23. unset: Unset the value associated with name
  24. If none of --user, --global and --site are passed, a virtual
  25. environment configuration file is used if one is active and the file
  26. exists. Otherwise, all modifications happen on the to the user file by
  27. default.
  28. """
  29. ignore_require_venv = True
  30. usage = """
  31. %prog [<file-option>] list
  32. %prog [<file-option>] [--editor <editor-path>] edit
  33. %prog [<file-option>] get name
  34. %prog [<file-option>] set name value
  35. %prog [<file-option>] unset name
  36. """
  37. def __init__(self, *args, **kwargs):
  38. super(ConfigurationCommand, self).__init__(*args, **kwargs)
  39. self.configuration = None
  40. self.cmd_opts.add_option(
  41. '--editor',
  42. dest='editor',
  43. action='store',
  44. default=None,
  45. help=(
  46. 'Editor to use to edit the file. Uses VISUAL or EDITOR '
  47. 'environment variables if not provided.'
  48. )
  49. )
  50. self.cmd_opts.add_option(
  51. '--global',
  52. dest='global_file',
  53. action='store_true',
  54. default=False,
  55. help='Use the system-wide configuration file only'
  56. )
  57. self.cmd_opts.add_option(
  58. '--user',
  59. dest='user_file',
  60. action='store_true',
  61. default=False,
  62. help='Use the user configuration file only'
  63. )
  64. self.cmd_opts.add_option(
  65. '--site',
  66. dest='site_file',
  67. action='store_true',
  68. default=False,
  69. help='Use the current environment configuration file only'
  70. )
  71. self.parser.insert_option_group(0, self.cmd_opts)
  72. def run(self, options, args):
  73. handlers = {
  74. "list": self.list_values,
  75. "edit": self.open_in_editor,
  76. "get": self.get_name,
  77. "set": self.set_name_value,
  78. "unset": self.unset_name
  79. }
  80. # Determine action
  81. if not args or args[0] not in handlers:
  82. logger.error("Need an action ({}) to perform.".format(
  83. ", ".join(sorted(handlers)))
  84. )
  85. return ERROR
  86. action = args[0]
  87. # Determine which configuration files are to be loaded
  88. # Depends on whether the command is modifying.
  89. try:
  90. load_only = self._determine_file(
  91. options, need_value=(action in ["get", "set", "unset", "edit"])
  92. )
  93. except PipError as e:
  94. logger.error(e.args[0])
  95. return ERROR
  96. # Load a new configuration
  97. self.configuration = Configuration(
  98. isolated=options.isolated_mode, load_only=load_only
  99. )
  100. self.configuration.load()
  101. # Error handling happens here, not in the action-handlers.
  102. try:
  103. handlers[action](options, args[1:])
  104. except PipError as e:
  105. logger.error(e.args[0])
  106. return ERROR
  107. return SUCCESS
  108. def _determine_file(self, options, need_value):
  109. file_options = [key for key, value in (
  110. (kinds.USER, options.user_file),
  111. (kinds.GLOBAL, options.global_file),
  112. (kinds.SITE, options.site_file),
  113. ) if value]
  114. if not file_options:
  115. if not need_value:
  116. return None
  117. # Default to user, unless there's a site file.
  118. elif any(
  119. os.path.exists(site_config_file)
  120. for site_config_file in get_configuration_files()[kinds.SITE]
  121. ):
  122. return kinds.SITE
  123. else:
  124. return kinds.USER
  125. elif len(file_options) == 1:
  126. return file_options[0]
  127. raise PipError(
  128. "Need exactly one file to operate upon "
  129. "(--user, --site, --global) to perform."
  130. )
  131. def list_values(self, options, args):
  132. self._get_n_args(args, "list", n=0)
  133. for key, value in sorted(self.configuration.items()):
  134. write_output("%s=%r", key, value)
  135. def get_name(self, options, args):
  136. key = self._get_n_args(args, "get [name]", n=1)
  137. value = self.configuration.get_value(key)
  138. write_output("%s", value)
  139. def set_name_value(self, options, args):
  140. key, value = self._get_n_args(args, "set [name] [value]", n=2)
  141. self.configuration.set_value(key, value)
  142. self._save_configuration()
  143. def unset_name(self, options, args):
  144. key = self._get_n_args(args, "unset [name]", n=1)
  145. self.configuration.unset_value(key)
  146. self._save_configuration()
  147. def open_in_editor(self, options, args):
  148. editor = self._determine_editor(options)
  149. fname = self.configuration.get_file_to_edit()
  150. if fname is None:
  151. raise PipError("Could not determine appropriate file.")
  152. try:
  153. subprocess.check_call([editor, fname])
  154. except subprocess.CalledProcessError as e:
  155. raise PipError(
  156. "Editor Subprocess exited with exit code {}"
  157. .format(e.returncode)
  158. )
  159. def _get_n_args(self, args, example, n):
  160. """Helper to make sure the command got the right number of arguments
  161. """
  162. if len(args) != n:
  163. msg = (
  164. 'Got unexpected number of arguments, expected {}. '
  165. '(example: "{} config {}")'
  166. ).format(n, get_prog(), example)
  167. raise PipError(msg)
  168. if n == 1:
  169. return args[0]
  170. else:
  171. return args
  172. def _save_configuration(self):
  173. # We successfully ran a modifying command. Need to save the
  174. # configuration.
  175. try:
  176. self.configuration.save()
  177. except Exception:
  178. logger.error(
  179. "Unable to save configuration. Please report this as a bug.",
  180. exc_info=1
  181. )
  182. raise PipError("Internal Error.")
  183. def _determine_editor(self, options):
  184. if options.editor is not None:
  185. return options.editor
  186. elif "VISUAL" in os.environ:
  187. return os.environ["VISUAL"]
  188. elif "EDITOR" in os.environ:
  189. return os.environ["EDITOR"]
  190. else:
  191. raise PipError("Could not determine editor to use.")