4
4
providing credentials in the context of network requests.
5
5
"""
6
6
7
+ import functools
7
8
import os
8
9
import shutil
9
10
import subprocess
10
11
import urllib .parse
11
12
from abc import ABC , abstractmethod
12
- from typing import Any , Dict , List , NamedTuple , Optional , Tuple , Type
13
+ from typing import Any , Dict , List , NamedTuple , Optional , Tuple
13
14
14
15
from pip ._vendor .requests .auth import AuthBase , HTTPBasicAuth
15
16
from pip ._vendor .requests .models import Request , Response
@@ -37,59 +38,56 @@ class Credentials(NamedTuple):
37
38
class KeyRingBaseProvider (ABC ):
38
39
"""Keyring base provider interface"""
39
40
40
- @classmethod
41
41
@abstractmethod
42
- def is_available (cls ) -> bool :
42
+ def is_available (self ) -> bool :
43
43
...
44
44
45
- @classmethod
46
45
@abstractmethod
47
- def get_auth_info (cls , url : str , username : Optional [str ]) -> Optional [AuthInfo ]:
46
+ def get_auth_info (self , url : str , username : Optional [str ]) -> Optional [AuthInfo ]:
48
47
...
49
48
50
- @classmethod
51
49
@abstractmethod
52
- def save_auth_info (cls , url : str , username : str , password : str ) -> None :
50
+ def save_auth_info (self , url : str , username : str , password : str ) -> None :
53
51
...
54
52
55
53
56
54
class KeyRingPythonProvider (KeyRingBaseProvider ):
57
55
"""Keyring interface which uses locally imported `keyring`"""
58
56
59
- try :
60
- import keyring
61
- except ImportError :
62
- keyring = None # type: ignore[assignment]
57
+ def __init__ (self ) -> None :
58
+ try :
59
+ import keyring
60
+ except ImportError :
61
+ keyring = None # type: ignore[assignment]
63
62
64
- @classmethod
65
- def is_available (cls ) -> bool :
66
- return cls .keyring is not None
63
+ self .keyring = keyring
67
64
68
- @classmethod
69
- def get_auth_info (cls , url : str , username : Optional [str ]) -> Optional [AuthInfo ]:
70
- if cls .is_available is False :
65
+ def is_available (self ) -> bool :
66
+ return self .keyring is not None
67
+
68
+ def get_auth_info (self , url : str , username : Optional [str ]) -> Optional [AuthInfo ]:
69
+ if self .is_available is False :
71
70
return None
72
71
73
72
# Support keyring's get_credential interface which supports getting
74
73
# credentials without a username. This is only available for
75
74
# keyring>=15.2.0.
76
- if hasattr (cls .keyring , "get_credential" ):
75
+ if hasattr (self .keyring , "get_credential" ):
77
76
logger .debug ("Getting credentials from keyring for %s" , url )
78
- cred = cls .keyring .get_credential (url , username )
77
+ cred = self .keyring .get_credential (url , username )
79
78
if cred is not None :
80
79
return cred .username , cred .password
81
80
return None
82
81
83
82
if username is not None :
84
83
logger .debug ("Getting password from keyring for %s" , url )
85
- password = cls .keyring .get_password (url , username )
84
+ password = self .keyring .get_password (url , username )
86
85
if password :
87
86
return username , password
88
87
return None
89
88
90
- @classmethod
91
- def save_auth_info (cls , url : str , username : str , password : str ) -> None :
92
- cls .keyring .set_password (url , username , password )
89
+ def save_auth_info (self , url : str , username : str , password : str ) -> None :
90
+ self .keyring .set_password (url , username , password )
93
91
94
92
95
93
class KeyRingCliProvider (KeyRingBaseProvider ):
@@ -101,38 +99,35 @@ class KeyRingCliProvider(KeyRingBaseProvider):
101
99
PATH.
102
100
"""
103
101
104
- keyring = shutil .which ("keyring" )
102
+ def __init__ (self ) -> None :
103
+ self .keyring = shutil .which ("keyring" )
105
104
106
- @classmethod
107
- def is_available (cls ) -> bool :
108
- return cls .keyring is not None
105
+ def is_available (self ) -> bool :
106
+ return self .keyring is not None
109
107
110
- @classmethod
111
- def get_auth_info (cls , url : str , username : Optional [str ]) -> Optional [AuthInfo ]:
112
- if cls .is_available is False :
108
+ def get_auth_info (self , url : str , username : Optional [str ]) -> Optional [AuthInfo ]:
109
+ if self .is_available is False :
113
110
return None
114
111
115
112
# This is the default implementation of keyring.get_credential
116
113
# https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
117
114
if username is not None :
118
- password = cls ._get_password (url , username )
115
+ password = self ._get_password (url , username )
119
116
if password is not None :
120
117
return username , password
121
118
return None
122
119
123
- @classmethod
124
- def save_auth_info (cls , url : str , username : str , password : str ) -> None :
125
- if not cls .is_available :
120
+ def save_auth_info (self , url : str , username : str , password : str ) -> None :
121
+ if not self .is_available :
126
122
raise RuntimeError ("keyring is not available" )
127
- return cls ._set_password (url , username , password )
123
+ return self ._set_password (url , username , password )
128
124
129
- @classmethod
130
- def _get_password (cls , service_name : str , username : str ) -> Optional [str ]:
125
+ def _get_password (self , service_name : str , username : str ) -> Optional [str ]:
131
126
"""Mirror the implemenation of keyring.get_password using cli"""
132
- if cls .keyring is None :
127
+ if self .keyring is None :
133
128
return None
134
129
135
- cmd = [cls .keyring , "get" , service_name , username ]
130
+ cmd = [self .keyring , "get" , service_name , username ]
136
131
env = os .environ .copy ()
137
132
env ["PYTHONIOENCODING" ] = "utf-8"
138
133
res = subprocess .run (
@@ -145,13 +140,12 @@ def _get_password(cls, service_name: str, username: str) -> Optional[str]:
145
140
return None
146
141
return res .stdout .decode ("utf-8" ).strip ("\n " )
147
142
148
- @classmethod
149
- def _set_password (cls , service_name : str , username : str , password : str ) -> None :
143
+ def _set_password (self , service_name : str , username : str , password : str ) -> None :
150
144
"""Mirror the implemenation of keyring.set_password using cli"""
151
- if cls .keyring is None :
145
+ if self .keyring is None :
152
146
return None
153
147
154
- cmd = [cls .keyring , "set" , service_name , username ]
148
+ cmd = [self .keyring , "set" , service_name , username ]
155
149
input_ = password .encode ("utf-8" ) + b"\n "
156
150
env = os .environ .copy ()
157
151
env ["PYTHONIOENCODING" ] = "utf-8"
@@ -160,11 +154,16 @@ def _set_password(cls, service_name: str, username: str, password: str) -> None:
160
154
return None
161
155
162
156
163
- def get_keyring_provider () -> Optional [Type [KeyRingBaseProvider ]]:
164
- if KeyRingPythonProvider .is_available ():
165
- return KeyRingPythonProvider
166
- if KeyRingCliProvider .is_available ():
167
- return KeyRingCliProvider
157
+ @functools .lru_cache (maxsize = 1 )
158
+ def get_keyring_provider () -> Optional [KeyRingBaseProvider ]:
159
+ python_keyring = KeyRingPythonProvider ()
160
+ if python_keyring .is_available ():
161
+ return python_keyring
162
+
163
+ cli_keyring = KeyRingCliProvider ()
164
+ if cli_keyring .is_available ():
165
+ return cli_keyring
166
+
168
167
return None
169
168
170
169
0 commit comments