Skip to content

Commit 0c4e786

Browse files
author
Dos Moonen
committed
Add cli flag --force-keyring.
Currently, Pip is conservative and does not query keyring at all when `--no-input` is used because the keyring might require user interaction such as prompting the user on the console. This commit adds a flag to force keyring usage if it is installed. It defaults to `False`, making this opt-in behaviour. Tools such as Pipx and Pipenv use Pip and have their own progress indicator that hides output from the subprocess Pip runs in. They should pass `--no-input` in my opinion, but Pip should provide some mechanism to still query the keyring in that case. Just not by default. So here we are.
1 parent 46bf998 commit 0c4e786

File tree

3 files changed

+36
-11
lines changed

3 files changed

+36
-11
lines changed

src/pip/_internal/cli/cmdoptions.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,19 @@ class PipOption(Option):
261261
help="Disable prompting for input.",
262262
)
263263

264+
force_keyring: Callable[..., Option] = partial(
265+
Option,
266+
"--force-keyring",
267+
dest="force_keyring",
268+
action="store_true",
269+
default=False,
270+
help=(
271+
"Always query the keyring, regardless of pip's --no-input option. Note"
272+
" that this may cause problems if the keyring expects to be able to"
273+
" prompt the user interactively and no interactive user is available."
274+
),
275+
)
276+
264277
proxy: Callable[..., Option] = partial(
265278
Option,
266279
"--proxy",
@@ -993,6 +1006,7 @@ def check_list_path_option(options: Values) -> None:
9931006
quiet,
9941007
log,
9951008
no_input,
1009+
force_keyring,
9961010
proxy,
9971011
retries,
9981012
timeout,

src/pip/_internal/cli/req_command.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ def _build_session(
116116

117117
# Determine if we can prompt the user for authentication or not
118118
session.auth.prompting = not options.no_input
119+
# We won't use keyring when --no-input is passed unless
120+
# --force-keyring is passed as well because it might require
121+
# user interaction
122+
session.auth.use_keyring = session.auth.prompting or options.force_keyring
119123

120124
return session
121125

src/pip/_internal/network/auth.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,14 @@ def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[Au
7272

7373
class MultiDomainBasicAuth(AuthBase):
7474
def __init__(
75-
self, prompting: bool = True, index_urls: Optional[List[str]] = None
75+
self,
76+
prompting: bool = True,
77+
index_urls: Optional[List[str]] = None,
78+
use_keyring: bool = True,
7679
) -> None:
7780
self.prompting = prompting
7881
self.index_urls = index_urls
82+
self.use_keyring = keyring and use_keyring
7983
self.passwords: Dict[str, AuthInfo] = {}
8084
# When the user is prompted to enter credentials and keyring is
8185
# available, we will offer to save them. If the user accepts,
@@ -231,7 +235,7 @@ def __call__(self, req: Request) -> Request:
231235
def _prompt_for_password(
232236
self, netloc: str
233237
) -> Tuple[Optional[str], Optional[str], bool]:
234-
username = ask_input(f"User for {netloc}: ")
238+
username = ask_input(f"User for {netloc}: ") if self.prompting else None
235239
if not username:
236240
return None, None, False
237241
auth = get_keyring_auth(netloc, username)
@@ -242,7 +246,7 @@ def _prompt_for_password(
242246

243247
# Factored out to allow for easy patching in tests
244248
def _should_save_password_to_keyring(self) -> bool:
245-
if not keyring:
249+
if not self.prompting or not keyring:
246250
return False
247251
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
248252

@@ -252,19 +256,22 @@ def handle_401(self, resp: Response, **kwargs: Any) -> Response:
252256
if resp.status_code != 401:
253257
return resp
254258

259+
username, password = None, None
260+
261+
# Query the keyring for credentials:
262+
if self.use_keyring:
263+
username, password = self._get_new_credentials(
264+
resp.url,
265+
allow_netrc=True,
266+
allow_keyring=True,
267+
)
268+
255269
# We are not able to prompt the user so simply return the response
256-
if not self.prompting:
270+
if not self.prompting and not username and not password:
257271
return resp
258272

259273
parsed = urllib.parse.urlparse(resp.url)
260274

261-
# Query the keyring for credentials:
262-
username, password = self._get_new_credentials(
263-
resp.url,
264-
allow_netrc=True,
265-
allow_keyring=True,
266-
)
267-
268275
# Prompt the user for a new username and password
269276
save = False
270277
if not username and not password:

0 commit comments

Comments
 (0)