prompt.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. from __future__ import annotations
  2. import logging
  3. from urllib.parse import urlsplit, urlunsplit
  4. from wandb import util
  5. from wandb.errors import links, term
  6. from . import saas, validation, wbnetrc
  7. from .host_url import HostUrl
  8. _logger = logging.getLogger(__name__)
  9. _LOGIN_CHOICE_NEW = "Create a W&B account"
  10. _LOGIN_CHOICE_EXISTS = "Use an existing W&B account"
  11. _LOGIN_CHOICE_OFFLINE = "Don't visualize my results"
  12. _LOGIN_CHOICES = [
  13. _LOGIN_CHOICE_NEW,
  14. _LOGIN_CHOICE_EXISTS,
  15. _LOGIN_CHOICE_OFFLINE,
  16. ]
  17. def prompt_and_save_api_key(
  18. *,
  19. host: str | HostUrl,
  20. no_offline: bool = False,
  21. no_create: bool = False,
  22. referrer: str = "",
  23. input_timeout: float | None = None,
  24. ) -> str | None:
  25. """Prompt for an API key and save it to the .netrc file.
  26. Args:
  27. host: The URL to the W&B server, like 'https://api.wandb.ai'.
  28. no_offline: If true, do not show an option to skip logging in.
  29. no_create: If true, do not show an option to create a new account.
  30. referrer: A referrer string to tack on as a query parameter to
  31. the printed URL for analytics.
  32. input_timeout: How long to wait for user input before timing out.
  33. Returns:
  34. Either the resulting API key or None if the user selected offline mode.
  35. Raises:
  36. NotATerminalError: If a terminal is not available.
  37. TimeoutError: If the specified timeout expired.
  38. """
  39. if not isinstance(host, HostUrl):
  40. host = HostUrl(host)
  41. api_key = _prompt_api_key(
  42. host=host,
  43. no_offline=no_offline,
  44. no_create=no_create,
  45. referrer=referrer,
  46. input_timeout=input_timeout,
  47. )
  48. if not api_key:
  49. return None
  50. wbnetrc.write_netrc_auth(host=host.url, api_key=api_key)
  51. return api_key
  52. def _prompt_api_key(
  53. *,
  54. host: HostUrl,
  55. no_offline: bool = False,
  56. no_create: bool = False,
  57. referrer: str = "",
  58. input_timeout: float | None = None,
  59. ) -> str | None:
  60. """Prompt for an API key without saving it to .netrc.
  61. Arguments are the same as for prompt_and_save_api_key().
  62. """
  63. if not term.can_use_terminput():
  64. raise term.NotATerminalError
  65. choices = list(_LOGIN_CHOICES)
  66. if no_offline:
  67. choices.remove(_LOGIN_CHOICE_OFFLINE)
  68. if no_create:
  69. choices.remove(_LOGIN_CHOICE_NEW)
  70. while True:
  71. choice = util.prompt_choices(choices, input_timeout=input_timeout)
  72. if choice == _LOGIN_CHOICE_NEW:
  73. key = _create_new_account(host=host, referrer=referrer)
  74. if problems := validation.check_api_key(key):
  75. term.termerror(f"Invalid API key: {problems}")
  76. else:
  77. return key
  78. elif choice == _LOGIN_CHOICE_EXISTS:
  79. key = _use_existing_account(host=host, referrer=referrer)
  80. if problems := validation.check_api_key(key):
  81. term.termerror(f"Invalid API key: {problems}")
  82. else:
  83. return key
  84. elif choice == _LOGIN_CHOICE_OFFLINE:
  85. return None
  86. else:
  87. term.termerror("Not implemented. Please select another choice.")
  88. def _use_existing_account(host: HostUrl, referrer: str) -> str:
  89. """Prompt the user to paste an API key from an existing W&B account.
  90. Args:
  91. host: The W&B server URL.
  92. referrer: The referrer to add to the printed URL, if any.
  93. Returns:
  94. The API key entered by the user.
  95. """
  96. if saas.is_wandb_domain(host.url):
  97. help_url = links.url_registry.url("wandb-server")
  98. term.termlog(
  99. f"Logging into {host}. "
  100. + f"(Learn how to deploy a W&B server locally: {help_url})"
  101. )
  102. auth_url = _authorize_url(host, signup=False, referrer=referrer)
  103. term.termlog(f"Create a new API key at: {auth_url}")
  104. term.termlog("Store your API key securely and do not share it.")
  105. return term.terminput(
  106. "Paste your API key and hit enter: ",
  107. hide=True,
  108. )
  109. def _create_new_account(host: HostUrl, referrer: str) -> str:
  110. """Prompt the user to create a new W&B account.
  111. Args:
  112. host: The W&B server URL.
  113. referrer: The referrer to add to the printed URL, if any.
  114. Returns:
  115. The API key entered by the user.
  116. """
  117. url = _authorize_url(host, signup=True, referrer=referrer)
  118. term.termlog(f"Create an account here: {url}")
  119. term.termlog(
  120. "After creating your account, create a new API key and store it securely."
  121. )
  122. return term.terminput(
  123. "Paste your API key and hit enter: ",
  124. hide=True,
  125. )
  126. def _authorize_url(host: HostUrl, *, signup: bool, referrer: str) -> str:
  127. """Returns the URL for the web page showing the user's API key.
  128. Args:
  129. host: The W&B server URL.
  130. signup: If true, shows a signup page.
  131. referrer: The referrer to add to the URL, if any.
  132. """
  133. scheme, netloc, *_ = urlsplit(host.app_url, scheme="https")
  134. query_parts: list[str] = []
  135. if signup:
  136. query_parts.append("signup=true")
  137. if referrer:
  138. query_parts.append(f"ref={referrer}")
  139. query = "&".join(query_parts)
  140. return urlunsplit((scheme, netloc, "authorize", query, ""))