4
4
providing credentials in the context of network requests.
5
5
"""
6
6
7
- import functools
8
7
import os
9
8
import shutil
10
9
import subprocess
11
10
import urllib .parse
12
11
from abc import ABC , abstractmethod
12
+ from types import ModuleType
13
13
from typing import Any , Dict , List , NamedTuple , Optional , Tuple
14
14
15
15
from pip ._vendor .requests .auth import AuthBase , HTTPBasicAuth
@@ -40,10 +40,6 @@ class Credentials(NamedTuple):
40
40
class KeyRingBaseProvider (ABC ):
41
41
"""Keyring base provider interface"""
42
42
43
- @abstractmethod
44
- def is_available (self ) -> bool :
45
- ...
46
-
47
43
@abstractmethod
48
44
def get_auth_info (self , url : str , username : Optional [str ]) -> Optional [AuthInfo ]:
49
45
...
@@ -53,24 +49,23 @@ def save_auth_info(self, url: str, username: str, password: str) -> None:
53
49
...
54
50
55
51
56
- class KeyRingPythonProvider (KeyRingBaseProvider ):
57
- """Keyring interface which uses locally imported `keyring` """
52
+ class KeyRingNullProvider (KeyRingBaseProvider ):
53
+ """Keyring null provider """
58
54
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
64
57
65
- self .keyring = keyring
58
+ def save_auth_info (self , url : str , username : str , password : str ) -> None :
59
+ return None
66
60
67
- def is_available (self ) -> bool :
68
- return self .keyring is not None
69
61
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`"""
73
64
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 ]:
74
69
# Support keyring's get_credential interface which supports getting
75
70
# credentials without a username. This is only available for
76
71
# keyring>=15.2.0.
@@ -101,16 +96,10 @@ class KeyRingCliProvider(KeyRingBaseProvider):
101
96
PATH.
102
97
"""
103
98
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
109
101
110
102
def get_auth_info (self , url : str , username : Optional [str ]) -> Optional [AuthInfo ]:
111
- if self .is_available is False :
112
- return None
113
-
114
103
# This is the default implementation of keyring.get_credential
115
104
# https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
116
105
if username is not None :
@@ -120,8 +109,6 @@ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]
120
109
return None
121
110
122
111
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" )
125
112
return self ._set_password (url , username , password )
126
113
127
114
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
156
143
return None
157
144
158
145
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
168
151
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 ()
170
159
171
160
172
161
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
176
165
return None
177
166
178
167
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
-
184
168
try :
185
169
return keyring .get_auth_info (url , username )
186
170
except Exception as exc :
187
171
logger .warning (
188
172
"Keyring is skipped due to an exception: %s" ,
189
173
str (exc ),
190
174
)
175
+ global KEYRING_DISABLED
191
176
KEYRING_DISABLED = True
192
177
return None
193
178
@@ -436,9 +421,9 @@ def warn_on_401(self, resp: Response, **kwargs: Any) -> None:
436
421
def save_credentials (self , resp : Response , ** kwargs : Any ) -> None :
437
422
"""Response callback to save credentials on success."""
438
423
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"
442
427
443
428
creds = self ._credentials_to_save
444
429
self ._credentials_to_save = None
0 commit comments