Skip to content

Commit

Permalink
Merge pull request #11073 from wimglenn/issue-9330
Browse files Browse the repository at this point in the history
``pip config`` normalizes names, converting underscores into dashes
  • Loading branch information
pradyunsg committed May 4, 2022
2 parents 8d51b83 + ae1c2e3 commit bab5bfc
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 8 deletions.
2 changes: 1 addition & 1 deletion docs/html/development/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ The contents of this file are reStructuredText formatted text that
will be used as the content of the news file entry. You do not need to
reference the issue or PR numbers in the entry, since ``towncrier``
will automatically add a reference to all of the affected issues when
rendering the NEWS file.
rendering the NEWS file. There must be a newline at the end of the file.

In order to maintain a consistent style in the ``NEWS.rst`` file, it is
preferred to keep the news entry to the point, in sentence case, shorter than
Expand Down
1 change: 1 addition & 0 deletions news/9330.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``pip config`` now normalizes names by converting underscores into dashes.
12 changes: 10 additions & 2 deletions src/pip/_internal/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,19 @@ def items(self) -> Iterable[Tuple[str, Any]]:

def get_value(self, key: str) -> Any:
"""Get a value from the configuration."""
orig_key = key
key = _normalize_name(key)
try:
return self._dictionary[key]
except KeyError:
raise ConfigurationError(f"No such key - {key}")
# 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
_disassemble_key(key)
raise ConfigurationError(f"No such key - {orig_key}")

def set_value(self, key: str, value: Any) -> None:
"""Modify a value in the configuration."""
key = _normalize_name(key)
self._ensure_have_load_only()

assert self.load_only
Expand All @@ -167,11 +173,13 @@ def set_value(self, key: str, value: Any) -> None:

def unset_value(self, key: str) -> None:
"""Unset a value in the configuration."""
orig_key = key
key = _normalize_name(key)
self._ensure_have_load_only()

assert self.load_only
if key not in self._config[self.load_only]:
raise ConfigurationError(f"No such key - {key}")
raise ConfigurationError(f"No such key - {orig_key}")

fname, parser = self._get_parser_to_modify()

Expand Down
39 changes: 34 additions & 5 deletions tests/unit/test_configuration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Tests for all things related to the configuration
"""

import re
from unittest.mock import MagicMock

import pytest
Expand Down Expand Up @@ -87,6 +88,25 @@ def test_environment_config_errors_if_malformed(
err.value
)

def test_no_such_key_error_message_no_command(self) -> None:
self.configuration.load_only = kinds.GLOBAL
self.configuration.load()
expected_msg = (
"Key does not contain dot separated section and key. "
"Perhaps you wanted to use 'global.index-url' instead?"
)
pat = f"^{re.escape(expected_msg)}$"
with pytest.raises(ConfigurationError, match=pat):
self.configuration.get_value("index-url")

def test_no_such_key_error_message_missing_option(self) -> None:
self.configuration.load_only = kinds.GLOBAL
self.configuration.load()
expected_msg = "No such key - global.index-url"
pat = f"^{re.escape(expected_msg)}$"
with pytest.raises(ConfigurationError, match=pat):
self.configuration.get_value("global.index-url")


class TestConfigurationPrecedence(ConfigurationMixin):
# Tests for methods to that determine the order of precedence of
Expand Down Expand Up @@ -185,12 +205,8 @@ class TestConfigurationModification(ConfigurationMixin):
def test_no_specific_given_modification(self) -> None:
self.configuration.load()

try:
with pytest.raises(ConfigurationError):
self.configuration.set_value("test.hello", "10")
except ConfigurationError:
pass
else:
assert False, "Should have raised an error."

def test_site_modification(self) -> None:
self.configuration.load_only = kinds.SITE
Expand Down Expand Up @@ -241,3 +257,16 @@ def test_global_modification(self) -> None:
# get the path to user config file
assert mymock.call_count == 1
assert mymock.call_args[0][0] == (get_configuration_files()[kinds.GLOBAL][-1])

def test_normalization(self) -> None:
# underscores and dashes can be used interchangeably.
# internally, underscores get converted into dashes before reading/writing file
self.configuration.load_only = kinds.GLOBAL
self.configuration.load()
self.configuration.set_value("global.index_url", "example.org")
assert self.configuration.get_value("global.index_url") == "example.org"
assert self.configuration.get_value("global.index-url") == "example.org"
self.configuration.unset_value("global.index-url")
pat = r"^No such key - global\.index-url$"
with pytest.raises(ConfigurationError, match=pat):
self.configuration.get_value("global.index-url")

0 comments on commit bab5bfc

Please sign in to comment.