Skip to content

Commit

Permalink
network.auth: Refactor MultiDomainBasicAuth to load keyring lazily
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
nbraud committed Feb 1, 2021
1 parent 9d00fda commit 2914cb4
Showing 1 changed file with 56 additions and 43 deletions.
99 changes: 56 additions & 43 deletions src/pip/_internal/network/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,54 +27,66 @@
from pip._internal.vcs.versioncontrol import AuthInfo

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


logger = logging.getLogger(__name__)

try:
import keyring
except ImportError:
keyring = None
except Exception as exc:
logger.warning(
"Keyring is skipped due to an exception: %s", str(exc),
)
keyring = None


def get_keyring_auth(url, username):
# type: (str, str) -> Optional[AuthInfo]
"""Return the tuple auth for a given url from keyring."""
global keyring
if not url or not keyring:
return None

try:
class MultiDomainBasicAuth(AuthBase):
@classmethod
def get_keyring(cls):
# type: () -> Optional[Keyring]
# Cache so the import is attempted at most once
if hasattr(cls, '_keyring'):
return cls._keyring

try:
get_credential = keyring.get_credential
except AttributeError:
pass
else:
logger.debug("Getting credentials from keyring for %s", url)
cred = get_credential(url, username)
if cred is not None:
return cred.username, cred.password
return None
import keyring
except ImportError:
keyring = None
except Exception as exc:
logger.warning(
"Keyring is skipped due to an exception: %s", str(exc),
)
keyring = None

if username:
logger.debug("Getting password from keyring for %s", url)
password = keyring.get_password(url, username)
if password:
return username, password
cls._keyring = keyring
return keyring

except Exception as exc:
logger.warning(
"Keyring is skipped due to an exception: %s", str(exc),
)
keyring = None
return None
@classmethod
def get_keyring_auth(cls, url, username):
# type: (str, str) -> Optional[AuthInfo]
"""Return the tuple auth for a given url from keyring."""
keyring = cls.get_keyring()
if not url or not keyring:
return None

try:
try:
get_credential = keyring.get_credential
except AttributeError:
pass
else:
logger.debug("Getting credentials from keyring for %s", url)
cred = get_credential(url, username)
if cred is not None:
return cred.username, cred.password
return None

if username:
logger.debug("Getting password from keyring for %s", url)
password = keyring.get_password(url, username)
if password:
return username, password

except Exception as exc:
logger.warning(
"Keyring is skipped due to an exception: %s", str(exc),
)
cls._keyring = None
return None

class MultiDomainBasicAuth(AuthBase):

def __init__(self, prompting=True, index_urls=None):
# type: (bool, Optional[List[str]]) -> None
Expand Down Expand Up @@ -153,8 +165,8 @@ def _get_new_credentials(self, original_url, allow_netrc=True,
if allow_keyring:
# The index url is more specific than the netloc, so try it first
kr_auth = (
get_keyring_auth(index_url, username) or
get_keyring_auth(netloc, username)
self.get_keyring_auth(index_url, username) or
self.get_keyring_auth(netloc, username)
)
if kr_auth:
logger.debug("Found credentials in keyring for %s", netloc)
Expand Down Expand Up @@ -226,7 +238,7 @@ def _prompt_for_password(self, netloc):
username = ask_input(f"User for {netloc}: ")
if not username:
return None, None, False
auth = get_keyring_auth(netloc, username)
auth = self.get_keyring_auth(netloc, username)
if auth and auth[0] is not None and auth[1] is not None:
return auth[0], auth[1], False
password = ask_password("Password: ")
Expand All @@ -235,7 +247,7 @@ def _prompt_for_password(self, netloc):
# Factored out to allow for easy patching in tests
def _should_save_password_to_keyring(self):
# type: () -> bool
if not keyring:
if not self.get_keyring():
return False
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"

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

0 comments on commit 2914cb4

Please sign in to comment.