Skip to content

Commit 23cfe7e

Browse files
Access tokens (#507)
* Access tokens * Unset access token on logout * Update src/huggingface_hub/hf_api.py Co-authored-by: Julien Chaumond <[email protected]> * Address review comments * style Co-authored-by: Julien Chaumond <[email protected]>
1 parent 99bae57 commit 23cfe7e

File tree

4 files changed

+57
-7
lines changed

4 files changed

+57
-7
lines changed

src/huggingface_hub/commands/user.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,19 @@ def run(self):
164164
_| _| _| _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
165165
_| _| _|_| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _| _| _| _|_|_| _|_|_|_|
166166
167+
To login, `huggingface_hub` now requires a token generated from https://huggingface.co/settings/token.
168+
(Deprecated, will be removed in v0.3.0) To login with username and password instead, interrupt with Ctrl+C.
167169
"""
168170
)
169-
username = input("Username: ")
170-
password = getpass()
171-
_login(self._api, username, password)
171+
172+
try:
173+
token = getpass("Token: ")
174+
_login(self._api, token=token)
175+
176+
except KeyboardInterrupt:
177+
username = input("\rUsername: ")
178+
password = getpass()
179+
_login(self._api, username, password)
172180

173181

174182
class WhoamiCommand(BaseUserCommand):
@@ -196,7 +204,13 @@ def run(self):
196204
print("Not logged in")
197205
exit()
198206
HfFolder.delete_token()
199-
self._api.logout(token)
207+
HfApi.unset_access_token()
208+
try:
209+
self._api.logout(token)
210+
except HTTPError as e:
211+
# Logging out with an access token will return a client error.
212+
if not e.response.status_code == 400:
213+
raise e
200214
print("Successfully logged out.")
201215

202216

@@ -398,6 +412,7 @@ def _login(hf_api, username=None, password=None, token=None):
398412
elif not hf_api._is_valid_token(token):
399413
raise ValueError("Invalid token passed.")
400414

415+
hf_api.set_access_token(token)
401416
HfFolder.save_token(token)
402417
print("Login successful")
403418
print("Your token has been saved to", HfFolder.path_token)

src/huggingface_hub/hf_api.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15-
16-
15+
import logging
1716
import os
1817
import re
1918
import subprocess
@@ -41,6 +40,8 @@
4140
from typing_extensions import Literal
4241

4342

43+
USERNAME_PLACEHOLDER = "hf_user"
44+
4445
REMOTE_FILEPATH_REGEX = re.compile(r"^\w[\w\/\-]*(\.\w+)?$")
4546
# ^^ No trailing slash, no backslash, no spaces, no relative parts ("." or "..")
4647
# Only word characters and an optional extension
@@ -333,7 +334,6 @@ def erase_from_credential_store(username=None):
333334
standard_input += "\n"
334335

335336
process.stdin.write(standard_input.encode("utf-8"))
336-
print(standard_input)
337337
process.stdin.flush()
338338

339339

@@ -349,6 +349,9 @@ def login(self, username: str, password: str) -> str:
349349
350350
Throws: requests.exceptions.HTTPError if credentials are invalid
351351
"""
352+
logging.error(
353+
"HfApi.login: This method is deprecated in favor of `set_access_token`."
354+
)
352355
path = "{}/api/login".format(self.endpoint)
353356
r = requests.post(path, json={"username": username, "password": password})
354357
r.raise_for_status()
@@ -391,6 +394,7 @@ def logout(self, token: Optional[str] = None) -> None:
391394
token (``str``, `optional`):
392395
Hugging Face token. Will default to the locally saved token if not provided.
393396
"""
397+
logging.error("This method is deprecated in favor of `unset_access_token`.")
394398
if token is None:
395399
token = HfFolder.get_token()
396400
if token is None:
@@ -405,6 +409,14 @@ def logout(self, token: Optional[str] = None) -> None:
405409
r = requests.post(path, headers={"authorization": "Bearer {}".format(token)})
406410
r.raise_for_status()
407411

412+
@staticmethod
413+
def set_access_token(access_token: str):
414+
write_to_credential_store(USERNAME_PLACEHOLDER, access_token)
415+
416+
@staticmethod
417+
def unset_access_token():
418+
erase_from_credential_store(USERNAME_PLACEHOLDER)
419+
408420
def list_models(
409421
self,
410422
filter: Union[str, Iterable[str], None] = None,

tests/test_hf_api.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@
2525
import pytest
2626

2727
import requests
28+
from huggingface_hub.commands.user import _login
2829
from huggingface_hub.constants import (
2930
REPO_TYPE_DATASET,
3031
REPO_TYPE_SPACE,
3132
SPACES_SDK_TYPES,
3233
)
3334
from huggingface_hub.file_download import cached_download, hf_hub_download
3435
from huggingface_hub.hf_api import (
36+
USERNAME_PLACEHOLDER,
3537
DatasetInfo,
3638
HfApi,
3739
HfFolder,
@@ -48,6 +50,7 @@
4850
ENDPOINT_STAGING_BASIC_AUTH,
4951
FULL_NAME,
5052
PASS,
53+
TOKEN,
5154
USER,
5255
)
5356
from .testing_utils import (
@@ -111,6 +114,22 @@ def test_login_git_credentials(self):
111114
erase_from_credential_store(username=USER)
112115
self.assertTupleEqual(read_from_credential_store(USER), (None, None))
113116

117+
def test_login_cli(self):
118+
_login(self._api, username=USER, password=PASS)
119+
self.assertTupleEqual(read_from_credential_store(USER), (USER.lower(), PASS))
120+
erase_from_credential_store(username=USER)
121+
self.assertTupleEqual(read_from_credential_store(USER), (None, None))
122+
123+
_login(self._api, token=TOKEN)
124+
self.assertTupleEqual(
125+
read_from_credential_store(USERNAME_PLACEHOLDER),
126+
(USERNAME_PLACEHOLDER, TOKEN),
127+
)
128+
erase_from_credential_store(username=USERNAME_PLACEHOLDER)
129+
self.assertTupleEqual(
130+
read_from_credential_store(USERNAME_PLACEHOLDER), (None, None)
131+
)
132+
114133

115134
class HfApiCommonTestWithLogin(HfApiCommonTest):
116135
@classmethod
@@ -572,6 +591,7 @@ def tearDown(self) -> None:
572591
self._api.delete_repo(name=self.REPO_NAME, token=self._token)
573592

574593
def test_model_info(self):
594+
shutil.rmtree(os.path.dirname(HfFolder.path_token))
575595
# Test we cannot access model info without a token
576596
with self.assertRaisesRegex(requests.exceptions.HTTPError, "404 Client Error"):
577597
_ = self._api.model_info(repo_id=f"{USER}/{self.REPO_NAME}")

tests/testing_constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
FULL_NAME = "Dummy User"
33
PASS = "__DUMMY_TRANSFORMERS_PASS__"
44

5+
# Not critical, only usable on the sandboxed CI instance.
6+
TOKEN = "hf_94wBhPGp6KrrTH3KDchhKpRxZwd6dmHWLL"
7+
58
ENDPOINT_PRODUCTION = "https://huggingface.co"
69
ENDPOINT_STAGING = "https://moon-staging.huggingface.co"
710
ENDPOINT_STAGING_BASIC_AUTH = f"https://{USER}:{PASS}@moon-staging.huggingface.co"

0 commit comments

Comments
 (0)