From fb260f9d1a433a3fcaab8d2b6f97ac2f02eca9f3 Mon Sep 17 00:00:00 2001 From: Martin Stolle Date: Wed, 25 Sep 2024 10:26:04 +0200 Subject: [PATCH 1/3] Support username and password from keyring --- src/hatch/publish/auth.py | 11 +++++++++-- tests/utils/test_auth.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/hatch/publish/auth.py b/src/hatch/publish/auth.py index 79790a543..4d3afbece 100644 --- a/src/hatch/publish/auth.py +++ b/src/hatch/publish/auth.py @@ -1,5 +1,7 @@ from __future__ import annotations +import keyring + from hatch.utils.fs import Path @@ -44,8 +46,6 @@ def __get_password(self) -> str: if password is not None: return password - import keyring - password = keyring.get_password(self._repo, self.username) if password is not None: return password @@ -62,6 +62,7 @@ def __get_username(self) -> str: or self._repo_config.get('user') or self._read_pypirc() or self._read_previous_working_user_data() + or self._read_keyring() ) if username is not None: return username @@ -72,6 +73,12 @@ def __get_username(self) -> str: self.__username_was_read = True return self._app.prompt(f"Username for '{self._repo_config['url']}' [__token__]") or '__token__' + def _read_keyring(self) -> str | None: + creds = keyring.get_credential(self._repo, None) + if not creds: + return None + return creds.username + def _read_previous_working_user_data(self) -> str | None: if self._pwu_path.is_file(): contents = self._pwu_path.read_text() diff --git a/tests/utils/test_auth.py b/tests/utils/test_auth.py index 67e666033..9a9a254f2 100644 --- a/tests/utils/test_auth.py +++ b/tests/utils/test_auth.py @@ -1,3 +1,4 @@ +import hatch.publish.auth from hatch.publish.auth import AuthenticationCredentials from hatch.utils.fs import Path @@ -42,3 +43,30 @@ def test_pypirc(fs): ) assert credentials.username == 'guido' assert credentials.password == 'gat' + + +def test_keyring_credentials(monkeypatch): + class MockKeyring: + @staticmethod + def get_credential(*_): + class Credential: + username = 'gat' + + return Credential() + + @staticmethod + def get_password(*_): + return 'guido' + + monkeypatch.setattr(hatch.publish.auth, 'keyring', MockKeyring) + + credentials = AuthenticationCredentials( + app=None, + cache_dir=Path('/none'), + options={}, + repo='arbitrary', + repo_config={'url': 'https://kaashandel.nl/'}, + ) + + assert credentials.username == 'gat' + assert credentials.password == 'guido' From 69e4596f226d7f5c8aa03a59f3524401a62049f0 Mon Sep 17 00:00:00 2001 From: Martin Stolle Date: Mon, 18 Nov 2024 08:36:45 +0100 Subject: [PATCH 2/3] Set password directly if keyring credentials are used --- src/hatch/publish/auth.py | 6 ++---- tests/utils/test_auth.py | 32 +++++++++++++++++--------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/hatch/publish/auth.py b/src/hatch/publish/auth.py index 4d3afbece..5600ff5cc 100644 --- a/src/hatch/publish/auth.py +++ b/src/hatch/publish/auth.py @@ -46,10 +46,6 @@ def __get_password(self) -> str: if password is not None: return password - password = keyring.get_password(self._repo, self.username) - if password is not None: - return password - if self._options['no_prompt']: self._app.abort('Missing required option: auth') @@ -77,6 +73,8 @@ def _read_keyring(self) -> str | None: creds = keyring.get_credential(self._repo, None) if not creds: return None + self.__password = creds.password + self.__password_was_read = True return creds.username def _read_previous_working_user_data(self) -> str | None: diff --git a/tests/utils/test_auth.py b/tests/utils/test_auth.py index 9a9a254f2..12a379b33 100644 --- a/tests/utils/test_auth.py +++ b/tests/utils/test_auth.py @@ -1,8 +1,24 @@ +import pytest + import hatch.publish.auth from hatch.publish.auth import AuthenticationCredentials from hatch.utils.fs import Path +@pytest.fixture(autouse=True) +def mock_keyring(monkeypatch): + class MockKeyring: + @staticmethod + def get_credential(*_): + class Credential: + username = 'gat' + password = 'guido' + + return Credential() + + monkeypatch.setattr(hatch.publish.auth, 'keyring', MockKeyring) + + def test_pypirc(fs): fs.create_file( Path.home() / '.pypirc', @@ -45,21 +61,7 @@ def test_pypirc(fs): assert credentials.password == 'gat' -def test_keyring_credentials(monkeypatch): - class MockKeyring: - @staticmethod - def get_credential(*_): - class Credential: - username = 'gat' - - return Credential() - - @staticmethod - def get_password(*_): - return 'guido' - - monkeypatch.setattr(hatch.publish.auth, 'keyring', MockKeyring) - +def test_keyring_credentials(): credentials = AuthenticationCredentials( app=None, cache_dir=Path('/none'), From 56489c1dc109e5f0b134f9cc7ce0e0fa8e9a4bd8 Mon Sep 17 00:00:00 2001 From: Martin Stolle Date: Wed, 20 Nov 2024 10:32:29 +0100 Subject: [PATCH 3/3] Only import keyring if its needed --- src/hatch/publish/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hatch/publish/auth.py b/src/hatch/publish/auth.py index 5600ff5cc..300aafbc9 100644 --- a/src/hatch/publish/auth.py +++ b/src/hatch/publish/auth.py @@ -1,7 +1,5 @@ from __future__ import annotations -import keyring - from hatch.utils.fs import Path @@ -70,6 +68,8 @@ def __get_username(self) -> str: return self._app.prompt(f"Username for '{self._repo_config['url']}' [__token__]") or '__token__' def _read_keyring(self) -> str | None: + import keyring + creds = keyring.get_credential(self._repo, None) if not creds: return None