Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pip config normalizes names, converting underscores into dashes #11073

Merged
merged 5 commits into from
May 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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")