From 98be0248671523fd2030db7569cd5b70702b5cb2 Mon Sep 17 00:00:00 2001 From: Louis Shawn Date: Fri, 6 Feb 2026 11:08:51 +0800 Subject: [PATCH 1/4] fix(config): support dotted repository names --- src/poetry/config/config.py | 5 +++-- src/poetry/config/config_source.py | 28 +++++++++++++++++++++++++ src/poetry/config/dict_config_source.py | 7 ++++--- src/poetry/config/file_config_source.py | 7 ++++--- src/poetry/console/commands/config.py | 19 ++++++++++------- src/poetry/publishing/publisher.py | 4 +++- src/poetry/utils/authenticator.py | 11 ++++++---- src/poetry/utils/password_manager.py | 20 +++++++++++------- tests/config/test_dict_config_source.py | 17 +++++++++++++++ tests/config/test_file_config_source.py | 16 ++++++++++++++ tests/console/commands/test_config.py | 7 +++++++ tests/utils/test_password_manager.py | 20 ++++++++++++++++++ 12 files changed, 134 insertions(+), 27 deletions(-) diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index 03b5ee5bc63..1cd2ef6b673 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -29,7 +29,8 @@ from collections.abc import Mapping from collections.abc import Sequence - from poetry.config.config_source import ConfigSource +from poetry.config.config_source import ConfigSource +from poetry.config.config_source import split_config_key def boolean_validator(val: str) -> bool: @@ -313,7 +314,7 @@ def get(self, setting_name: str, default: Any = None) -> Any: """ Retrieve a setting value. """ - keys = setting_name.split(".") + keys = split_config_key(setting_name) build_config_settings: Mapping[ NormalizedName, Mapping[str, str | Sequence[str]] ] = {} diff --git a/src/poetry/config/config_source.py b/src/poetry/config/config_source.py index e06c934e70a..2722d28116c 100644 --- a/src/poetry/config/config_source.py +++ b/src/poetry/config/config_source.py @@ -33,6 +33,34 @@ def add_property(self, key: str, value: Any) -> None: ... def remove_property(self, key: str) -> None: ... +def split_config_key(key: str) -> list[str]: + parts: list[str] = [] + current: list[str] = [] + escaped = False + + for char in key: + if escaped: + current.append(char) + escaped = False + elif char == "\\": + escaped = True + elif char == ".": + parts.append("".join(current)) + current = [] + else: + current.append(char) + + if escaped: + current.append("\\") + + parts.append("".join(current)) + return parts + + +def escape_config_key(key: str) -> str: + return key.replace("\\", "\\\\").replace(".", "\\.") + + @dataclasses.dataclass class ConfigSourceMigration: old_key: str diff --git a/src/poetry/config/dict_config_source.py b/src/poetry/config/dict_config_source.py index 4b5a87a1699..e3e9a193796 100644 --- a/src/poetry/config/dict_config_source.py +++ b/src/poetry/config/dict_config_source.py @@ -4,6 +4,7 @@ from poetry.config.config_source import ConfigSource from poetry.config.config_source import PropertyNotFoundError +from poetry.config.config_source import split_config_key class DictConfigSource(ConfigSource): @@ -15,7 +16,7 @@ def config(self) -> dict[str, Any]: return self._config def get_property(self, key: str) -> Any: - keys = key.split(".") + keys = split_config_key(key) config = self._config for i, key in enumerate(keys): @@ -28,7 +29,7 @@ def get_property(self, key: str) -> Any: config = config[key] def add_property(self, key: str, value: Any) -> None: - keys = key.split(".") + keys = split_config_key(key) config = self._config for i, key in enumerate(keys): @@ -42,7 +43,7 @@ def add_property(self, key: str, value: Any) -> None: config = config[key] def remove_property(self, key: str) -> None: - keys = key.split(".") + keys = split_config_key(key) config = self._config for i, key in enumerate(keys): diff --git a/src/poetry/config/file_config_source.py b/src/poetry/config/file_config_source.py index 9170e0ef254..a2fbc71b03d 100644 --- a/src/poetry/config/file_config_source.py +++ b/src/poetry/config/file_config_source.py @@ -10,6 +10,7 @@ from poetry.config.config_source import ConfigSource from poetry.config.config_source import PropertyNotFoundError from poetry.config.config_source import drop_empty_config_category +from poetry.config.config_source import split_config_key if TYPE_CHECKING: @@ -33,7 +34,7 @@ def file(self) -> TOMLFile: return self._file def get_property(self, key: str) -> Any: - keys = key.split(".") + keys = split_config_key(key) config = self.file.read() if self.file.exists() else {} @@ -49,7 +50,7 @@ def get_property(self, key: str) -> Any: def add_property(self, key: str, value: Any) -> None: with self.secure() as toml: config: dict[str, Any] = toml - keys = key.split(".") + keys = split_config_key(key) for i, key in enumerate(keys): if key not in config and i < len(keys) - 1: @@ -64,7 +65,7 @@ def add_property(self, key: str, value: Any) -> None: def remove_property(self, key: str) -> None: with self.secure() as toml: config: dict[str, Any] = toml - keys = key.split(".") + keys = split_config_key(key) current_config = config for i, key in enumerate(keys): diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index 60eaa33b046..6012637f0a5 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -113,7 +113,8 @@ def handle(self) -> int: from poetry.core.pyproject.exceptions import PyProjectError - from poetry.config.config import Config +from poetry.config.config import Config +from poetry.config.config_source import escape_config_key from poetry.config.file_config_source import FileConfigSource from poetry.locations import CONFIG_DIR from poetry.toml.file import TOMLFile @@ -182,7 +183,8 @@ def handle(self) -> int: if config.get("repositories") is not None: value = config.get("repositories") else: - repo = config.get(f"repositories.{m.group(1)}") + repository = escape_config_key(m.group(1)) + repo = config.get(f"repositories.{repository}") if repo is None: raise ValueError(f"There is no {m.group(1)} repository defined") @@ -221,20 +223,23 @@ def handle(self) -> int: if m: if not m.group(1): raise ValueError("You cannot remove the [repositories] section") + repository = escape_config_key(m.group(1)) if self.option("unset"): - repo = config.get(f"repositories.{m.group(1)}") + repo = config.get(f"repositories.{repository}") if repo is None: raise ValueError(f"There is no {m.group(1)} repository defined") - config.config_source.remove_property(f"repositories.{m.group(1)}") + config.config_source.remove_property(f"repositories.{repository}") return 0 if len(values) == 1: url = values[0] - config.config_source.add_property(f"repositories.{m.group(1)}.url", url) + config.config_source.add_property( + f"repositories.{repository}.url", url + ) return 0 @@ -286,9 +291,9 @@ def handle(self) -> int: return 0 # handle certs - m = re.match(r"certificates\.([^.]+)\.(cert|client-cert)", self.argument("key")) + m = re.match(r"certificates\.(.+)\.(cert|client-cert)", self.argument("key")) if m: - repository = m.group(1) + repository = escape_config_key(m.group(1)) key = m.group(2) if self.option("unset"): diff --git a/src/poetry/publishing/publisher.py b/src/poetry/publishing/publisher.py index f752a0d5ee4..f5de4d3dec6 100644 --- a/src/poetry/publishing/publisher.py +++ b/src/poetry/publishing/publisher.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from poetry.publishing.uploader import Uploader +from poetry.config.config_source import escape_config_key from poetry.utils.authenticator import Authenticator @@ -49,7 +50,8 @@ def publish( repository_name = "pypi" else: # Retrieving config information - url = self._poetry.config.get(f"repositories.{repository_name}.url") + repository = escape_config_key(repository_name) + url = self._poetry.config.get(f"repositories.{repository}.url") if url is None: raise RuntimeError(f"Repository {repository_name} is not defined") diff --git a/src/poetry/utils/authenticator.py b/src/poetry/utils/authenticator.py index 05b83a5400b..3832f3b80d9 100644 --- a/src/poetry/utils/authenticator.py +++ b/src/poetry/utils/authenticator.py @@ -22,6 +22,7 @@ from poetry.__version__ import __version__ from poetry.config.config import Config +from poetry.config.config_source import escape_config_key from poetry.console.exceptions import ConsoleMessage from poetry.console.exceptions import PoetryRuntimeError from poetry.exceptions import PoetryError @@ -50,12 +51,13 @@ def create( cls, repository: str, config: Config | None ) -> RepositoryCertificateConfig: config = config if config else Config.create() + repository_key = escape_config_key(repository) verify: str | bool = config.get( - f"certificates.{repository}.verify", - config.get(f"certificates.{repository}.cert", True), + f"certificates.{repository_key}.verify", + config.get(f"certificates.{repository_key}.cert", True), ) - client_cert: str = config.get(f"certificates.{repository}.client-cert") + client_cert: str = config.get(f"certificates.{repository_key}.client-cert") return cls( cert=Path(verify) if isinstance(verify, str) else None, @@ -389,7 +391,8 @@ def configured_repositories(self) -> dict[str, AuthenticatorRepositoryConfig]: if self._configured_repositories is None: self._configured_repositories = {} for repository_name in self._config.get("repositories", []): - url = self._config.get(f"repositories.{repository_name}.url") + repository = escape_config_key(repository_name) + url = self._config.get(f"repositories.{repository}.url") self._configured_repositories[repository_name] = ( AuthenticatorRepositoryConfig(repository_name, url) ) diff --git a/src/poetry/utils/password_manager.py b/src/poetry/utils/password_manager.py index e6455529bc3..1e1361741e1 100644 --- a/src/poetry/utils/password_manager.py +++ b/src/poetry/utils/password_manager.py @@ -8,6 +8,7 @@ from typing import TYPE_CHECKING from poetry.config.config import Config +from poetry.config.config_source import escape_config_key from poetry.utils.threading import atomic_cached_property @@ -210,10 +211,11 @@ def warn_plaintext_credentials_stored() -> None: logger.warning("Using a plaintext file to store credentials") def set_pypi_token(self, repo_name: str, token: str) -> None: + repository = escape_config_key(repo_name) if not self.use_keyring: self.warn_plaintext_credentials_stored() self._config.auth_config_source.add_property( - f"pypi-token.{repo_name}", token + f"pypi-token.{repository}", token ) else: self.keyring.set_password(repo_name, "__token__", token) @@ -228,7 +230,8 @@ def get_pypi_token(self, repo_name: str) -> str | None: :param repo_name: Name of repository. :return: Returns a token as a string if found, otherwise None. """ - token: str | None = self._config.get(f"pypi-token.{repo_name}") + repository = escape_config_key(repo_name) + token: str | None = self._config.get(f"pypi-token.{repository}") if token: return token @@ -240,14 +243,15 @@ def get_pypi_token(self, repo_name: str) -> str | None: def delete_pypi_token(self, repo_name: str) -> None: if not self.use_keyring: return self._config.auth_config_source.remove_property( - f"pypi-token.{repo_name}" + f"pypi-token.{escape_config_key(repo_name)}" ) self.keyring.delete_password(repo_name, "__token__") def get_http_auth(self, repo_name: str) -> HTTPAuthCredential: - username = self._config.get(f"http-basic.{repo_name}.username") - password = self._config.get(f"http-basic.{repo_name}.password") + repository = escape_config_key(repo_name) + username = self._config.get(f"http-basic.{repository}.username") + password = self._config.get(f"http-basic.{repository}.password") if password is None and self.use_keyring: password = self.keyring.get_password(repo_name, username) @@ -264,7 +268,8 @@ def set_http_password(self, repo_name: str, username: str, password: str) -> Non else: self.keyring.set_password(repo_name, username, password) - self._config.auth_config_source.add_property(f"http-basic.{repo_name}", auth) + repository = escape_config_key(repo_name) + self._config.auth_config_source.add_property(f"http-basic.{repository}", auth) def delete_http_password(self, repo_name: str) -> None: auth = self.get_http_auth(repo_name) @@ -275,7 +280,8 @@ def delete_http_password(self, repo_name: str) -> None: with suppress(PoetryKeyringError): self.keyring.delete_password(repo_name, auth.username) - self._config.auth_config_source.remove_property(f"http-basic.{repo_name}") + repository = escape_config_key(repo_name) + self._config.auth_config_source.remove_property(f"http-basic.{repository}") def get_credential( self, *names: str, username: str | None = None diff --git a/tests/config/test_dict_config_source.py b/tests/config/test_dict_config_source.py index 5c28589ce6a..4abd94e2940 100644 --- a/tests/config/test_dict_config_source.py +++ b/tests/config/test_dict_config_source.py @@ -66,3 +66,20 @@ def test_dict_config_source_get_property_should_raise_if_not_found() -> None: PropertyNotFoundError, match=r"Key virtualenvs\.use-poetry-python not in config" ): _ = config_source.get_property("virtualenvs.use-poetry-python") + + +def test_dict_config_source_escaped_dot_key() -> None: + config_source = DictConfigSource() + + config_source.add_property("repositories.foo\\.bar.url", "https://example.com/simple") + assert config_source._config == { + "repositories": {"foo.bar": {"url": "https://example.com/simple"}} + } + + assert ( + config_source.get_property("repositories.foo\\.bar.url") + == "https://example.com/simple" + ) + + config_source.remove_property("repositories.foo\\.bar.url") + assert config_source._config == {"repositories": {"foo.bar": {}}} diff --git a/tests/config/test_file_config_source.py b/tests/config/test_file_config_source.py index 0e517d2e269..3092d017824 100644 --- a/tests/config/test_file_config_source.py +++ b/tests/config/test_file_config_source.py @@ -89,3 +89,19 @@ def test_file_config_source_get_property_should_raise_if_not_found( PropertyNotFoundError, match=r"Key virtualenvs\.use-poetry-python not in config" ): _ = config_source.get_property("virtualenvs.use-poetry-python") + + +def test_file_config_source_escaped_dot_key(tmp_path: Path) -> None: + config = tmp_path.joinpath("config.toml") + config.touch() + + config_source = FileConfigSource(TOMLFile(config)) + config_source.add_property("repositories.foo\\.bar.url", "https://example.com/simple") + + assert config_source._file.read() == { + "repositories": {"foo.bar": {"url": "https://example.com/simple"}} + } + assert ( + config_source.get_property("repositories.foo\\.bar.url") + == "https://example.com/simple" + ) diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index a49360bb10c..ff080e838fb 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -237,6 +237,13 @@ def test_display_single_setting( assert tester.io.fetch_output() == expected +def test_repositories_setting_with_dot_in_name(tester: CommandTester) -> None: + tester.execute("repositories.foo.bar.url https://bar.com/simple/") + tester.execute("repositories.foo.bar") + + assert tester.io.fetch_output() == "{'url': 'https://bar.com/simple/'}\n" + + def test_display_single_local_setting( command_tester_factory: CommandTesterFactory, fixture_dir: FixtureDirGetter ) -> None: diff --git a/tests/utils/test_password_manager.py b/tests/utils/test_password_manager.py index 329e3582ff1..c47c16577a1 100644 --- a/tests/utils/test_password_manager.py +++ b/tests/utils/test_password_manager.py @@ -132,6 +132,17 @@ def test_set_http_password_with_unavailable_backend( assert auth["password"] == "baz" +def test_set_http_password_with_dot_in_repo_name( + config: Config, with_fail_keyring: None +) -> None: + manager = PasswordManager(config) + manager.set_http_password("foo.bar", "baz", "qux") + + auth = config.get("http-basic.foo\\.bar") + assert auth["username"] == "baz" + assert auth["password"] == "qux" + + @pytest.mark.parametrize( ("username", "password", "is_valid"), [ @@ -189,6 +200,15 @@ def test_set_pypi_token_with_unavailable_backend( assert config.get("pypi-token.foo") == "baz" +def test_set_pypi_token_with_dot_in_repo_name( + config: Config, with_fail_keyring: None +) -> None: + manager = PasswordManager(config) + manager.set_pypi_token("foo.bar", "baz") + + assert config.get("pypi-token.foo\\.bar") == "baz" + + def test_get_pypi_token_with_unavailable_backend( config: Config, with_fail_keyring: None ) -> None: From 0efe6c15222f12a68f54ec6f379e4665e6a60bbb Mon Sep 17 00:00:00 2001 From: Louis Shawn Date: Fri, 6 Feb 2026 11:41:36 +0800 Subject: [PATCH 2/4] test(password-manager): cover dotted repository read/delete flows --- tests/utils/test_password_manager.py | 90 ++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/utils/test_password_manager.py b/tests/utils/test_password_manager.py index c47c16577a1..bba71ef7f98 100644 --- a/tests/utils/test_password_manager.py +++ b/tests/utils/test_password_manager.py @@ -143,6 +143,55 @@ def test_set_http_password_with_dot_in_repo_name( assert auth["password"] == "qux" +def test_get_http_auth_with_dot_in_repo_name( + config: Config, with_fail_keyring: None +) -> None: + config.auth_config_source.add_property( + "http-basic.foo\\.bar", {"username": "baz", "password": "qux"} + ) + manager = PasswordManager(config) + + auth = manager.get_http_auth("foo.bar") + assert auth.username == "baz" + assert auth.password == "qux" + + +def test_delete_http_password_with_dot_in_repo_name( + config: Config, with_fail_keyring: None +) -> None: + config.auth_config_source.add_property( + "http-basic.foo\\.bar", {"username": "baz", "password": "qux"} + ) + manager = PasswordManager(config) + + manager.delete_http_password("foo.bar") + assert config.get("http-basic.foo\\.bar") is None + + +def test_get_http_auth_with_dot_in_repo_name_keyring( + config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend +) -> None: + manager = PasswordManager(config) + manager.set_http_password("foo.bar", "baz", "qux") + + auth = manager.get_http_auth("foo.bar") + assert auth.username == "baz" + assert auth.password == "qux" + assert dummy_keyring.get_password("poetry-repository-foo.bar", "baz") == "qux" + + +def test_delete_http_password_with_dot_in_repo_name_keyring( + config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend +) -> None: + manager = PasswordManager(config) + manager.set_http_password("foo.bar", "baz", "qux") + + manager.delete_http_password("foo.bar") + + assert dummy_keyring.get_password("poetry-repository-foo.bar", "baz") is None + assert config.get("http-basic.foo\\.bar") is None + + @pytest.mark.parametrize( ("username", "password", "is_valid"), [ @@ -209,6 +258,47 @@ def test_set_pypi_token_with_dot_in_repo_name( assert config.get("pypi-token.foo\\.bar") == "baz" +def test_get_pypi_token_with_dot_in_repo_name( + config: Config, with_fail_keyring: None +) -> None: + config.auth_config_source.add_property("pypi-token.foo\\.bar", "baz") + manager = PasswordManager(config) + + assert manager.get_pypi_token("foo.bar") == "baz" + + +def test_delete_pypi_token_with_dot_in_repo_name( + config: Config, with_fail_keyring: None +) -> None: + config.auth_config_source.add_property("pypi-token.foo\\.bar", "baz") + manager = PasswordManager(config) + + manager.delete_pypi_token("foo.bar") + assert config.get("pypi-token.foo\\.bar") is None + + +def test_get_pypi_token_with_dot_in_repo_name_keyring( + config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend +) -> None: + manager = PasswordManager(config) + manager.set_pypi_token("foo.bar", "baz") + + assert manager.get_pypi_token("foo.bar") == "baz" + assert ( + dummy_keyring.get_password("poetry-repository-foo.bar", "__token__") == "baz" + ) + + +def test_delete_pypi_token_with_dot_in_repo_name_keyring( + config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend +) -> None: + manager = PasswordManager(config) + manager.set_pypi_token("foo.bar", "baz") + + manager.delete_pypi_token("foo.bar") + assert dummy_keyring.get_password("poetry-repository-foo.bar", "__token__") is None + + def test_get_pypi_token_with_unavailable_backend( config: Config, with_fail_keyring: None ) -> None: From b86dc321a5ad7072722e195d360f612aaae377d6 Mon Sep 17 00:00:00 2001 From: Louis Shawn Date: Tue, 3 Mar 2026 16:56:10 +0800 Subject: [PATCH 3/4] style: fix formatting after rebasing dotted repository support --- src/poetry/console/commands/config.py | 8 +++----- src/poetry/publishing/publisher.py | 2 +- tests/config/test_dict_config_source.py | 4 +++- tests/config/test_file_config_source.py | 4 +++- tests/utils/test_password_manager.py | 4 +--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index 6012637f0a5..3bea271da86 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -113,8 +113,8 @@ def handle(self) -> int: from poetry.core.pyproject.exceptions import PyProjectError -from poetry.config.config import Config -from poetry.config.config_source import escape_config_key + from poetry.config.config import Config + from poetry.config.config_source import escape_config_key from poetry.config.file_config_source import FileConfigSource from poetry.locations import CONFIG_DIR from poetry.toml.file import TOMLFile @@ -237,9 +237,7 @@ def handle(self) -> int: if len(values) == 1: url = values[0] - config.config_source.add_property( - f"repositories.{repository}.url", url - ) + config.config_source.add_property(f"repositories.{repository}.url", url) return 0 diff --git a/src/poetry/publishing/publisher.py b/src/poetry/publishing/publisher.py index f5de4d3dec6..e6ed2a1fdd1 100644 --- a/src/poetry/publishing/publisher.py +++ b/src/poetry/publishing/publisher.py @@ -4,8 +4,8 @@ from typing import TYPE_CHECKING -from poetry.publishing.uploader import Uploader from poetry.config.config_source import escape_config_key +from poetry.publishing.uploader import Uploader from poetry.utils.authenticator import Authenticator diff --git a/tests/config/test_dict_config_source.py b/tests/config/test_dict_config_source.py index 4abd94e2940..48931038d55 100644 --- a/tests/config/test_dict_config_source.py +++ b/tests/config/test_dict_config_source.py @@ -71,7 +71,9 @@ def test_dict_config_source_get_property_should_raise_if_not_found() -> None: def test_dict_config_source_escaped_dot_key() -> None: config_source = DictConfigSource() - config_source.add_property("repositories.foo\\.bar.url", "https://example.com/simple") + config_source.add_property( + "repositories.foo\\.bar.url", "https://example.com/simple" + ) assert config_source._config == { "repositories": {"foo.bar": {"url": "https://example.com/simple"}} } diff --git a/tests/config/test_file_config_source.py b/tests/config/test_file_config_source.py index 3092d017824..22ef3bc99a6 100644 --- a/tests/config/test_file_config_source.py +++ b/tests/config/test_file_config_source.py @@ -96,7 +96,9 @@ def test_file_config_source_escaped_dot_key(tmp_path: Path) -> None: config.touch() config_source = FileConfigSource(TOMLFile(config)) - config_source.add_property("repositories.foo\\.bar.url", "https://example.com/simple") + config_source.add_property( + "repositories.foo\\.bar.url", "https://example.com/simple" + ) assert config_source._file.read() == { "repositories": {"foo.bar": {"url": "https://example.com/simple"}} diff --git a/tests/utils/test_password_manager.py b/tests/utils/test_password_manager.py index bba71ef7f98..d9d8042e13a 100644 --- a/tests/utils/test_password_manager.py +++ b/tests/utils/test_password_manager.py @@ -284,9 +284,7 @@ def test_get_pypi_token_with_dot_in_repo_name_keyring( manager.set_pypi_token("foo.bar", "baz") assert manager.get_pypi_token("foo.bar") == "baz" - assert ( - dummy_keyring.get_password("poetry-repository-foo.bar", "__token__") == "baz" - ) + assert dummy_keyring.get_password("poetry-repository-foo.bar", "__token__") == "baz" def test_delete_pypi_token_with_dot_in_repo_name_keyring( From caa00e38a031c1a50d54dff38936e9c80994d2ba Mon Sep 17 00:00:00 2001 From: Louis Shawn Date: Tue, 3 Mar 2026 17:24:05 +0800 Subject: [PATCH 4/4] fix(config): avoid stale repository state on unset Signed-off-by: Louis Shawn --- src/poetry/console/commands/config.py | 5 +++-- tests/console/commands/test_config.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index 3bea271da86..a8778af4898 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -226,8 +226,9 @@ def handle(self) -> int: repository = escape_config_key(m.group(1)) if self.option("unset"): - repo = config.get(f"repositories.{repository}") - if repo is None: + try: + config.config_source.get_property(f"repositories.{repository}") + except PropertyNotFoundError: raise ValueError(f"There is no {m.group(1)} repository defined") config.config_source.remove_property(f"repositories.{repository}") diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index ff080e838fb..31e394b965e 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -238,7 +238,7 @@ def test_display_single_setting( def test_repositories_setting_with_dot_in_name(tester: CommandTester) -> None: - tester.execute("repositories.foo.bar.url https://bar.com/simple/") + tester.execute("repositories.foo.bar https://bar.com/simple/") tester.execute("repositories.foo.bar") assert tester.io.fetch_output() == "{'url': 'https://bar.com/simple/'}\n"