Compare commits

...

13 Commits

Author SHA1 Message Date
chrysle 03ad36fcb3
Merge af49b81e05 into a15dd75d98 2023-11-29 15:16:42 -07:00
Tzu-ping Chung a15dd75d98
Merge pull request #12417 from xqm32/fix-outdated-pip-install 2023-11-28 16:08:29 +09:00
Tzu-ping Chung d8ab6dc6c1 Clarify news fragment 2023-11-28 15:06:25 +08:00
Qiming Xu fe10d368f6
Add end line 2023-11-28 14:25:56 +08:00
Qiming Xu 28250baffb
Fix line wrap length and add news entry 2023-11-28 14:17:51 +08:00
Qiming Xu 88ac529219
Fix outdated pip install argument description 2023-11-28 13:15:31 +08:00
chrysle af49b81e05
Merge branch 'main' into separate-config-same-level 2023-10-23 15:49:23 +02:00
Pradyun Gedam ed035ab6f8
Merge branch 'main' into separate-config-same-level 2023-09-10 00:23:54 +01:00
chrysle 8d6c282c91
Apply suggestions from code review
Co-authored-by: Tzu-ping Chung <uranusjr@gmail.com>
Co-authored-by: Pradyun Gedam <pradyunsg@gmail.com>
2023-09-09 17:09:55 +02:00
chrysle d9b5a9df85
Apply suggestions from code review 2023-09-05 19:13:13 +02:00
chrysle f12b8db25f
Update NEWS entry 2023-09-05 19:13:13 +02:00
chrysle 4bd343f805
Add functional test 2023-09-05 19:13:12 +02:00
chrysle 4b87307b34
Correctly assign configuration to origin files with `pip config` 2023-09-05 19:13:12 +02:00
7 changed files with 104 additions and 30 deletions

View File

@ -45,8 +45,8 @@ When looking at the items to be installed, pip checks what type of item
each is, in the following order:
1. Project or archive URL.
2. Local directory (which must contain a ``setup.py``, or pip will report
an error).
2. Local directory (which must contain a ``pyproject.toml`` or ``setup.py``,
otherwise pip will report an error).
3. Local file (a sdist or wheel format archive, following the naming
conventions for those formats).
4. A requirement, as specified in :pep:`440`.

1
news/12099.bugfix.rst Normal file
View File

@ -0,0 +1 @@
This change will fix the incorrect assignment of configuration values to files by ``pip config debug``

1
news/12417.doc.rst Normal file
View File

@ -0,0 +1 @@
Fix outdated pip install argument description in documentation.

View File

