Skip to content

Commit 2914cb4

Browse files
committed
network.auth: Refactor MultiDomainBasicAuth to load keyring lazily
Per the discussion in pypa#8719 (keyring support should be opt-in) > [...] defer importing keyring at all until we confirm the opt-in > pypa#8719 (comment)
1 parent 9d00fda commit 2914cb4

File tree

1 file changed

+56
-43
lines changed

1 file changed

+56
-43
lines changed

src/pip/_internal/network/auth.py

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -27,54 +27,66 @@
2727
from pip._internal.vcs.versioncontrol import AuthInfo
2828

2929
Credentials = Tuple[str, str, str]
30+
Keyring = Any # TODO: Use a more-specific type for the keyring module
31+
3032

3133
logger = logging.getLogger(__name__)
3234

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
5035

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+
5244
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
6253

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
6856

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
7564

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
7689

77-
class MultiDomainBasicAuth(AuthBase):
7890

7991
def __init__(self, prompting=True, index_urls=None):
8092
# type: (bool, Optional[List[str]]) -> None
@@ -153,8 +165,8 @@ def _get_new_credentials(self, original_url, allow_netrc=True,
153165
if allow_keyring:
154166
# The index url is more specific than the netloc, so try it first
155167
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)
158170
)
159171
if kr_auth:
160172
logger.debug("Found credentials in keyring for %s", netloc)
@@ -226,7 +238,7 @@ def _prompt_for_password(self, netloc):
226238
username = ask_input(f"User for {netloc}: ")
227239
if not username:
228240
return None, None, False
229-
auth = get_keyring_auth(netloc, username)
241+
auth = self.get_keyring_auth(netloc, username)
230242
if auth and auth[0] is not None and auth[1] is not None:
231243
return auth[0], auth[1], False
232244
password = ask_password("Password: ")
@@ -235,7 +247,7 @@ def _prompt_for_password(self, netloc):
235247
# Factored out to allow for easy patching in tests
236248
def _should_save_password_to_keyring(self):
237249
# type: () -> bool
238-
if not keyring:
250+
if not self.get_keyring():
239251
return False
240252
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
241253

@@ -296,6 +308,7 @@ def warn_on_401(self, resp, **kwargs):
296308
def save_credentials(self, resp, **kwargs):
297309
# type: (Response, **Any) -> None
298310
"""Response callback to save credentials on success."""
311+
keyring = self.get_keyring()
299312
assert keyring is not None, "should never reach here without keyring"
300313
if not keyring:
301314
return

0 commit comments

Comments
 (0)