27
27
from pip ._internal .vcs .versioncontrol import AuthInfo
28
28
29
29
Credentials = Tuple [str , str , str ]
30
+ Keyring = Any # TODO: Use a more-specific type for the keyring module
31
+
30
32
31
33
logger = logging .getLogger (__name__ )
32
34
33
- try :
34
- import keyring
35
- except ImportError :
36
- keyring = None
37
- except Exception as exc :
38
- logger .warning (
39
- "Keyring is skipped due to an exception: %s" , str (exc ),
40
- )
41
- keyring = None
42
-
43
-
44
- def get_keyring_auth (url , username ):
45
- # type: (str, str) -> Optional[AuthInfo]
46
- """Return the tuple auth for a given url from keyring."""
47
- global keyring
48
- if not url or not keyring :
49
- return None
50
35
51
- try :
36
+ class MultiDomainBasicAuth (AuthBase ):
37
+ @classmethod
38
+ def get_keyring (cls ):
39
+ # type: () -> Optional[Keyring]
40
+ # Cache so the import is attempted at most once
41
+ if hasattr (cls , '_keyring' ):
42
+ return cls ._keyring
43
+
52
44
try :
53
- get_credential = keyring .get_credential
54
- except AttributeError :
55
- pass
56
- else :
57
- logger .debug ("Getting credentials from keyring for %s" , url )
58
- cred = get_credential (url , username )
59
- if cred is not None :
60
- return cred .username , cred .password
61
- return None
45
+ import keyring
46
+ except ImportError :
47
+ keyring = None
48
+ except Exception as exc :
49
+ logger .warning (
50
+ "Keyring is skipped due to an exception: %s" , str (exc ),
51
+ )
52
+ keyring = None
62
53
63
- if username :
64
- logger .debug ("Getting password from keyring for %s" , url )
65
- password = keyring .get_password (url , username )
66
- if password :
67
- return username , password
54
+ cls ._keyring = keyring
55
+ return keyring
68
56
69
- except Exception as exc :
70
- logger .warning (
71
- "Keyring is skipped due to an exception: %s" , str (exc ),
72
- )
73
- keyring = None
74
- return None
57
+ @classmethod
58
+ def get_keyring_auth (cls , url , username ):
59
+ # type: (str, str) -> Optional[AuthInfo]
60
+ """Return the tuple auth for a given url from keyring."""
61
+ keyring = cls .get_keyring ()
62
+ if not url or not keyring :
63
+ return None
75
64
65
+ try :
66
+ try :
67
+ get_credential = keyring .get_credential
68
+ except AttributeError :
69
+ pass
70
+ else :
71
+ logger .debug ("Getting credentials from keyring for %s" , url )
72
+ cred = get_credential (url , username )
73
+ if cred is not None :
74
+ return cred .username , cred .password
75
+ return None
76
+
77
+ if username :
78
+ logger .debug ("Getting password from keyring for %s" , url )
79
+ password = keyring .get_password (url , username )
80
+ if password :
81
+ return username , password
82
+
83
+ except Exception as exc :
84
+ logger .warning (
85
+ "Keyring is skipped due to an exception: %s" , str (exc ),
86
+ )
87
+ cls ._keyring = None
88
+ return None
76
89
77
- class MultiDomainBasicAuth (AuthBase ):
78
90
79
91
def __init__ (self , prompting = True , index_urls = None ):
80
92
# type: (bool, Optional[List[str]]) -> None
@@ -153,8 +165,8 @@ def _get_new_credentials(self, original_url, allow_netrc=True,
153
165
if allow_keyring :
154
166
# The index url is more specific than the netloc, so try it first
155
167
kr_auth = (
156
- get_keyring_auth (index_url , username ) or
157
- get_keyring_auth (netloc , username )
168
+ self . get_keyring_auth (index_url , username ) or
169
+ self . get_keyring_auth (netloc , username )
158
170
)
159
171
if kr_auth :
160
172
logger .debug ("Found credentials in keyring for %s" , netloc )
@@ -226,7 +238,7 @@ def _prompt_for_password(self, netloc):
226
238
username = ask_input (f"User for { netloc } : " )
227
239
if not username :
228
240
return None , None , False
229
- auth = get_keyring_auth (netloc , username )
241
+ auth = self . get_keyring_auth (netloc , username )
230
242
if auth and auth [0 ] is not None and auth [1 ] is not None :
231
243
return auth [0 ], auth [1 ], False
232
244
password = ask_password ("Password: " )
@@ -235,7 +247,7 @@ def _prompt_for_password(self, netloc):
235
247
# Factored out to allow for easy patching in tests
236
248
def _should_save_password_to_keyring (self ):
237
249
# type: () -> bool
238
- if not keyring :
250
+ if not self . get_keyring () :
239
251
return False
240
252
return ask ("Save credentials to keyring [y/N]: " , ["y" , "n" ]) == "y"
241
253
@@ -296,6 +308,7 @@ def warn_on_401(self, resp, **kwargs):
296
308
def save_credentials (self , resp , ** kwargs ):
297
309
# type: (Response, **Any) -> None
298
310
"""Response callback to save credentials on success."""
311
+ keyring = self .get_keyring ()
299
312
assert keyring is not None , "should never reach here without keyring"
300
313
if not keyring :
301
314
return
0 commit comments