wandb 0.19.4rc1__py3-none-macosx_11_0_arm64.whl → 0.19.5__py3-none-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wandb/__init__.py CHANGED
@@ -10,7 +10,7 @@ For reference documentation, see https://docs.wandb.com/ref/python.
10
10
  """
11
11
  from __future__ import annotations
12
12
 
13
- __version__ = "0.19.4rc1"
13
+ __version__ = "0.19.5"
14
14
 
15
15
 
16
16
  from wandb.errors import Error
wandb/__init__.pyi CHANGED
@@ -103,7 +103,7 @@ if TYPE_CHECKING:
103
103
  import wandb
104
104
  from wandb.plot import CustomChart
105
105
 
106
- __version__: str = "0.19.4rc1"
106
+ __version__: str = "0.19.5"
107
107
 
108
108
  run: Run | None
109
109
  config: wandb_config.Config
@@ -595,7 +595,6 @@ def log(
595
595
  [our guides to logging](https://docs.wandb.com/guides/track/log).
596
596
 
597
597
  ### Basic usage
598
- <!--yeadoc-test:init-and-log-basic-->
599
598
  ```python
600
599
  import wandb
601
600
 
@@ -604,7 +603,6 @@ def log(
604
603
  ```
605
604
 
606
605
  ### Incremental logging
607
- <!--yeadoc-test:init-and-log-incremental-->
608
606
  ```python
609
607
  import wandb
610
608
 
@@ -615,7 +613,6 @@ def log(
615
613
  ```
616
614
 
617
615
  ### Histogram
618
- <!--yeadoc-test:init-and-log-histogram-->
619
616
  ```python
620
617
  import numpy as np
621
618
  import wandb
@@ -627,7 +624,6 @@ def log(
627
624
  ```
628
625
 
629
626
  ### Image from numpy
630
- <!--yeadoc-test:init-and-log-image-numpy-->
631
627
  ```python
632
628
  import numpy as np
633
629
  import wandb
@@ -642,7 +638,6 @@ def log(
642
638
  ```
643
639
 
644
640
  ### Image from PIL
645
- <!--yeadoc-test:init-and-log-image-pillow-->
646
641
  ```python
647
642
  import numpy as np
648
643
  from PIL import Image as PILImage
@@ -664,7 +659,6 @@ def log(
664
659
  ```
665
660
 
666
661
  ### Video from numpy
667
- <!--yeadoc-test:init-and-log-video-numpy-->
668
662
  ```python
669
663
  import numpy as np
670
664
  import wandb
@@ -681,7 +675,6 @@ def log(
681
675
  ```
682
676
 
683
677
  ### Matplotlib Plot
684
- <!--yeadoc-test:init-and-log-matplotlib-->
685
678
  ```python
686
679
  from matplotlib import pyplot as plt
687
680
  import numpy as np
wandb/_iterutils.py ADDED
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterable, TypeVar
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ def one(
9
+ iterable: Iterable[T],
10
+ too_short: Exception | None = None,
11
+ too_long: Exception | None = None,
12
+ ) -> T:
13
+ """Return the only item in the iterable.
14
+
15
+ Note:
16
+ This is intended **only** as an internal helper/convenience function,
17
+ and its implementation is directly adapted from `more_itertools.one`.
18
+ Users needing similar functionality are strongly encouraged to use
19
+ that library instead:
20
+ https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.one
21
+
22
+ Args:
23
+ iterable: The iterable to get the only item from.
24
+ too_short: Custom exception to raise if the iterable has no items.
25
+ too_long: Custom exception to raise if the iterable has multiple items.
26
+
27
+ Raises:
28
+ ValueError or `too_short`: If the iterable has no items.
29
+ ValueError or `too_long`: If the iterable has multiple items.
30
+ """
31
+ # For a general iterable, avoid inadvertently iterating through all values,
32
+ # which may be costly or impossible (e.g. if infinite). Only check that:
33
+
34
+ # ... the first item exists
35
+ it = iter(iterable)
36
+ try:
37
+ obj = next(it)
38
+ except StopIteration as e:
39
+ raise too_short or ValueError("Expected 1 item in iterable, got 0") from e
40
+
41
+ # ...the second item doesn't
42
+ try:
43
+ _ = next(it)
44
+ except StopIteration:
45
+ return obj
46
+ raise too_long or ValueError("Expected 1 item in iterable, got multiple")
wandb/apis/internal.py CHANGED
@@ -63,6 +63,10 @@ class Api:
63
63
  def git(self):
64
64
  return self.api.git
65
65
 
66
+ def validate_api_key(self) -> bool:
67
+ """Returns whether the API key stored on initialization is valid."""
68
+ return self.api.validate_api_key()
69
+
66
70
  def file_current(self, *args):
67
71
  return self.api.file_current(*args)
68
72
 
wandb/bin/gpu_stats CHANGED
Binary file
wandb/bin/wandb-core CHANGED
Binary file
wandb/cli/cli.py CHANGED
@@ -217,12 +217,19 @@ def projects(entity, display=True):
217
217
  @cli.command(context_settings=CONTEXT, help="Login to Weights & Biases")
218
218
  @click.argument("key", nargs=-1)
219
219
  @click.option("--cloud", is_flag=True, help="Login to the cloud instead of local")
220
- @click.option("--host", default=None, help="Login to a specific instance of W&B")
220
+ @click.option(
221
+ "--host", "--base-url", default=None, help="Login to a specific instance of W&B"
222
+ )
221
223
  @click.option(
222
224
  "--relogin", default=None, is_flag=True, help="Force relogin if already logged in."
223
225
  )
224
226
  @click.option("--anonymously", default=False, is_flag=True, help="Log in anonymously")
225
- @click.option("--verify", default=False, is_flag=True, help="Verify login credentials")
227
+ @click.option(
228
+ "--verify/--no-verify",
229
+ default=False,
230
+ is_flag=True,
231
+ help="Verify login credentials",
232
+ )
226
233
  @display_error
227
234
  def login(key, host, cloud, relogin, anonymously, verify, no_offline=False):
228
235
  # TODO: move CLI to wandb-core backend
@@ -19,7 +19,7 @@ from datetime import datetime, timedelta
19
19
  from functools import partial
20
20
  from pathlib import PurePosixPath
21
21
  from typing import IO, TYPE_CHECKING, Any, Dict, Iterator, Literal, Sequence, Type, cast
22
- from urllib.parse import urlparse
22
+ from urllib.parse import quote, urljoin, urlparse
23
23
 
24
24
  import requests
25
25
 
@@ -32,6 +32,7 @@ from wandb.errors.term import termerror, termlog, termwarn
32
32
  from wandb.sdk.artifacts._validators import (
33
33
  ensure_logged,
34
34
  ensure_not_finalized,
35
+ is_artifact_registry_project,
35
36
  validate_aliases,
36
37
  validate_tags,
37
38
  )
@@ -532,6 +533,91 @@ class Artifact:
532
533
  """The artifact's type. Common types include `dataset` or `model`."""
