Skip to content

Commit c04222f

Browse files
committed
Simplify provider interface
1 parent 8d9ea8b commit c04222f

File tree

2 files changed

+31
-47
lines changed

2 files changed

+31
-47
lines changed

src/pip/_internal/network/auth.py

Lines changed: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
providing credentials in the context of network requests.
55
"""
66

7-
import functools
87
import os
98
import shutil
109
import subprocess
1110
import urllib.parse
1211
from abc import ABC, abstractmethod
12+
from types import ModuleType
1313
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
1414

1515
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
@@ -40,10 +40,6 @@ class Credentials(NamedTuple):
4040
class KeyRingBaseProvider(ABC):
4141
"""Keyring base provider interface"""
4242

43-
@abstractmethod
44-
def is_available(self) -> bool:
45-
...
46-
4743
@abstractmethod
4844
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
4945
...
@@ -53,24 +49,23 @@ def save_auth_info(self, url: str, username: str, password: str) -> None:
5349
...
5450

5551

56-
class KeyRingPythonProvider(KeyRingBaseProvider):
57-
"""Keyring interface which uses locally imported `keyring`"""
52+
class KeyRingNullProvider(KeyRingBaseProvider):
53+
"""Keyring null provider"""
5854

59-
def __init__(self) -> None:
60-
try:
61-
import keyring
62-
except ImportError:
63-
keyring = None # type: ignore[assignment]
55+
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
56+
return None
6457

65-
self.keyring = keyring
58+
def save_auth_info(self, url: str, username: str, password: str) -> None:
59+
return None
6660

67-
def is_available(self) -> bool:
68-
return self.keyring is not None
6961

70-
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
71-
if self.is_available is False:
72-
return None
62+
class KeyRingPythonProvider(KeyRingBaseProvider):
63+
"""Keyring interface which uses locally imported `keyring`"""
7364

65+
def __init__(self, module: ModuleType) -> None:
66+
self.keyring = module
67+
68+
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
7469
# Support keyring's get_credential interface which supports getting
7570
# credentials without a username. This is only available for
7671
# keyring>=15.2.0.
@@ -101,16 +96,10 @@ class KeyRingCliProvider(KeyRingBaseProvider):
10196
PATH.
10297
"""
10398

104-
def __init__(self) -> None:
105-
self.keyring = shutil.which("keyring")
106-
107-
def is_available(self) -> bool:
108-
return self.keyring is not None
99+
def __init__(self, cmd: str) -> None:
100+
self.keyring = cmd
109101

110102
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
111-
if self.is_available is False:
112-
return None
113-
114103
# This is the default implementation of keyring.get_credential
115104
# https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
116105
if username is not None:
@@ -120,8 +109,6 @@ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]
120109
return None
121110

122111
def save_auth_info(self, url: str, username: str, password: str) -> None:
123-
if not self.is_available:
124-
raise RuntimeError("keyring is not available")
125112
return self._set_password(url, username, password)
126113

127114
def _get_password(self, service_name: str, username: str) -> Optional[str]:
@@ -156,17 +143,19 @@ def _set_password(self, service_name: str, username: str, password: str) -> None
156143
return None
157144

158145

159-
@functools.lru_cache(maxsize=1)
160-
def get_keyring_provider() -> Optional[KeyRingBaseProvider]:
161-
python_keyring = KeyRingPythonProvider()
162-
if python_keyring.is_available():
163-
return python_keyring
164-
165-
cli_keyring = KeyRingCliProvider()
166-
if cli_keyring.is_available():
167-
return cli_keyring
146+
def get_keyring_provider() -> KeyRingBaseProvider:
147+
# keyring has previously failed and been disabled
148+
if not KEYRING_DISABLED:
149+
try:
150+
import keyring
168151

169-
return None
152+
return KeyRingPythonProvider(keyring)
153+
except ImportError:
154+
pass
155+
cli = shutil.which("keyring")
156+
if cli:
157+
return KeyRingCliProvider(cli)
158+
return KeyRingNullProvider()
170159

171160

172161
def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]:
@@ -176,18 +165,14 @@ def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[Au
176165
return None
177166

178167
keyring = get_keyring_provider()
179-
# Do nothing if keyring is not available
180-
global KEYRING_DISABLED
181-
if keyring is None or KEYRING_DISABLED:
182-
return None
183-
184168
try:
185169
return keyring.get_auth_info(url, username)
186170
except Exception as exc:
187171
logger.warning(
188172
"Keyring is skipped due to an exception: %s",
189173
str(exc),
190174
)
175+
global KEYRING_DISABLED
191176
KEYRING_DISABLED = True
192177
return None
193178

@@ -436,9 +421,9 @@ def warn_on_401(self, resp: Response, **kwargs: Any) -> None:
436421
def save_credentials(self, resp: Response, **kwargs: Any) -> None:
437422
"""Response callback to save credentials on success."""
438423
keyring = get_keyring_provider()
439-
assert keyring is not None, "should never reach here without keyring"
440-
if not keyring:
441-
return None
424+
assert not isinstance(
425+
keyring, KeyRingNullProvider
426+
), "should never reach here without keyring"
442427

443428
creds = self._credentials_to_save
444429
self._credentials_to_save = None

tests/unit/test_network_auth.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ def reset_keyring() -> Iterable[None]:
1414
yield None
1515
# Reset the state of the module between tests
1616
pip._internal.network.auth.KEYRING_DISABLED = False
17-
pip._internal.network.auth.get_keyring_provider.cache_clear()
1817

1918

2019
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)