wandb 0.19.4__py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.19.5__py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.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.4"
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.4"
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/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."""
@@ -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)
@@ -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
 
wandb/sdk/lib/mailbox.py CHANGED
@@ -341,16 +341,6 @@ class Mailbox:
341
341
  def enable_keepalive(self) -> None:
342
342
  self._keepalive = True
343
343
 
344
- def wait(
345
- self,
346
- handle: MailboxHandle,
347
- *,
348
- timeout: float,
349
- on_progress: Optional[Callable[[MailboxProgress], None]] = None,
350
- cancel: bool = False,
351
- ) -> Optional[pb.Result]:
352
- return handle.wait(timeout=timeout, on_progress=on_progress, cancel=cancel)
353
-
354
344
  def _time(self) -> float:
355
345
  return time.monotonic()
356
346
 
wandb/sdk/lib/server.py CHANGED
@@ -25,6 +25,7 @@ class Server:
25
25
  def query_with_timeout(self, timeout: int | float = 5) -> None:
26
26
  if self._settings.x_disable_viewer:
27
27
  return
28
+
28
29
  async_viewer = util.async_call(self._api.viewer_server_info, timeout=timeout)
29
30
  try:
30
31
  viewer_tuple, viewer_thread = async_viewer()
@@ -36,3 +37,22 @@ class Server:
36
37
  # TODO(jhr): should we kill the thread?
37
38
  self._viewer, self._serverinfo = viewer_tuple
38
39
  self._flags = json.loads(self._viewer.get("flags", "{}"))
40
+
41
+ @property
42
+ def viewer(self) -> dict[str, Any]:
43
+ """Returns information about the currently authenticated user.
44
+
45
+ If the API key is valid, the following is returned:
46
+ - id
47
+ - entity
48
+ - username
49
+ - flags
50
+ - teams
51
+
52
+ If the API key is not valid or the server is not reachable,
53
+ an empty dict is returned.
54
+ """
55
+ if not self._viewer and not self._settings._offline:
56
+ self.query_with_timeout()
57
+
58
+ return self._viewer