533
534
  return self._type
534
535
 
536
+ @property
537
+ @ensure_logged
538
+ def url(self) -> str:
539
+ """
540
+ Constructs the URL of the artifact.
541
+
542
+ Returns:
543
+ str: The URL of the artifact.
544
+ """
545
+ try:
546
+ base_url = self._client.app_url # type: ignore[union-attr]
547
+ except AttributeError:
548
+ return ""
549
+
550
+ if self.collection.is_sequence():
551
+ return self._construct_standard_url(base_url)
552
+ if is_artifact_registry_project(self.project):
553
+ return self._construct_registry_url(base_url)
554
+ if self._type == "model" or self.project == "model-registry":
555
+ return self._construct_model_registry_url(base_url)
556
+ return self._construct_standard_url(base_url)
557
+
558
+ def _construct_standard_url(self, base_url: str) -> str:
559
+ if not all(
560
+ [
561
+ base_url,
562
+ self.entity,
563
+ self.project,
564
+ self._type,
565
+ self.collection.name,
566
+ self._version,
567
+ ]
568
+ ):
569
+ return ""
570
+ return urljoin(
571
+ base_url,
572
+ f"{self.entity}/{self.project}/artifacts/{quote(self._type)}/{quote(self.collection.name)}/{self._version}",
573
+ )
574
+
575
+ def _construct_registry_url(self, base_url: str) -> str:
576
+ if not all(
577
+ [
578
+ base_url,
579
+ self.entity,
580
+ self.project,
581
+ self.collection.name,
582
+ self._version,
583
+ ]
584
+ ):
585
+ return ""
586
+
587
+ try:
588
+ org, *_ = InternalApi()._fetch_orgs_and_org_entities_from_entity(
589
+ self.entity
590
+ )
591
+ except ValueError:
592
+ return ""
593
+
594
+ selection_path = quote(
595
+ f"{self.entity}/{self.project}/{self.collection.name}", safe=""
596
+ )
597
+ return urljoin(
598
+ base_url,
599
+ f"orgs/{org.display_name}/registry/{self._type}?selectionPath={selection_path}&view=membership&version={self._version}",
600
+ )
601
+
602
+ def _construct_model_registry_url(self, base_url: str) -> str:
603
+ if not all(
604
+ [
605
+ base_url,
606
+ self.entity,
607
+ self.project,
608
+ self.collection.name,
609
+ self._version,
610
+ ]
611
+ ):
612
+ return ""
613
+ selection_path = quote(
614
+ f"{self.entity}/{self.project}/{self.collection.name}", safe=""
615
+ )
616
+ return urljoin(
617
+ base_url,
618
+ f"{self.entity}/registry/model?selectionPath={selection_path}&view=membership&version={self._version}",
619
+ )
620
+
535
621
  @property
