wandb_login.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. """Log in to Weights & Biases.
  2. This authenticates your machine to log data to your account.
  3. """
  4. from __future__ import annotations
  5. import click
  6. import wandb
  7. from wandb.errors import AuthenticationError, term
  8. from wandb.sdk import wandb_setup
  9. from wandb.sdk.lib import settings_file, wbauth
  10. from wandb.sdk.lib.deprecation import UNSET, DoNotSet
  11. from ..apis import InternalApi
  12. def login(
  13. key: str | None = None,
  14. relogin: bool | None = None,
  15. host: str | None = None,
  16. force: bool | None = None,
  17. timeout: int | None = None,
  18. verify: bool = False,
  19. referrer: str | None = None,
  20. anonymous: DoNotSet = UNSET,
  21. ) -> bool:
  22. """Log into W&B.
  23. You generally don't have to use this because most W&B methods that need
  24. authentication can log in implicitly. This is the programmatic counterpart
  25. to the `wandb login` CLI.
  26. This updates global credentials for the session (affecting all wandb usage
  27. in the current Python process after this call) and possibly the .netrc file.
  28. If the identity_token_file setting is set, like through the
  29. WANDB_IDENTITY_TOKEN_FILE environment variable, then this is a no-op.
  30. Otherwise, if an explicit API key is provided, it is used and written to
  31. the system .netrc file. If no key is provided, but the session is already
  32. authenticated, then the session key is used for verification (if verify
  33. is True) and the .netrc file is not updated.
  34. If none of the above is true, then this gets the API key from the first of:
  35. - The WANDB_API_KEY environment variable
  36. - The api_key setting in a system or workspace settings file
  37. - The .netrc file (either ~/.netrc, ~/_netrc or the path specified by the
  38. NETRC environment variable)
  39. - An interactive prompt (if available)
  40. Args:
  41. key: The API key to use.
  42. relogin: If true, get the API key from an interactive prompt, skipping
  43. reading .netrc, environment variables, etc.
  44. host: The W&B server URL to connect to.
  45. force: If true, disallows selecting offline mode in the interactive
  46. prompt.
  47. timeout: Number of seconds to wait for user input in the interactive
  48. prompt. This can be used as a failsafe if an interactive prompt
  49. is incorrectly shown in a non-interactive environment.
  50. verify: Verify the credentials with the W&B server and raise an
  51. AuthenticationError on failure.
  52. referrer: The referrer to use in the URL login request for analytics.
  53. Returns:
  54. bool: If `key` is configured.
  55. Raises:
  56. AuthenticationError: If `api_key` fails verification with the server.
  57. UsageError: If `api_key` cannot be configured and no tty.
  58. """
  59. if anonymous is not UNSET:
  60. term.termwarn(
  61. "The anonymous parameter to wandb.login() has no effect and will"
  62. + " be removed in future versions.",
  63. repeat=False,
  64. )
  65. if wandb.run is not None:
  66. term.termwarn("Calling wandb.login() after wandb.init() has no effect.")
  67. return False
  68. global_settings = wandb_setup.singleton().settings
  69. if global_settings._noop:
  70. return False
  71. if global_settings._offline and not global_settings.x_cli_only_mode:
  72. term.termwarn("Unable to verify login in offline mode.")
  73. return False
  74. if host:
  75. host = host.rstrip("/")
  76. _update_system_settings(
  77. global_settings.read_system_settings(),
  78. host=host,
  79. )
  80. logged_in, _ = _login(
  81. key=key,
  82. relogin=relogin,
  83. host=host,
  84. force=force,
  85. timeout=timeout,
  86. verify=verify,
  87. referrer=referrer or "models",
  88. )
  89. return logged_in
  90. def _update_system_settings(
  91. system_settings: settings_file.SettingsFiles,
  92. *,
  93. host: str | None,
  94. ) -> None:
  95. """Update the user's system settings files."""
  96. # 'anonymous' is deprecated; we clear it automatically for now.
  97. system_settings.clear("anonymous", globally=True)
  98. if host:
  99. if host == "https://api.wandb.ai":
  100. system_settings.clear("base_url", globally=True)
  101. else:
  102. system_settings.set("base_url", host, globally=True)
  103. try:
  104. system_settings.save()
  105. except settings_file.SaveSettingsError as e:
  106. wandb.termwarn(str(e))
  107. def _login(
  108. *,
  109. key: str | None = None,
  110. relogin: bool | None = None,
  111. host: str | None = None,
  112. force: bool | None = None,
  113. timeout: float | None = None,
  114. verify: bool = False,
  115. referrer: str = "models",
  116. update_api_key: bool = True,
  117. _silent: bool | None = None,
  118. ) -> tuple[bool, str | None]:
  119. """Log in to W&B.
  120. Arguments are the same as for wandb.login() with the following additions:
  121. Args:
  122. update_api_key: If true and an explicit API key is given, it will be
  123. saved to the .netrc file.
  124. _silent: If true, will not print any messages to the console.
  125. Returns:
  126. A pair (is_successful, key).
  127. """
  128. settings = wandb_setup.singleton().settings
  129. if host is None:
  130. host_url = wbauth.HostUrl(settings.base_url, app_url=settings.app_url)
  131. else:
  132. host_url = wbauth.HostUrl(host)
  133. if relogin is None:
  134. relogin = settings.relogin
  135. if force is None:
  136. force = settings.force
  137. if timeout is None:
  138. timeout = settings.login_timeout
  139. if _silent is None:
  140. _silent = settings.silent
  141. if wandb.util._is_kaggle() and not wandb.util._has_internet():
  142. term.termerror(
  143. "To use W&B in kaggle you must enable internet in the settings"
  144. + " panel on the right."
  145. )
  146. return False, None
  147. if key:
  148. auth: wbauth.Auth | None = _use_explicit_key(
  149. key,
  150. host=host_url,
  151. settings=settings,
  152. update_api_key=update_api_key,
  153. silent=_silent,
  154. )
  155. else:
  156. auth = _find_or_prompt_for_key(
  157. settings,
  158. host=host_url,
  159. force=force,
  160. relogin=relogin,
  161. referrer=referrer,
  162. input_timeout=timeout,
  163. )
  164. if verify and isinstance(auth, wbauth.AuthApiKey):
  165. _verify_login(key=auth.api_key, base_url=auth.host.url)
  166. wandb_setup.singleton().update_user_settings()
  167. if not _silent:
  168. _print_logged_in_message(settings, host=str(host_url))
  169. if auth is None:
  170. return False, None
  171. elif isinstance(auth, wbauth.AuthApiKey):
  172. return True, auth.api_key
  173. else:
  174. return True, None
  175. def _use_explicit_key(
  176. key: str,
  177. settings: wandb.Settings,
  178. *,
  179. host: wbauth.HostUrl,
  180. update_api_key: bool,
  181. silent: bool,
  182. ) -> wbauth.Auth:
  183. """Log in with an explicit key.
  184. Same arguments as `_login()`.
  185. """
  186. if settings._notebook and not silent:
  187. term.termwarn(
  188. "If you're specifying your api key in code, ensure this"
  189. + " code is not shared publicly."
  190. + "\nConsider setting the WANDB_API_KEY environment variable,"
  191. + " or running `wandb login` from the command line."
  192. )
  193. auth = wbauth.AuthApiKey(host=host, api_key=key)
  194. wbauth.use_explicit_auth(auth, source="wandb.login()")
  195. if update_api_key:
  196. try:
  197. wbauth.write_netrc_auth(
  198. host=auth.host.url,
  199. api_key=auth.api_key,
  200. )
  201. except wbauth.WriteNetrcError as e:
  202. wandb.termwarn(str(e))
  203. return auth
  204. def _find_or_prompt_for_key(
  205. settings: wandb.Settings,
  206. *,
  207. host: wbauth.HostUrl,
  208. force: bool,
  209. relogin: bool,
  210. referrer: str,
  211. input_timeout: float | None,
  212. ) -> wbauth.Auth | None:
  213. """Log in without an explicit key.
  214. Same arguments as `_login()`.
  215. """
  216. timed_out = False
  217. auth: wbauth.Auth | None = None
  218. try:
  219. auth = wbauth.authenticate_session(
  220. host=host,
  221. source="wandb.login()",
  222. no_offline=force,
  223. no_create=force,
  224. referrer=referrer,
  225. input_timeout=input_timeout,
  226. relogin=relogin,
  227. )
  228. except TimeoutError:
  229. timed_out = True
  230. if not auth:
  231. if timed_out:
  232. term.termwarn("W&B disabled due to login timeout.")
  233. settings.mode = "disabled"
  234. else:
  235. term.termlog("Using W&B in offline mode.")
  236. settings.mode = "offline"
  237. return auth
  238. def _verify_login(key: str, base_url: str) -> None:
  239. from requests.exceptions import ConnectionError
  240. api = InternalApi(
  241. api_key=key,
  242. default_settings={"base_url": base_url},
  243. )
  244. try:
  245. is_api_key_valid = api.validate_api_key()
  246. except ConnectionError as e:
  247. raise AuthenticationError(
  248. f"Unable to connect to {base_url} to verify API token."
  249. ) from e
  250. except Exception as e:
  251. raise AuthenticationError(
  252. "An error occurred while verifying the API key."
  253. ) from e
  254. if not is_api_key_valid:
  255. raise AuthenticationError(
  256. f"API key verification failed for host {base_url}."
  257. + " Make sure your API key is valid."
  258. )
  259. def _print_logged_in_message(settings: wandb.Settings, *, host: str) -> None:
  260. """Print a message telling the user they are logged in."""
  261. singleton = wandb_setup.singleton()
  262. username = singleton._get_username()
  263. if username:
  264. host_str = f" to {click.style(host, fg='green')}" if host else ""
  265. # check to see if we got an entity from the setup call or from the user
  266. entity = settings.entity or singleton._get_entity()
  267. entity_str = ""
  268. # check if entity exist, valid (is part of a certain team) and different from the username
  269. if entity and entity in singleton._get_teams() and entity != username:
  270. entity_str = f" ({click.style(entity, fg='yellow')})"
  271. login_state_str = f"Currently logged in as: {click.style(username, fg='yellow')}{entity_str}{host_str}"
  272. else:
  273. login_state_str = "W&B API key is configured"
  274. login_info_str = (
  275. f"Use {click.style('`wandb login --relogin`', bold=True)} to force relogin"
  276. )
  277. term.termlog(
  278. f"{login_state_str}. {login_info_str}",
  279. repeat=False,
  280. )