wandb 0.19.4rc1__py3-none-win32.whl → 0.19.6rc4__py3-none-win32.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.
Files changed (47) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +1 -8
  3. wandb/_iterutils.py +46 -0
  4. wandb/apis/internal.py +4 -0
  5. wandb/apis/normalize.py +13 -5
  6. wandb/bin/gpu_stats.exe +0 -0
  7. wandb/bin/wandb-core +0 -0
  8. wandb/cli/cli.py +9 -2
  9. wandb/proto/v3/wandb_internal_pb2.py +36 -36
  10. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  11. wandb/proto/v4/wandb_internal_pb2.py +36 -36
  12. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  13. wandb/proto/v5/wandb_internal_pb2.py +36 -36
  14. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  15. wandb/sdk/artifacts/artifact.py +120 -8
  16. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +12 -5
  17. wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +1 -1
  18. wandb/sdk/backend/backend.py +7 -11
  19. wandb/sdk/data_types/base_types/wb_value.py +10 -10
  20. wandb/sdk/data_types/helper_types/bounding_boxes_2d.py +2 -2
  21. wandb/sdk/data_types/helper_types/image_mask.py +2 -2
  22. wandb/sdk/data_types/image.py +0 -3
  23. wandb/sdk/data_types/saved_model.py +1 -1
  24. wandb/sdk/data_types/utils.py +2 -6
  25. wandb/sdk/interface/interface.py +26 -12
  26. wandb/sdk/interface/interface_sock.py +7 -11
  27. wandb/sdk/internal/internal_api.py +9 -1
  28. wandb/sdk/internal/sender.py +2 -2
  29. wandb/sdk/internal/system/assets/cpu.py +1 -1
  30. wandb/sdk/lib/apikey.py +7 -19
  31. wandb/sdk/lib/mailbox.py +0 -14
  32. wandb/sdk/lib/retry.py +6 -3
  33. wandb/sdk/lib/run_moment.py +19 -7
  34. wandb/sdk/lib/server.py +20 -0
  35. wandb/sdk/lib/service_connection.py +2 -2
  36. wandb/sdk/wandb_init.py +71 -46
  37. wandb/sdk/wandb_login.py +86 -110
  38. wandb/sdk/wandb_metadata.py +60 -31
  39. wandb/sdk/wandb_run.py +32 -45
  40. wandb/sdk/wandb_settings.py +465 -143
  41. wandb/sdk/wandb_setup.py +10 -22
  42. wandb/util.py +44 -12
  43. {wandb-0.19.4rc1.dist-info → wandb-0.19.6rc4.dist-info}/METADATA +1 -1
  44. {wandb-0.19.4rc1.dist-info → wandb-0.19.6rc4.dist-info}/RECORD +47 -46
  45. {wandb-0.19.4rc1.dist-info → wandb-0.19.6rc4.dist-info}/WHEEL +0 -0
  46. {wandb-0.19.4rc1.dist-info → wandb-0.19.6rc4.dist-info}/entry_points.txt +0 -0
  47. {wandb-0.19.4rc1.dist-info → wandb-0.19.6rc4.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -362,7 +362,7 @@ class _PytorchSavedModel(_PicklingSavedModel["torch.nn.Module"]):
362
362
 
363
363
  @staticmethod
364
364
  def _deserialize(dir_or_file_path: str) -> "torch.nn.Module":
365
- return _get_torch().load(dir_or_file_path)
365
+ return _get_torch().load(dir_or_file_path, weights_only=False)
366
366
 
367
367
  @staticmethod
368
368
  def _validate_obj(obj: Any) -> bool:
@@ -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
 
@@ -52,9 +52,9 @@ from wandb.sdk.lib import (
52
52
  filesystem,
53
53
  proto_util,
54
54
  redirect,
55
+ retry,
55
56
  telemetry,
56
57
  )
57
- from wandb.sdk.lib.mailbox import ContextCancelledError
58
58
  from wandb.sdk.lib.proto_util import message_to_dict
59
59
 
60
60
  if TYPE_CHECKING:
@@ -388,7 +388,7 @@ class SendManager:
388
388
  try:
389
389
  self._api.set_local_context(api_context)
390
390
  send_handler(record)
391
- except ContextCancelledError:
391
+ except retry.RetryCancelledError:
392
392
  logger.debug(f"Record cancelled: {record_type}")
393
393
  self._context_keeper.release(context_id)
394
394
  finally:
@@ -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,20 @@ 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
- f"{log_string}: Paste an API key from your profile and hit enter, "
128
- "or press ctrl+c to quit"
127
+ f"{log_string}: Paste an API key from your profile and hit enter"
128
+ if jupyter
129
+ else f"{log_string}: Paste an API key from your profile and hit enter, or press ctrl+c to quit"
129
130
  )
130
131
  if result == LOGIN_CHOICE_ANON:
131
132
  key = api.create_anonymous_api_key()
132
-
133
- write_key(settings, key, api=api, anonymous=True)
134
- return key # type: ignore
135
133
  elif result == LOGIN_CHOICE_NEW:
136
134
  key = browser_callback(signup=True) if browser_callback else None
137
135
 
138
136
  if not key:
139
137
  wandb.termlog(f"Create an account here: {app_url}/authorize?signup=true")
140
138
  key = input_callback(api_ask).strip()
141
-
142
- write_key(settings, key, api=api)
143
- return key # type: ignore
144
139
  elif result == LOGIN_CHOICE_EXISTS:
145
140
  key = browser_callback() if browser_callback else None
146
141
 
@@ -158,8 +153,6 @@ def prompt_api_key( # noqa: C901
158
153
  f"You can find your API key in your browser here: {app_url}/authorize"
159
154
  )
160
155
  key = input_callback(api_ask).strip()
