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.

227 lines
7.0 KiB

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