codekingpro/portable-devtools
114k
1# Copyright 2020 The HuggingFace Team. All rights reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7# http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14"""Contains methods to log in to the Hub."""15 16import os17import subprocess18from getpass import getpass19from pathlib import Path20 21import typer22 23from . import constants24from .utils import (25 ANSI,26 capture_output,27 get_token,28 is_google_colab,29 is_notebook,30 list_credential_helpers,31 logging,32 run_subprocess,33 set_git_credential,34 unset_git_credential,35)36from .utils._auth import (37 _get_token_by_name,38 _get_token_from_environment,39 _get_token_from_file,40 _get_token_from_google_colab,41 _save_stored_tokens,42 _save_token,43 get_stored_tokens,44)45 46 47logger = logging.get_logger(__name__)48 49_HF_LOGO_ASCII = """50 _| _| _| _| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _|_|_|_| _|_| _|_|_| _|_|_|_|51 _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|52 _|_|_|_| _| _| _| _|_| _| _|_| _| _| _| _| _| _|_| _|_|_| _|_|_|_| _| _|_|_|53 _| _| _| _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|54 _| _| _|_| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _| _| _| _|_|_| _|_|_|_|55"""56 57 58def login(59 token: str | None = None,60 *,61 add_to_git_credential: bool = False,62 skip_if_logged_in: bool = True,63) -> None:64 """Login the machine to access the Hub.65 66 The `token` is persisted in cache and set as a git credential. Once done, the machine67 is logged in and the access token will be available across all `huggingface_hub`68 components. If `token` is not provided, it will be prompted to the user either with69 a widget (in a notebook) or via the terminal.70 71 To log in from outside of a script, one can also use `hf auth login` which is72 a cli command that wraps [`login`].73 74 > [!TIP]75 > [`login`] is a drop-in replacement method for [`notebook_login`] as it wraps and76 > extends its capabilities.77 78 > [!TIP]79 > When the token is not passed, [`login`] will automatically detect if the script runs80 > in a notebook or not. However, this detection might not be accurate due to the81 > variety of notebooks that exists nowadays. If that is the case, you can always force82 > the UI by using [`notebook_login`] or [`interpreter_login`].83 84 Args:85 token (`str`, *optional*):86 User access token to generate from https://huggingface.co/settings/token.87 add_to_git_credential (`bool`, defaults to `False`):88 If `True`, token will be set as git credential. If no git credential helper89 is configured, a warning will be displayed to the user. If `token` is `None`,90 the value of `add_to_git_credential` is ignored and will be prompted again91 to the end user.92 skip_if_logged_in (`bool`, defaults to `True`):93 If `True`, do not prompt for token if user is already logged in.94 Set to `False` to force re-login. In CLI, use `--force` instead.95 Raises:96 [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)97 If an organization token is passed. Only personal account tokens are valid98 to log in.99 [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)100 If token is invalid.101 [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)102 If running in a notebook but `ipywidgets` is not installed.103 """104 if token is not None:105 if not add_to_git_credential:106 logger.info(107 "The token has not been saved to the git credentials helper. Pass "108 "`add_to_git_credential=True` in this function directly or "109 "`--add-to-git-credential` if using via `hf`CLI if "110 "you want to set the git credential as well."111 )112 _login(token, add_to_git_credential=add_to_git_credential)113 elif is_notebook():114 notebook_login(skip_if_logged_in=skip_if_logged_in)115 else:116 interpreter_login(skip_if_logged_in=skip_if_logged_in)117 118 119def logout(token_name: str | None = None) -> None:120 """Logout the machine from the Hub.121 122 Token is deleted from the machine and removed from git credential.123 124 Args:125 token_name (`str`, *optional*):126 Name of the access token to logout from. If `None`, will log out from all saved access tokens.127 Raises:128 [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError):129 If the access token name is not found.130 """131 if get_token() is None and not get_stored_tokens(): # No active token and no saved access tokens132 logger.warning("Not logged in!")133 return134 if not token_name:135 # Delete all saved access tokens and token136 for file_path in (constants.HF_TOKEN_PATH, constants.HF_STORED_TOKENS_PATH):137 try:138 Path(file_path).unlink()139 except FileNotFoundError:140 pass141 logger.info("Successfully logged out from all access tokens.")142 else:143 _logout_from_token(token_name)144 logger.info(f"Successfully logged out from access token: {token_name}.")145 146 unset_git_credential()147 148 # Check if still logged in149 if _get_token_from_google_colab() is not None:150 raise OSError(151 "You are automatically logged in using a Google Colab secret.\n"152 "To log out, you must unset the `HF_TOKEN` secret in your Colab settings."153 )154 if _get_token_from_environment() is not None:155 raise OSError(156 "Token has been deleted from your machine but you are still logged in.\n"157 "To log out, you must clear out both `HF_TOKEN` and `HUGGING_FACE_HUB_TOKEN` environment variables."158 )159 160 161def auth_switch(token_name: str, add_to_git_credential: bool = False) -> None:162 """Switch to a different access token.163 164 Args:165 token_name (`str`):166 Name of the access token to switch to.167 add_to_git_credential (`bool`, defaults to `False`):168 If `True`, token will be set as git credential. If no git credential helper169 is configured, a warning will be displayed to the user. If `token` is `None`,170 the value of `add_to_git_credential` is ignored and will be prompted again171 to the end user.172 173 Raises:174 [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError):175 If the access token name is not found.176 """177 token = _get_token_by_name(token_name)178 if not token:179 raise ValueError(f"Access token {token_name} not found in {constants.HF_STORED_TOKENS_PATH}")180 # Write token to HF_TOKEN_PATH181 _set_active_token(token_name, add_to_git_credential)182 logger.info(f"The current active token is: {token_name}")183 token_from_environment = _get_token_from_environment()184 if token_from_environment is not None and token_from_environment != token:185 logger.warning(186 "The environment variable `HF_TOKEN` is set and will override the access token you've just switched to."187 )188 189 190def auth_list() -> None:191 """List all stored access tokens."""192 tokens = get_stored_tokens()193 194 if not tokens:195 if _get_token_from_environment():196 logger.info("No stored access tokens found.")197 logger.warning("Note: Environment variable `HF_TOKEN` is set and is the current active token.")198 else:199 logger.info("No access tokens found.")200 return201 # Find current token202 current_token = get_token()203 current_token_name = None204 for token_name in tokens:205 if tokens.get(token_name) == current_token:206 current_token_name = token_name207 # Print header208 max_offset = max(len("token"), max(len(token) for token in tokens)) + 2209 print(f" {{:<{max_offset}}}| {{:<15}}".format("name", "token"))210 print("-" * (max_offset + 2) + "|" + "-" * 15)211 212 # Print saved access tokens213 for token_name in tokens:214 token = tokens.get(token_name, "<not set>")215 masked_token = f"{token[:3]}****{token[-4:]}" if token != "<not set>" else token216 is_current = "*" if token == current_token else " "217 218 print(f"{is_current} {{:<{max_offset}}}| {{:<15}}".format(token_name, masked_token))219 220 if _get_token_from_environment():221 logger.warning(222 "\nNote: Environment variable `HF_TOKEN` is set and is the current active token independently from the stored tokens listed above."223 )224 elif current_token_name is None:225 logger.warning(226 "\nNote: No active token is set and no environment variable `HF_TOKEN` is found. Use `hf auth login` to log in."227 )228 229 230###231# Interpreter-based login (text)232###233 234 235def interpreter_login(*, skip_if_logged_in: bool = True) -> None:236 """237 Displays a prompt to log in to the HF website and store the token.238 239 This is equivalent to [`login`] without passing a token when not run in a notebook.240 [`interpreter_login`] is useful if you want to force the use of the terminal prompt241 instead of a notebook widget.242 243 For more details, see [`login`].244 245 Args:246 skip_if_logged_in (`bool`, defaults to `True`):247 If `True`, do not prompt for token if user is already logged in.248 Set to `False` to force re-login. In CLI, use `--force` instead.249 """250 if skip_if_logged_in and get_token() is not None:251 logger.info("User is already logged in. Use `hf auth login --force` to force re-login.")252 return253 254 print(_HF_LOGO_ASCII)255 if get_token() is not None:256 logger.info(257 " A token is already saved on your machine. Run `hf auth whoami`"258 " to get more information or `hf auth logout` if you want"259 " to log out."260 )261 logger.info(" Setting a new token will erase the existing one.")262 263 logger.info(264 " To log in, `huggingface_hub` requires a token generated from https://huggingface.co/settings/tokens ."265 )266 if os.name == "nt":267 logger.info("Token can be pasted using 'Right-Click'.")268 token = getpass("Enter your token (input will not be visible): ")269 add_to_git_credential = typer.confirm("Add token as git credential?")270 271 _login(token=token, add_to_git_credential=add_to_git_credential)272 273 274###275# Notebook-based login (widget)276###277 278NOTEBOOK_LOGIN_PASSWORD_HTML = """<center> <img279src=https://huggingface.co/front/assets/huggingface_logo-noborder.svg280alt='Hugging Face'> <br> Immediately click login after typing your password or281it might be stored in plain text in this notebook file. </center>"""282 283 284NOTEBOOK_LOGIN_TOKEN_HTML_START = """<center> <img285src=https://huggingface.co/front/assets/huggingface_logo-noborder.svg286alt='Hugging Face'> <br> Copy a token from <a287href="https://huggingface.co/settings/tokens" target="_blank">your Hugging Face288tokens page</a> and paste it below. <br> Immediately click login after copying289your token or it might be stored in plain text in this notebook file. </center>"""290 291 292NOTEBOOK_LOGIN_TOKEN_HTML_END = """293<b>Pro Tip:</b> If you don't already have one, you can create a dedicated294'notebooks' token with 'write' access, that you can then easily reuse for all295notebooks. </center>"""296 297 298def notebook_login(*, skip_if_logged_in: bool = True) -> None:299 """300 Displays a widget to log in to the HF website and store the token.301 302 This is equivalent to [`login`] without passing a token when run in a notebook.303 [`notebook_login`] is useful if you want to force the use of the notebook widget304 instead of a prompt in the terminal.305 306 For more details, see [`login`].307 308 Args:309 skip_if_logged_in (`bool`, defaults to `True`):310 If `True`, do not prompt for token if user is already logged in.311 Set to `False` to force re-login. In CLI, use `--force` instead.312 """313 try:314 import ipywidgets.widgets as widgets # type: ignore315 from IPython.display import display # type: ignore316 except ImportError:317 raise ImportError(318 "The `notebook_login` function can only be used in a notebook (Jupyter or"319 " Colab) and you need the `ipywidgets` module: `pip install ipywidgets`."320 )321 if skip_if_logged_in and get_token() is not None:322 logger.info("User is already logged in. Use `hf auth login --force` to force re-login.")323 return324 325 box_layout = widgets.Layout(display="flex", flex_flow="column", align_items="center", width="50%")326 327 token_widget = widgets.Password(description="Token:")328 git_checkbox_widget = widgets.Checkbox(value=True, description="Add token as git credential?")329 token_finish_button = widgets.Button(description="Login")330 331 login_token_widget = widgets.VBox(332 [333 widgets.HTML(NOTEBOOK_LOGIN_TOKEN_HTML_START),334 token_widget,335 git_checkbox_widget,336 token_finish_button,337 widgets.HTML(NOTEBOOK_LOGIN_TOKEN_HTML_END),338 ],339 layout=box_layout,340 )341 display(login_token_widget)342 343 # On click events344 def login_token_event(t):345 """Event handler for the login button."""346 token = token_widget.value347 add_to_git_credential = git_checkbox_widget.value348 # Erase token and clear value to make sure it's not saved in the notebook.349 token_widget.value = ""350 # Hide inputs351 login_token_widget.children = [widgets.Label("Connecting...")]352 try:353 with capture_output() as captured:354 _login(token, add_to_git_credential=add_to_git_credential)355 message = captured.getvalue()356 except Exception as error:357 message = str(error)358 # Print result (success message or error)359 login_token_widget.children = [widgets.Label(line) for line in message.split("\n") if line.strip()]360 361 token_finish_button.on_click(login_token_event)362 363 364###365# Login private helpers366###367 368 369def _login(370 token: str,371 add_to_git_credential: bool,372) -> None:373 from .hf_api import whoami # avoid circular import374 375 if token.startswith("api_org"):376 raise ValueError("You must use your personal account token, not an organization token.")377 378 token_info = whoami(token)379 permission = token_info["auth"]["accessToken"]["role"]380 logger.info(f"Token is valid (permission: {permission}).")381 382 token_name = token_info["auth"]["accessToken"]["displayName"]383 # Store token locally384 _save_token(token=token, token_name=token_name)385 # Set active token386 _set_active_token(token_name=token_name, add_to_git_credential=add_to_git_credential)387 logger.info("Login successful.")388 if _get_token_from_environment():389 logger.warning(390 "Note: Environment variable`HF_TOKEN` is set and is the current active token independently from the token you've just configured."391 )392 else:393 logger.info(f"The current active token is: `{token_name}`")394 395 396def _logout_from_token(token_name: str) -> None:397 """Logout from a specific access token.398 399 Args:400 token_name (`str`):401 The name of the access token to logout from.402 Raises:403 [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError):404 If the access token name is not found.405 """406 stored_tokens = get_stored_tokens()407 # If there is no access tokens saved or the access token name is not found, do nothing408 if not stored_tokens or token_name not in stored_tokens:409 return410 411 token = stored_tokens.pop(token_name)412 _save_stored_tokens(stored_tokens)413 414 if token == _get_token_from_file():415 logger.warning(f"Active token '{token_name}' has been deleted.")416 Path(constants.HF_TOKEN_PATH).unlink(missing_ok=True)417 418 419def _set_active_token(420 token_name: str,421 add_to_git_credential: bool,422) -> None:423 """Set the active access token.424 425 Args:426 token_name (`str`):427 The name of the token to set as active.428 """429 token = _get_token_by_name(token_name)430 if not token:431 raise ValueError(f"Token {token_name} not found in {constants.HF_STORED_TOKENS_PATH}")432 if add_to_git_credential:433 if _is_git_credential_helper_configured():434 set_git_credential(token)435 logger.info(436 "Your token has been saved in your configured git credential helpers"437 + f" ({','.join(list_credential_helpers())})."438 )439 else:440 logger.warning("Token has not been saved to git credential helper.")441 # Write token to HF_TOKEN_PATH442 path = Path(constants.HF_TOKEN_PATH)443 path.parent.mkdir(parents=True, exist_ok=True)444 path.write_text(token)445 logger.info(f"Your token has been saved to {constants.HF_TOKEN_PATH}")446 447 448def _is_git_credential_helper_configured() -> bool:449 """Check if a git credential helper is configured.450 451 Warns user if not the case (except for Google Colab where "store" is set by default452 by `huggingface_hub`).453 """454 helpers = list_credential_helpers()455 if len(helpers) > 0:456 return True # Do not warn: at least 1 helper is set457 458 # Only in Google Colab to avoid the warning message459 # See https://github.com/huggingface/huggingface_hub/issues/1043#issuecomment-1247010710460 if is_google_colab():461 _set_store_as_git_credential_helper_globally()462 return True # Do not warn: "store" is used by default in Google Colab463 464 # Otherwise, warn user465 print(466 ANSI.red(467 "Cannot authenticate through git-credential as no helper is defined on your"468 " machine.\nYou might have to re-authenticate when pushing to the Hugging"469 " Face Hub.\nRun the following command in your terminal in case you want to"470 " set the 'store' credential helper as default.\n\ngit config --global"471 " credential.helper store\n\nRead"472 " https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage for more"473 " details."474 )475 )476 return False477 478 479def _set_store_as_git_credential_helper_globally() -> None:480 """Set globally the credential.helper to `store`.481 482 To be used only in Google Colab as we assume the user doesn't care about the git483 credential config. It is the only particular case where we don't want to display the484 warning message in [`notebook_login()`].485 486 Related:487 - https://github.com/huggingface/huggingface_hub/issues/1043488 - https://github.com/huggingface/huggingface_hub/issues/1051489 - https://git-scm.com/docs/git-credential-store490 """491 try:492 run_subprocess("git config --global credential.helper store")493 except subprocess.CalledProcessError as exc:494 raise OSError(exc.stderr)495 