Skip to content

Commit 5c3d1fe

Browse files
authored
Merge pull request #11698 from Darsstar/keyring-multi-choice
2 parents 85eb40d + 6affad8 commit 5c3d1fe

File tree

9 files changed

+498
-105
lines changed

9 files changed

+498
-105
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ repos:
4747
additional_dependencies: [
4848
'keyring==23.0.1',
4949
'nox==2021.6.12',
50-
'pytest==7.1.1',
50+
'pytest',
5151
'types-docutils==0.18.3',
5252
'types-setuptools==57.4.14',
5353
'types-freezegun==1.1.9',

docs/html/topics/authentication.md

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,120 @@ man pages][netrc-docs].
6666
## Keyring Support
6767

6868
pip supports loading credentials stored in your keyring using the
69-
{pypi}`keyring` library.
69+
{pypi}`keyring` library, which can be enabled py passing `--keyring-provider`
70+
with a value of `auto`, `disabled`, `import`, or `subprocess`. The default
71+
value `auto` respects `--no-input` and not query keyring at all if the option
72+
is used; otherwise it tries the `import`, `subprocess`, and `disabled`
73+
providers (in this order) and uses the first one that works.
7074

71-
pip will first try to use `keyring` in the same environment as itself and
72-
fallback to using any `keyring` installation which is available on `PATH`.
75+
### Configuring pip's keyring usage
7376

74-
Therefore, either of the following setups will work:
77+
Since the keyring configuration is likely system-wide, a more common way to
78+
configure its usage would be to use a configuration instead:
79+
80+
```{seealso}
81+
{doc}`./configuration` describes how pip configuration works.
82+
```
7583

7684
```bash
77-
$ pip install keyring # install keyring from PyPI into same environment as pip
78-
$ echo "your-password" | keyring set pypi.company.com your-username
79-
$ pip install your-package --index-url https://pypi.company.com/
85+
$ pip config set --global global.keyring-provider subprocess
86+
87+
# A different user on the same system which has PYTHONPATH configured and and
88+
# wanting to use keyring installed that way could then run
89+
$ pip config set --user global.keyring-provider import
90+
91+
# For a specific virtual environment you might want to use disable it again
92+
# because you will only be using PyPI and the private repo (and mirror)
93+
# requires 2FA with a keycard and a pincode
94+
$ pip config set --site global.index https://pypi.org/simple
95+
$ pip config set --site global.keyring-provider disabled
96+
97+
# configuring it via environment variable is also possible
98+
$ export PIP_KEYRING_PROVIDER=disabled
8099
```
81100

82-
or
101+
### Using keyring's Python module
102+
103+
Setting `keyring-provider` to `import` makes pip communicate with `keyring` via
104+
its Python interface.
83105

84106
```bash
85-
$ pipx install keyring # install keyring from PyPI into standalone environment
107+
# install keyring from PyPI
108+
$ pip install keyring --index-url https://pypi.org/simple
86109
$ echo "your-password" | keyring set pypi.company.com your-username
87-
$ pip install your-package --index-url https://pypi.company.com/
110+
$ pip install your-package --keyring-provider import --index-url https://pypi.company.com/
111+
```
112+
113+
### Using keyring as a command line application
114+
115+
Setting `keyring-provider` to `subprocess` makes pip look for and use the
116+
`keyring` command found on `PATH`.
117+
118+
For this use case, a username *must* be included in the URL, since it is
119+
required by `keyring`'s command line interface. See the example below or the
120+
basic HTTP authentication section at the top of this page.
121+
122+
```bash
123+
# Install keyring from PyPI using pipx, which we assume is installed properly
124+
# you can also create a venv somewhere and add it to the PATH yourself instead
125+
$ pipx install keyring --index-url https://pypi.org/simple
126+
127+
# For Azure DevOps, also install its keyring backend.
128+
$ pipx inject keyring artifacts-keyring --index-url https://pypi.org/simple
129+
130+
# For Google Artifact Registry, also install and initialize its keyring backend.
131+
$ pipx inject keyring keyrings.google-artifactregistry-auth --index-url https://pypi.org/simple
132+
$ gcloud auth login
133+
134+
# Note that a username is required in the index URL.
135+
$ pip install your-package --keyring-provider subprocess --index-url https://[email protected]/
136+
```
137+
138+
### Here be dragons
139+
140+
The `auto` provider is conservative and does not query keyring at all when
141+
`--no-input` is used because the keyring might require user interaction such as
142+
prompting the user on the console. Third party tools frequently call Pip for
143+
you and do indeed pass `--no-input` as they are well-behaved and don't have
144+
much information to work with. (Keyring does have an api to request a backend
145+
that does not require user input.) You have more information about your system,
146+
however!
147+
148+
You can force keyring usage by requesting a keyring provider other than `auto`
149+
(or `disabled`). Leaving `import` and `subprocess`. You do this by passing
150+
`--keyring-provider import` or one of the following methods:
151+
152+
```bash
153+
# via config file, possibly with --user, --global or --site
154+
$ pip config set global.keyring-provider subprocess
155+
# or via environment variable
156+
$ export PIP_KEYRING_PROVIDER=import
157+
```
158+
159+
```{warning}
160+
Be careful when doing this since it could cause tools such as pipx and Pipenv
161+
to appear to hang. They show their own progress indicator while hiding output
162+
from the subprocess in which they run Pip. You won't know whether the keyring
163+
backend is waiting the user input or not in such situations.
164+
```
165+
166+
pip is conservative and does not query keyring at all when `--no-input` is used
167+
because the keyring might require user interaction such as prompting the user
168+
on the console. You can force keyring usage by passing `--force-keyring` or one
169+
of the following:
170+
171+
```bash
172+
# possibly with --user, --global or --site
173+
$ pip config set global.force-keyring true
174+
# or
175+
$ export PIP_FORCE_KEYRING=1
176+
```
177+
178+
```{warning}
179+
Be careful when doing this since it could cause tools such as pipx and Pipenv
180+
to appear to hang. They show their own progress indicator while hiding output
181+
from the subprocess in which they run Pip. You won't know whether the keyring
182+
backend is waiting the user input or not in such situations.
88183
```
89184

90185
Note that `keyring` (the Python package) needs to be installed separately from

news/8719.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add ``--keyring-provider`` flag. See the Authentication page in the documentation for more info.

src/pip/_internal/cli/cmdoptions.py

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

255+
keyring_provider: Callable[..., Option] = partial(
256+
Option,
257+
"--keyring-provider",
258+
dest="keyring_provider",
259+
choices=["auto", "disabled", "import", "subprocess"],
260+
default="disabled",
261+
help=(
262+
"Enable the credential lookup via the keyring library if user input is allowed."
263+
" Specify which mechanism to use [disabled, import, subprocess]."
264+
" (default: disabled)"
265+
),
266+
)
267+
255268
proxy: Callable[..., Option] = partial(
256269
Option,
257270
"--proxy",
@@ -1028,6 +1041,7 @@ def check_list_path_option(options: Values) -> None:
10281041
quiet,
10291042
log,
10301043
no_input,
1044+
keyring_provider,
10311045
proxy,
10321046
retries,
10331047
timeout,

src/pip/_internal/cli/req_command.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ def _build_session(
151151

152152
# Determine if we can prompt the user for authentication or not
153153
session.auth.prompting = not options.no_input
154+
session.auth.keyring_provider = options.keyring_provider
154155

155156
return session
156157

0 commit comments

Comments
 (0)