@ -185,23 +185,38 @@ class ConfigOptionParser(CustomOptionParser):
section_items: Dict[str, List[Tuple[str, Any]]] = {
name: [] for name in override_order
}
for section_key, val in self.config.items():
# ignore empty values
if not val:
logger.debug(
"Ignoring configuration key '%s' as it's value is empty.",
section_key,
)
continue
section, key = section_key.split(".", 1)
if section in override_order:
section_items[section].append((key, val))
for key, value in self.config.items():
if not isinstance(value, dict):
# ignore empty values
if not value:
logger.debug(
"Ignoring configuration key '%s' as it's empty.",
key,
)
continue
# Yield each group in their override order
for section in override_order:
for key, val in section_items[section]:
yield key, val
section, key = key.split(".", 1)
if section in override_order:
section_items[section].append((key, value))
else:
for section_key, val in value.items():
# ignore empty values
if not val:
logger.debug(
"Ignoring configuration key '%s' as it's value is empty.",
section_key,
)
continue
section, key = section_key.split(".", 1)
if section in override_order:
section_items[section].append((key, val))
# Yield each group in their override order
for section in override_order:
for key, val in section_items[section]:
yield key, val
def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]:
"""Updates the given defaults with values from the config files and

View File

@ -172,7 +172,11 @@ class ConfigurationCommand(Command):
self._get_n_args(args, "list", n=0)
for key, value in sorted(self.configuration.items()):
write_output("%s=%r", key, value)
if isinstance(value, dict):
for key, value in sorted(value.items()):
write_output("%s=%r", key, value)
else:
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)
@ -206,13 +210,15 @@ class ConfigurationCommand(Command):
file_exists = os.path.exists(fname)
write_output("%s, exists: %r", fname, file_exists)
if file_exists:
self.print_config_file_values(variant)
self.print_config_file_values(variant, fname)
def print_config_file_values(self, variant: Kind) -> None:
def print_config_file_values(self, variant: Kind, fname: str) -> 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)
if name == fname:
for confname, confvalue in value.items():
write_output("%s: %s", confname, confvalue)
def print_env_var_values(self) -> None:
"""Get key-values pairs present as environment variables"""

View File

@ -114,7 +114,7 @@ class Configuration:
self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = {
variant: [] for variant in OVERRIDE_ORDER
}
self._config: Dict[Kind, Dict[str, Any]] = {
self._config: Dict[Kind, Dict[str, Dict[str, Any]]] = {
variant: {} for variant in OVERRIDE_ORDER
}
self._modified_parsers: List[Tuple[str, RawConfigParser]] = []
@ -145,7 +145,13 @@ class Configuration:
orig_key = key
key = _normalize_name(key)
try:
return self._dictionary[key]
clean_config = {}
for k, value in self._dictionary.items():
if isinstance(value, dict):
clean_config.update(value)
else:
clean_config[k] = value
return clean_config[key]
except KeyError:
# disassembling triggers a more useful error message than simply
# "No such key" in the case that the key isn't in the form command.option
@ -168,7 +174,8 @@ class Configuration:
parser.add_section(section)
parser.set(section, name, value)
self._config[self.load_only][key] = value
self._config[self.load_only].setdefault(fname, {})
self._config[self.load_only][fname][key] = value
self._mark_as_modified(fname, parser)
def unset_value(self, key: str) -> None:
@ -178,11 +185,14 @@ class Configuration:
self._ensure_have_load_only()
assert self.load_only
if key not in self._config[self.load_only]:
raise ConfigurationError(f"No such key - {orig_key}")
fname, parser = self._get_parser_to_modify()
if (
key not in self._config[self.load_only][fname]
and key not in self._config[self.load_only]
):
raise ConfigurationError(f"No such key - {orig_key}")
if parser is not None:
section, name = _disassemble_key(key)
if not (
@ -197,8 +207,10 @@ class Configuration:
if not parser.items(section):
parser.remove_section(section)
self._mark_as_modified(fname, parser)
del self._config[self.load_only][key]
try:
del self._config[self.load_only][fname][key]
except KeyError:
del self._config[self.load_only][key]
def save(self) -> None:
"""Save the current in-memory state."""
@ -270,7 +282,8 @@ class Configuration:
for section in parser.sections():
items = parser.items(section)
self._config[variant].update(self._normalized_keys(section, items))
self._config[variant].setdefault(fname, {})
self._config[variant][fname].update(self._normalized_keys(section, items))
return parser

View File

@ -1,5 +1,6 @@
"""Tests for the config command
"""
import os
import re
import textwrap
@ -110,6 +111,9 @@ class TestBasicLoading(ConfigurationMixin):
assert "freeze.timeout: 10" in result.stdout
assert re.search(r"user:\n( .+\n)+", result.stdout)
# Avoid state leaking for tests re-using the new config file
os.remove(new_config_file)
def test_site_values(
self, script: PipTestEnvironment, virtualenv: VirtualEnvironment
) -> None:
@ -147,3 +151,37 @@ class TestBasicLoading(ConfigurationMixin):
"config", "edit", "--editor", "notrealeditor", expect_error=True
)
assert "notrealeditor" in result.stderr
def test_config_separated(
self, script: PipTestEnvironment, virtualenv: VirtualEnvironment
) -> None:
"""Test that the pip configuration values in the different config sections
are correctly assigned to their origin files.
"""
# Use new config file
new_config_file = get_configuration_files()[kinds.USER][1]
# Get legacy config file and touch it for testing purposes
legacy_config_file = get_configuration_files()[kinds.USER][0]
os.makedirs(os.path.dirname(legacy_config_file))
open(legacy_config_file, "a").close()
# Site config file
site_config_file = virtualenv.location / CONFIG_BASENAME
script.pip("config", "--user", "set", "global.timeout", "60")
script.pip("config", "--site", "set", "freeze.timeout", "10")
result = script.pip("config", "debug")
assert (
f"{site_config_file}, exists: True\n freeze.timeout: 10" in result.stdout
)
assert (
f"{new_config_file}, exists: True\n global.timeout: 60" in result.stdout
)
assert re.search(
rf"{legacy_config_file}, exists: True\n( {new_config_file}.+\n)+",
result.stdout,
)