536
622
  def description(self) -> str | None:
537
623
  """A description of the artifact."""
@@ -25,8 +25,6 @@ if TYPE_CHECKING:
25
25
  from wandb.proto.wandb_internal_pb2 import Record, Result
26
26
  from wandb.sdk.lib import service_connection
27
27
 
28
- from ..wandb_run import Run
29
-
30
28
  RecordQueue = Union["queue.Queue[Record]", multiprocessing.Queue[Record]]
31
29
  ResultQueue = Union["queue.Queue[Result]", multiprocessing.Queue[Result]]
32
30
 
@@ -54,7 +52,7 @@ class Backend:
54
52
  interface: Optional[InterfaceBase]
55
53
  _internal_pid: Optional[int]
56
54
  wandb_process: Optional[multiprocessing.process.BaseProcess]
57
- _settings: Optional[Settings]
55
+ _settings: Settings
58
56
  record_q: Optional["RecordQueue"]
59
57
  result_q: Optional["ResultQueue"]
60
58
  _mailbox: Mailbox
@@ -62,7 +60,7 @@ class Backend:
62
60
  def __init__(
63
61
  self,
64
62
  mailbox: Mailbox,
65
- settings: Optional[Settings] = None,
63
+ settings: Settings,
66
64
  log_level: Optional[int] = None,
67
65
  service: "Optional[service_connection.ServiceConnection]" = None,
68
66
  ) -> None:
@@ -84,12 +82,7 @@ class Backend:
84
82
  self._save_mod_path: Optional[str] = None
85
83
  self._save_mod_spec = None
86
84
 
87
- def _hack_set_run(self, run: "Run") -> None:
88
- assert self.interface
89
- self.interface._hack_set_run(run)
90
-
91
85
  def _multiprocessing_setup(self) -> None:
92
- assert self._settings
93
86
  if self._settings.start_method == "thread":
94
87
  return
95
88
 
@@ -141,10 +134,13 @@ class Backend:
141
134
  def ensure_launched(self) -> None:
142
135
  """Launch backend worker if not running."""
143
136
  if self._service:
144
- self.interface = self._service.make_interface(self._mailbox)
137
+ assert self._settings.run_id
138
+ self.interface = self._service.make_interface(
139
+ self._mailbox,
140
+ stream_id=self._settings.run_id,
141
+ )
145
142
  return
146
143
 
147
- assert self._settings
148
144
  settings = self._settings.model_copy()
149
145
  settings.x_log_level = self._log_level or logging.DEBUG
150
146
 
@@ -24,16 +24,16 @@ def _server_accepts_client_ids() -> bool:
24
24
  # The latest SDK version that is < "0.11.0" was released on 2021/06/29.
25
25
  # AS OF NOW, 2024/11/06, we assume that all customer's server deployments accept
26
26
  # client IDs.
27
- #
28
- # If there are any users with issues on an older backend, customers can disable the
29
- # setting `allow_offline_artifacts` to revert the SDK's behavior back to not
30
- # using client IDs in offline mode.
31
- if (
32
- util._is_offline()
33
- and wandb.run
34
- and not wandb.run.settings.allow_offline_artifacts
35
- ):
36
- return False
27
+
28
+ if util._is_offline():
29
+ # If there are any users with issues on an older backend, customers can disable the
30
+ # setting `allow_offline_artifacts` to revert the SDK's behavior back to not
31
+ # using client IDs in offline mode.
32
+ if wandb.run and not wandb.run.settings.allow_offline_artifacts:
33
+ return False
34
+ # Assume client IDs are accepted
35
+ else:
36
+ return True
37
37
 