161
- write_key(settings, key, api=api)
162
- return key # type: ignore
163
156
  elif result == LOGIN_CHOICE_NOTTY:
164
157
  # TODO: Needs refactor as this needs to be handled by caller
165
158
  return False
@@ -172,8 +165,9 @@ def prompt_api_key( # noqa: C901
172
165
  browser_callback() if jupyter and browser_callback else (None, False)
173
166
  )
174
167
 
175
- write_key(settings, key, api=api)
176
- return key # type: ignore
168
+ if not key:
169
+ raise ValueError("No API key specified.")
170
+ return key
177
171
 
178
172
 
179
173
  def write_netrc(host: str, entity: str, key: str) -> Optional[bool]:
@@ -232,7 +226,6 @@ def write_key(
232
226
  settings: "Settings",
233
227
  key: Optional[str],
234
228
  api: Optional["InternalApi"] = None,
235
- anonymous: bool = False,
236
229
  ) -> None:
237
230
  if not key:
238
231
  raise ValueError("No API key specified.")
@@ -249,11 +242,6 @@ def write_key(
249
242
  "API key must be 40 characters long, yours was {}".format(len(key))
250
243
  )
251
244
 
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
245
  write_netrc(settings.base_url, "user", key)
258
246
 
259
247
 
wandb/sdk/lib/mailbox.py CHANGED
@@ -24,10 +24,6 @@ class MailboxError(Error):
24
24
  """Generic Mailbox Exception."""
25
25
 
26
26
 
27
- class ContextCancelledError(MailboxError):
28
- """Context cancelled Exception."""
29
-
30
-
31
27
  class _MailboxWaitAll:
32
28
  _event: threading.Event
33
29
  _lock: threading.Lock
@@ -341,16 +337,6 @@ class Mailbox:
341
337
  def enable_keepalive(self) -> None:
342
338
  self._keepalive = True
343
339
 
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
340
  def _time(self) -> float:
355
341
  return time.monotonic()
356
342
 
wandb/sdk/lib/retry.py CHANGED
@@ -12,10 +12,9 @@ from typing import Any, Awaitable, Callable, Generic, Optional, Tuple, Type, Typ
12
12
  from requests import HTTPError
13
13
 
14
14
  import wandb
15
+ import wandb.errors
15
16
  from wandb.util import CheckRetryFnType
16
17
 
17
- from .mailbox import ContextCancelledError
18
-
19
18
  logger = logging.getLogger(__name__)
20
19
 
21
20
 
@@ -26,6 +25,10 @@ SLEEP_FN = time.sleep
26
25
  SLEEP_ASYNC_FN = asyncio.sleep
27
26
 
28
27
 
28
+ class RetryCancelledError(wandb.errors.Error):
29
+ """A retry did not occur because it was cancelled."""
30
+
31
+
29
32
  class TransientError(Exception):
30
33
  """Exception type designated for errors that may only be temporary.
31
34
 
@@ -191,7 +194,7 @@ class Retry(Generic[_R]):
191
194
  sleep + random.random() * 0.25 * sleep, cancel_event=retry_cancel_event
192
195
  )
193
196
  if cancelled:
194
- raise ContextCancelledError("retry timeout")
197
+ raise RetryCancelledError("retry timeout")
195
198
  sleep *= 2
196
199
  if sleep > self.MAX_SLEEP_SECONDS:
197
200
  sleep = self.MAX_SLEEP_SECONDS
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
- from typing import Literal, Union, cast
4
+ from typing import Literal, cast
3
5
  from urllib import parse
4
6
 
5
7
  _STEP = Literal["_step"]
@@ -7,15 +9,25 @@ _STEP = Literal["_step"]
7
9
 
8
10
  @dataclass
9
11
  class RunMoment:
10
- """A moment in a run."""
12
+ """A moment in a run.
13
+
14
+ Defines a branching point in a finished run to fork or resume from.
15
+ A run moment is identified by a run ID and a metric value.
16
+ Currently, only the metric '_step' is supported.
17
+ """
11
18
 
12
- run: str # run name
19
+ run: str
20
+ """run ID"""
13
21
 
14
- # currently, the _step value to fork from. in future, this will be optional
15
- value: Union[int, float]
22
+ value: int | float
23
+ """Value of the metric."""
16
24
 
17
- # only step for now, in future this will be relaxed to be any metric
18
25
  metric: _STEP = "_step"
26
+ """Metric to use to determine the moment in the run.
27
+
28
+ Currently, only the metric '_step' is supported.
29
+ In future, this will be relaxed to be any metric.
30
+ """
19
31
 
20
32
  def __post_init__(self):
21
33
  if self.metric != "_step":
@@ -30,7 +42,7 @@ class RunMoment:
30
42
  raise ValueError(f"Only string run names are supported, got '{self.run}'.")
31
43
 
32
44
  @classmethod
33
- def from_uri(cls, uri: str) -> "RunMoment":
45
+ def from_uri(cls, uri: str) -> RunMoment:
34
46
  parsable = "runmoment://" + uri
35
47
  parse_err = ValueError(
36
48
  f"Could not parse passed run moment string '{uri}', "
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
@@ -124,9 +124,9 @@ class ServiceConnection:
124
124
  self._torn_down = False
125
125
  self._cleanup = cleanup
126
126
 
127
- def make_interface(self, mailbox: Mailbox) -> InterfaceBase:
127
+ def make_interface(self, mailbox: Mailbox, stream_id: str) -> InterfaceBase:
128
128
  """Returns an interface for communicating with the service."""
129
- return InterfaceSock(self._client, mailbox)
129
+ return InterfaceSock(self._client, mailbox, stream_id=stream_id)
130
130
 
131
131
  def send_record(self, record: pb.Record) -> None:
132
132
  """Sends data to the service."""