| | import logging |
| | import os |
| | import subprocess |
| | from optparse import Values |
| | from typing import Any, List, Optional |
| |
|
| | from pip._internal.cli.base_command import Command |
| | from pip._internal.cli.status_codes import ERROR, SUCCESS |
| | from pip._internal.configuration import ( |
| | Configuration, |
| | Kind, |
| | get_configuration_files, |
| | kinds, |
| | ) |
| | from pip._internal.exceptions import PipError |
| | from pip._internal.utils.logging import indent_log |
| | from pip._internal.utils.misc import get_prog, write_output |
| |
|
| | logger = logging.getLogger(__name__) |
| |
|
| |
|
| | class ConfigurationCommand(Command): |
| | """ |
| | Manage local and global configuration. |
| | |
| | Subcommands: |
| | |
| | - list: List the active configuration (or from the file specified) |
| | - edit: Edit the configuration file in an editor |
| | - get: Get the value associated with command.option |
| | - set: Set the command.option=value |
| | - unset: Unset the value associated with command.option |
| | - debug: List the configuration files and values defined under them |
| | |
| | Configuration keys should be dot separated command and option name, |
| | with the special prefix "global" affecting any command. For example, |
| | "pip config set global.index-url https://example.org/" would configure |
| | the index url for all commands, but "pip config set download.timeout 10" |
| | would configure a 10 second timeout only for "pip download" commands. |
| | |
| | If none of --user, --global and --site are passed, a virtual |
| | environment configuration file is used if one is active and the file |
| | exists. Otherwise, all modifications happen to the user file by |
| | default. |
| | """ |
| |
|
| | ignore_require_venv = True |
| | usage = """ |
| | %prog [<file-option>] list |
| | %prog [<file-option>] [--editor <editor-path>] edit |
| | |
| | %prog [<file-option>] get command.option |
| | %prog [<file-option>] set command.option value |
| | %prog [<file-option>] unset command.option |
| | %prog [<file-option>] debug |
| | """ |
| |
|
| | def add_options(self) -> None: |
| | self.cmd_opts.add_option( |
| | "--editor", |
| | dest="editor", |
| | action="store", |
| | default=None, |
| | help=( |
| | "Editor to use to edit the file. Uses VISUAL or EDITOR " |
| | "environment variables if not provided." |
| | ), |
| | ) |
| |
|
| | self.cmd_opts.add_option( |
| | "--global", |
| | dest="global_file", |
| | action="store_true", |
| | default=False, |
| | help="Use the system-wide configuration file only", |
| | ) |
| |
|
| | self.cmd_opts.add_option( |
| | "--user", |
| | dest="user_file", |
| | action="store_true", |
| | default=False, |
| | help="Use the user configuration file only", |
| | ) |
| |
|
| | self.cmd_opts.add_option( |
| | "--site", |
| | dest="site_file", |
| | action="store_true", |
| | default=False, |
| | help="Use the current environment configuration file only", |
| | ) |
| |
|
| | self.parser.insert_option_group(0, self.cmd_opts) |
| |
|
| | def run(self, options: Values, args: List[str]) -> int: |
| | handlers = { |
| | "list": self.list_values, |
| | "edit": self.open_in_editor, |
| | "get": self.get_name, |
| | "set": self.set_name_value, |
| | "unset": self.unset_name, |
| | "debug": self.list_config_values, |
| | } |
| |
|
| | |
| | if not args or args[0] not in handlers: |
| | logger.error( |
| | "Need an action (%s) to perform.", |
| | ", ".join(sorted(handlers)), |
| | ) |
| | return ERROR |
| |
|
| | action = args[0] |
| |
|
| | |
| | |
| | try: |
| | load_only = self._determine_file( |
| | options, need_value=(action in ["get", "set", "unset", "edit"]) |
| | ) |
| | except PipError as e: |
| | logger.error(e.args[0]) |
| | return ERROR |
| |
|
| | |
| | self.configuration = Configuration( |
| | isolated=options.isolated_mode, load_only=load_only |
| | ) |
| | self.configuration.load() |
| |
|
| | |
| | try: |
| | handlers[action](options, args[1:]) |
| | except PipError as e: |
| | logger.error(e.args[0]) |
| | return ERROR |
| |
|
| | return SUCCESS |
| |
|
| | def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]: |
| | file_options = [ |
| | key |
| | for key, value in ( |
| | (kinds.USER, options.user_file), |
| | (kinds.GLOBAL, options.global_file), |
| | (kinds.SITE, options.site_file), |
| | ) |
| | if value |
| | ] |
| |
|
| | if not file_options: |
| | if not need_value: |
| | return None |
| | |
| | elif any( |
| | os.path.exists(site_config_file) |
| | for site_config_file in get_configuration_files()[kinds.SITE] |
| | ): |
| | return kinds.SITE |
| | else: |
| | return kinds.USER |
| | elif len(file_options) == 1: |
| | return file_options[0] |
| |
|
| | raise PipError( |
| | "Need exactly one file to operate upon " |
| | "(--user, --site, --global) to perform." |
| | ) |
| |
|
| | def list_values(self, options: Values, args: List[str]) -> None: |
| | self._get_n_args(args, "list", n=0) |
| |
|
| | for key, value in sorted(self.configuration.items()): |
| | write_output("%s=%r", key, value) |
| |
|
| | def get_name(self, options: Values, args: List[str]) -> None: |
| | key = self._get_n_args(args, "get [name]", n=1) |
| | value = self.configuration.get_value(key) |
| |
|
| | write_output("%s", value) |
| |
|
| | def set_name_value(self, options: Values, args: List[str]) -> None: |
| | key, value = self._get_n_args(args, "set [name] [value]", n=2) |
| | self.configuration.set_value(key, value) |
| |
|
| | self._save_configuration() |
| |
|
| | def unset_name(self, options: Values, args: List[str]) -> None: |
| | key = self._get_n_args(args, "unset [name]", n=1) |
| | self.configuration.unset_value(key) |
| |
|
| | self._save_configuration() |
| |
|
| | def list_config_values(self, options: Values, args: List[str]) -> None: |
| | """List config key-value pairs across different config files""" |
| | self._get_n_args(args, "debug", n=0) |
| |
|
| | self.print_env_var_values() |
| | |
| | |
| | for variant, files in sorted(self.configuration.iter_config_files()): |
| | write_output("%s:", variant) |
| | for fname in files: |
| | with indent_log(): |
| | file_exists = os.path.exists(fname) |
| | write_output("%s, exists: %r", fname, file_exists) |
| | if file_exists: |
| | self.print_config_file_values(variant) |
| |
|
| | def print_config_file_values(self, variant: Kind) -> None: |
| | """Get key-value pairs from the file of a variant""" |
| | for name, value in self.configuration.get_values_in_config(variant).items(): |
| | with indent_log(): |
| | write_output("%s: %s", name, value) |
| |
|
| | def print_env_var_values(self) -> None: |
| | """Get key-values pairs present as environment variables""" |
| | write_output("%s:", "env_var") |
| | with indent_log(): |
| | for key, value in sorted(self.configuration.get_environ_vars()): |
| | env_var = f"PIP_{key.upper()}" |
| | write_output("%s=%r", env_var, value) |
| |
|
| | def open_in_editor(self, options: Values, args: List[str]) -> None: |
| | editor = self._determine_editor(options) |
| |
|
| | fname = self.configuration.get_file_to_edit() |
| | if fname is None: |
| | raise PipError("Could not determine appropriate file.") |
| | elif '"' in fname: |
| | |
| | |
| | raise PipError( |
| | f'Can not open an editor for a file name containing "\n{fname}' |
| | ) |
| |
|
| | try: |
| | subprocess.check_call(f'{editor} "{fname}"', shell=True) |
| | except FileNotFoundError as e: |
| | if not e.filename: |
| | e.filename = editor |
| | raise |
| | except subprocess.CalledProcessError as e: |
| | raise PipError( |
| | "Editor Subprocess exited with exit code {}".format(e.returncode) |
| | ) |
| |
|
| | def _get_n_args(self, args: List[str], example: str, n: int) -> Any: |
| | """Helper to make sure the command got the right number of arguments""" |
| | if len(args) != n: |
| | msg = ( |
| | "Got unexpected number of arguments, expected {}. " |
| | '(example: "{} config {}")' |
| | ).format(n, get_prog(), example) |
| | raise PipError(msg) |
| |
|
| | if n == 1: |
| | return args[0] |
| | else: |
| | return args |
| |
|
| | def _save_configuration(self) -> None: |
| | |
| | |
| | try: |
| | self.configuration.save() |
| | except Exception: |
| | logger.exception( |
| | "Unable to save configuration. Please report this as a bug." |
| | ) |
| | raise PipError("Internal Error.") |
| |
|
| | def _determine_editor(self, options: Values) -> str: |
| | if options.editor is not None: |
| | return options.editor |
| | elif "VISUAL" in os.environ: |
| | return os.environ["VISUAL"] |
| | elif "EDITOR" in os.environ: |
| | return os.environ["EDITOR"] |
| | else: |
| | raise PipError("Could not determine editor to use.") |
| |
|