diff --git a/news/1234.feature.rst b/news/1234.feature.rst new file mode 100644 index 00000000000..1947ae559dd --- /dev/null +++ b/news/1234.feature.rst @@ -0,0 +1 @@ +The config precedence can now be overridden with a special ``config-precedence`` config value diff --git a/src/pip/_internal/commands/configuration.py b/src/pip/_internal/commands/configuration.py index 84b134e490b..4f73dafebeb 100644 --- a/src/pip/_internal/commands/configuration.py +++ b/src/pip/_internal/commands/configuration.py @@ -207,6 +207,7 @@ def list_config_values(self, options: Values, args: List[str]) -> None: write_output("%s, exists: %r", fname, file_exists) if file_exists: self.print_config_file_values(variant) + write_output("load-precedence: %s", ", ".join(self.configuration.load_order)) def print_config_file_values(self, variant: Kind) -> None: """Get key-value pairs from the file of a variant""" @@ -216,7 +217,7 @@ def print_config_file_values(self, variant: Kind) -> None: def print_env_var_values(self) -> None: """Get key-values pairs present as environment variables""" - write_output("%s:", "env_var") + write_output("%s:", "env-var") with indent_log(): for key, value in sorted(self.configuration.get_environ_vars()): env_var = f"PIP_{key.upper()}" diff --git a/src/pip/_internal/configuration.py b/src/pip/_internal/configuration.py index 8fd46c9b8e0..820849ec24c 100644 --- a/src/pip/_internal/configuration.py +++ b/src/pip/_internal/configuration.py @@ -110,6 +110,8 @@ def __init__(self, isolated: bool, load_only: Optional[Kind] = None) -> None: self.isolated = isolated self.load_only = load_only + self._load_order: Tuple[Kind, ...] = OVERRIDE_ORDER + # Because we keep track of where we got the data from self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = { variant: [] for variant in OVERRIDE_ORDER @@ -125,6 +127,13 @@ def load(self) -> None: if not self.isolated: self._load_environment_vars() + @property + def load_order(self) -> Tuple[Kind, ...]: + # The act of computing the config dictionary will result in the _load_order + # attribute being correctly updated. + _ = self._dictionary + return self._load_order + def get_file_to_edit(self) -> Optional[str]: """Returns the file with highest priority in configuration""" assert self.load_only is not None, "Need to be specified a file to be editing" @@ -225,11 +234,45 @@ def _ensure_have_load_only(self) -> None: @property def _dictionary(self) -> Dict[str, Any]: """A dictionary representing the loaded configuration.""" + + # We always read the config in the default load-order first, giving + # us a deterministic load-order configuration value. + config = self._blended_config_dict(OVERRIDE_ORDER) + + # Re-compose the config based on the desired override-order, if + # different to the default. + config_precedence: List[str] = ( + str(config.get("global.config-precedence", "")).strip().splitlines() + ) + if config_precedence: + bad_values = [ + value + for value in config_precedence + if value not in kinds.reverse_mapping + ] + if bad_values: + term_or_terms = "term" if len(bad_values) == 1 else "terms" + raise ConfigurationError( + f"Invalid config-precedence {term_or_terms} provided " + f"({','.join(bad_values)}). " + f"Valid values are {', '.join(map(repr, kinds.reverse_mapping))}." + ) + self._load_order = tuple( + getattr(kinds, kinds.reverse_mapping[value]) + for value in config_precedence + ) + if self._load_order != OVERRIDE_ORDER: + config = self._blended_config_dict(self._load_order) + return config + + def _blended_config_dict( + self, load_order: Tuple[Kind, ...] = OVERRIDE_ORDER + ) -> Dict[str, Any]: # NOTE: Dictionaries are not populated if not loaded. So, conditionals # are not needed here. retval = {} - for variant in OVERRIDE_ORDER: + for variant in load_order: retval.update(self._config[variant]) return retval