38
38
  # If the script is online, request the max_cli_version and ensure the server
39
39
  # is of a high enough version.
@@ -48,7 +48,7 @@ class BoundingBoxes2D(JSONMetadata):
48
48
 
49
49
  Examples:
50
50
  ### Log bounding boxes for a single image
51
- <!--yeadoc-test:boundingbox-2d-->
51
+
52
52
  ```python
53
53
  import numpy as np
54
54
  import wandb
@@ -94,7 +94,7 @@ class BoundingBoxes2D(JSONMetadata):
94
94
  ```
95
95
 
96
96
  ### Log a bounding box overlay to a Table
97
- <!--yeadoc-test:bb2d-image-with-labels-->
97
+
98
98
  ```python
99
99
  import numpy as np
100
100
  import wandb
@@ -33,7 +33,7 @@ class ImageMask(Media):
33
33
 
34
34
  Examples:
35
35
  ### Logging a single masked image
36
- <!--yeadoc-test:log-image-mask-->
36
+
37
37
  ```python
38
38
  import numpy as np
39
39
  import wandb
@@ -69,7 +69,7 @@ class ImageMask(Media):
69
69
  ```
70
70
 
71
71
  ### Log a masked image inside a Table
72
- <!--yeadoc-test:log-image-mask-table-->
72
+
73
73
  ```python
74
74
  import numpy as np
75
75
  import wandb
@@ -77,7 +77,6 @@ class Image(BatchableMedia):
77
77
 
78
78
  Examples:
79
79
  ### Create a wandb.Image from a numpy array
80
- <!--yeadoc-test:log-image-numpy-->
81
80
  ```python
82
81
  import numpy as np
83
82
  import wandb
@@ -92,7 +91,6 @@ class Image(BatchableMedia):
92
91
  ```
93
92
 
94
93
  ### Create a wandb.Image from a PILImage
95
- <!--yeadoc-test:log-image-pillow-->
96
94
  ```python
97
95
  import numpy as np
98
96
  from PIL import Image as PILImage
@@ -111,7 +109,6 @@ class Image(BatchableMedia):
111
109
  ```
112
110
 
113
111
  ### log .jpg rather than .png (default)
114
- <!--yeadoc-test:log-image-format-->
115
112
  ```python
116
113
  import numpy as np
117
114
  import wandb
@@ -148,12 +148,8 @@ def val_to_json(
148
148
  "partitioned-table",
149
149
  "joined-table",
150
150
  ]:
151
- # Special conditional to log tables as artifact entries as well.
152
- # I suspect we will generalize this as we transition to storing all
153
- # files in an artifact
154
- # we sanitize the key to meet the constraints
155
- # in this case, leaving only alphanumerics or underscores.
156
- sanitized_key = re.sub(r"[^a-zA-Z0-9_]+", "", key)
151
+ # Sanitize the key to meet the constraints of artifact names.
152
+ sanitized_key = re.sub(r"[^a-zA-Z0-9_\-.]+", "", key)
157
153
  art = wandb.Artifact(f"run-{run.id}-{sanitized_key}", "run_table")
158
154
  art.add(val, key)
159
155
  run.log_artifact(art)
@@ -90,16 +90,11 @@ def file_enum_to_policy(enum: "pb.FilesItem.PolicyType.V") -> "PolicyName":
90
90
 
91
91
 
92
92
  class InterfaceBase:
93
- _run: Optional["Run"]
94
93
  _drop: bool
95
94
 
96
95
  def __init__(self) -> None:
97
- self._run = None
98
96
  self._drop = False
99
97
 
100
- def _hack_set_run(self, run: "Run") -> None:
101
- self._run = run
102
-
103
98
  def publish_header(self) -> None:
104
99
  header = pb.HeaderRecord()
105
100
  self._publish_header(header)
@@ -232,7 +227,12 @@ class InterfaceBase:
232
227
  update.value_json = json.dumps(v)
233
228
  return summary
234
229
 
235
- def _summary_encode(self, value: Any, path_from_root: str) -> dict:
230
+ def _summary_encode(
231
+ self,
232
+ value: Any,
233
+ path_from_root: str,
234
+ run: "Run",
235
+ ) -> dict:
236
236
  """Normalize, compress, and encode sub-objects for backend storage.
237
237
 
238
238
  value: Object to encode.
@@ -250,12 +250,14 @@ class InterfaceBase:
250
250
  json_value = {}
251
251
  for key, value in value.items(): # noqa: B020
252
252
  json_value[key] = self._summary_encode(
253
- value, path_from_root + "." + key
253
+ value,
254
+ path_from_root + "." + key,
255
+ run=run,
254
256
  )
255
257
  return json_value
256
258
  else:
257
259
  friendly_value, converted = json_friendly(
258
- val_to_json(self._run, path_from_root, value, namespace="summary")
260
+ val_to_json(run, path_from_root, value, namespace="summary")
259
261
  )
260
262
  json_value, compressed = maybe_compress_summary(
261
263
  friendly_value, get_h5_typename(value)
@@ -267,7 +269,11 @@ class InterfaceBase:
267
269
 
268
270
  return json_value
269
271
 
270
- def _make_summary(self, summary_record: sr.SummaryRecord) -> pb.SummaryRecord:
272
+ def _make_summary(
273
+ self,
274
+ summary_record: sr.SummaryRecord,
275
+ run: "Run",
276
+ ) -> pb.SummaryRecord:
271
277
  pb_summary_record = pb.SummaryRecord()
272
278
 
273
279
  for item in summary_record.update:
@@ -282,7 +288,11 @@ class InterfaceBase:
282
288
  pb_summary_item.key = item.key[0]
283
289
 
284
290
  path_from_root = ".".join(item.key)
285
- json_value = self._summary_encode(item.value, path_from_root)
291
+ json_value = self._summary_encode(
292
+ item.value,
293
+ path_from_root,
294
+ run=run,
295
+ )
286
296
  json_value, _ = json_friendly(json_value) # type: ignore
287
297
 
288
298
  pb_summary_item.value_json = json.dumps(
@@ -303,8 +313,12 @@ class InterfaceBase:
303
313
 
304
314
  return pb_summary_record
305
315
 
306
- def publish_summary(self, summary_record: sr.SummaryRecord) -> None:
307
- pb_summary_record = self._make_summary(summary_record)
316
+ def publish_summary(
317
+ self,
318
+ run: "Run",
319
+ summary_record: sr.SummaryRecord,
320
+ ) -> None:
321
+ pb_summary_record = self._make_summary(summary_record, run=run)
308
322
  self._publish_summary(pb_summary_record)
309
323
 
310
324
  @abstractmethod
@@ -16,32 +16,28 @@ from .router_sock import MessageSockRouter
16
16
  if TYPE_CHECKING:
17
17
  from wandb.proto import wandb_internal_pb2 as pb
18
18
 
19
- from ..wandb_run import Run
20
-
21
19
 
22
20
  logger = logging.getLogger("wandb")
23
21
 
24
22
 
25
23
  class InterfaceSock(InterfaceShared):
26
- _stream_id: Optional[str]
27
- _sock_client: SockClient
28
24
  _mailbox: Mailbox
29
25
 
30
- def __init__(self, sock_client: SockClient, mailbox: Mailbox) -> None:
26
+ def __init__(
27
+ self,
28
+ sock_client: SockClient,
29
+ mailbox: Mailbox,
30
+ stream_id: str,
31
+ ) -> None:
31
32
  # _sock_client is used when abstract method _init_router() is called by constructor
32
33
  self._sock_client = sock_client
33
34
  super().__init__(mailbox=mailbox)
34
35
  self._process_check = False
35
- self._stream_id = None
36
+ self._stream_id = stream_id
36
37
 
37
38
  def _init_router(self) -> None:
38
39
  self._router = MessageSockRouter(self._sock_client, mailbox=self._mailbox)
39
40
 
40
- def _hack_set_run(self, run: "Run") -> None:
41
- super()._hack_set_run(run)
42
- assert run._settings.run_id
43
- self._stream_id = run._settings.run_id
44
-
45
41
  def _assign(self, record: Any) -> None:
46
42
  assert self._stream_id
47
43
  record._info.stream_id = self._stream_id
@@ -243,6 +243,7 @@ class Api:
243
243
  ),
244
244
  environ: MutableMapping = os.environ,
245
245
  retry_callback: Optional[Callable[[int, str], Any]] = None,
246
+ api_key: Optional[str] = None,
246
247
  ) -> None:
247
248
  self._environ = environ
248
249
  self._global_context = context.Context()
@@ -284,7 +285,9 @@ class Api:
284
285
  self._extra_http_headers.update(_thread_local_api_settings.headers or {})
285
286
 
286
287
  auth = None
287
- if self.access_token is not None:
288
+ if api_key:
289
+ auth = ("api", api_key)
290
+ elif self.access_token is not None:
288
291
  self._extra_http_headers["Authorization"] = f"Bearer {self.access_token}"
289
292
  elif _thread_local_api_settings.cookies is None:
290
293
  auth = ("api", self.api_key or "")
@@ -400,6 +403,11 @@ class Api:
400
403
  wandb.termerror(f"Error while calling W&B API: {error} ({response})")
401
404
  raise
402
405
 
406
+ def validate_api_key(self) -> bool:
407
+ """Returns whether the API key stored on initialization is valid."""
408
+ res = self.execute(gql("query { viewer { id } }"))
409
+ return res is not None and res["viewer"] is not None
410
+
403
411
  def set_current_run_id(self, run_id: str) -> None:
404
412
  self._current_run_id = run_id
405
413
 
@@ -118,7 +118,7 @@ class CPU:
118
118
  self.name: str = self.__class__.__name__.lower()
119
119
  self.metrics: List[Metric] = [
120
120
  ProcessCpuPercent(settings.x_stats_pid),
121
- CpuPercent(),
121
+ # CpuPercent(),
122
122
  ProcessCpuThreads(settings.x_stats_pid),
123
123
  ]
124
124
  self.metrics_monitor: MetricsMonitor = MetricsMonitor(
wandb/sdk/lib/apikey.py CHANGED
@@ -104,7 +104,6 @@ def prompt_api_key( # noqa: C901
104
104
  log_string = term.LOG_STRING_NOCOLOR
105
105
  key = wandb.jupyter.attempt_colab_login(app_url) # type: ignore
106
106
  if key is not None:
107
- write_key(settings, key, api=api)
108
107
  return key # type: ignore
109
108
 
110
109
  if anon_mode == "must":
@@ -123,24 +122,19 @@ def prompt_api_key( # noqa: C901
123
122
  choices, input_timeout=settings.login_timeout, jupyter=jupyter
124
123
  )
125
124
 
125
+ key = None
126
126
  api_ask = (
127
127
  f"{log_string}: Paste an API key from your profile and hit enter, "
128
128
  "or press ctrl+c to quit"
129
129
  )
130
130
  if result == LOGIN_CHOICE_ANON:
131
131
  key = api.create_anonymous_api_key()
132
-
133
- write_key(settings, key, api=api, anonymous=True)
134
- return key # type: ignore
135
132
  elif result == LOGIN_CHOICE_NEW:
136
133
  key = browser_callback(signup=True) if browser_callback else None
137
134
 
138
135
  if not key:
139
136
  wandb.termlog(f"Create an account here: {app_url}/authorize?signup=true")
140
137
  key = input_callback(api_ask).strip()
141
-
142
- write_key(settings, key, api=api)
143
- return key # type: ignore
144
138
  elif result == LOGIN_CHOICE_EXISTS:
145
139
  key = browser_callback() if browser_callback else None
146
140
 
@@ -158,8 +152,6 @@ def prompt_api_key( # noqa: C901
158
152
  f"You can find your API key in your browser here: {app_url}/authorize"
159
153
  )
160
154
  key = input_callback(api_ask).strip()
161
- write_key(settings, key, api=api)
162
- return key # type: ignore
163
155
  elif result == LOGIN_CHOICE_NOTTY:
164
156
  # TODO: Needs refactor as this needs to be handled by caller
165
157
  return False
@@ -172,8 +164,9 @@ def prompt_api_key( # noqa: C901
172
164
  browser_callback() if jupyter and browser_callback else (None, False)
173
165
  )
174
166
 
175
- write_key(settings, key, api=api)
176
- return key # type: ignore
167
+ if not key:
168
+ raise ValueError("No API key specified.")
169
+ return key
177
170
 
178
171
 
179
172
  def write_netrc(host: str, entity: str, key: str) -> Optional[bool]:
@@ -232,7 +225,6 @@ def write_key(
232
225
  settings: "Settings",
233
226
  key: Optional[str],
234
227
  api: Optional["InternalApi"] = None,
235
- anonymous: bool = False,
236
228
  ) -> None:
237
229
  if not key:
238
230
  raise ValueError("No API key specified.")
@@ -249,11 +241,6 @@ def write_key(
249
241
  "API key must be 40 characters long, yours was {}".format(len(key))
250
242
  )
251
243
 
252
- if anonymous:
253
- api.set_setting("anonymous", "must", globally=True, persist=True)
254
- else:
255
- api.clear_setting("anonymous", globally=True, persist=True)
256
-
257
244
  write_netrc(settings.base_url, "user", key)
258
245
 
259
246