wandb 0.18.3__py3-none-win32.whl → 0.18.4__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 (46) hide show
  1. wandb/__init__.py +16 -7
  2. wandb/__init__.pyi +96 -63
  3. wandb/analytics/sentry.py +91 -88
  4. wandb/apis/public/api.py +18 -4
  5. wandb/apis/public/runs.py +53 -2
  6. wandb/bin/gpu_stats.exe +0 -0
  7. wandb/bin/wandb-core +0 -0
  8. wandb/cli/beta.py +178 -0
  9. wandb/cli/cli.py +5 -171
  10. wandb/data_types.py +3 -0
  11. wandb/env.py +74 -73
  12. wandb/errors/term.py +300 -43
  13. wandb/proto/v3/wandb_internal_pb2.py +263 -223
  14. wandb/proto/v3/wandb_server_pb2.py +57 -37
  15. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  16. wandb/proto/v4/wandb_internal_pb2.py +226 -218
  17. wandb/proto/v4/wandb_server_pb2.py +41 -37
  18. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  19. wandb/proto/v5/wandb_internal_pb2.py +226 -218
  20. wandb/proto/v5/wandb_server_pb2.py +41 -37
  21. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  22. wandb/sdk/__init__.py +3 -3
  23. wandb/sdk/artifacts/_validators.py +41 -8
  24. wandb/sdk/artifacts/artifact.py +32 -1
  25. wandb/sdk/artifacts/artifact_file_cache.py +1 -2
  26. wandb/sdk/data_types/_dtypes.py +7 -3
  27. wandb/sdk/data_types/video.py +15 -6
  28. wandb/sdk/interface/interface.py +2 -0
  29. wandb/sdk/internal/internal_api.py +122 -5
  30. wandb/sdk/internal/sender.py +16 -3
  31. wandb/sdk/launch/inputs/internal.py +1 -1
  32. wandb/sdk/lib/module.py +12 -0
  33. wandb/sdk/lib/printer.py +291 -105
  34. wandb/sdk/lib/progress.py +274 -0
  35. wandb/sdk/service/streams.py +21 -11
  36. wandb/sdk/wandb_init.py +58 -54
  37. wandb/sdk/wandb_run.py +380 -454
  38. wandb/sdk/wandb_settings.py +2 -0
  39. wandb/sdk/wandb_watch.py +17 -11
  40. wandb/util.py +6 -2
  41. {wandb-0.18.3.dist-info → wandb-0.18.4.dist-info}/METADATA +4 -3
  42. {wandb-0.18.3.dist-info → wandb-0.18.4.dist-info}/RECORD +45 -43
  43. wandb/bin/nvidia_gpu_stats.exe +0 -0
  44. {wandb-0.18.3.dist-info → wandb-0.18.4.dist-info}/WHEEL +0 -0
  45. {wandb-0.18.3.dist-info → wandb-0.18.4.dist-info}/entry_points.txt +0 -0
  46. {wandb-0.18.3.dist-info → wandb-0.18.4.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/lib/printer.py CHANGED
@@ -1,14 +1,25 @@
1
- # Note: this is a helper printer class, this file might go away once we switch to rich console printing
1
+ """Terminal, Jupyter and file output for W&B."""
2
2
 
3
+ from __future__ import annotations
4
+
5
+ import abc
6
+ import contextlib
3
7
  import itertools
4
8
  import platform
5
9
  import sys
6
- from abc import abstractmethod
7
- from typing import Callable, List, Optional, Tuple, Union
10
+ from typing import Callable, Iterator
11
+
12
+ import wandb.util
13
+
14
+ if sys.version_info >= (3, 12):
15
+ from typing import override
16
+ else:
17
+ from typing_extensions import override
8
18
 
9
19
  import click
10
20
 
11
21
  import wandb
22
+ from wandb.errors import term
12
23
 
13
24
  from . import ipython, sparkline
14
25
 
@@ -42,43 +53,82 @@ _name_to_level = {
42
53
  "NOTSET": NOTSET,
43
54
  }
44
55
 
56
+ _PROGRESS_SYMBOL_ANIMATION = "⢿⣻⣽⣾⣷⣯⣟⡿"
57
+ """Sequence of characters for a progress spinner.
45
58
 
46
- class _Printer:
47
- def sparklines(self, series: List[Union[int, float]]) -> Optional[str]:
48
- # Only print sparklines if the terminal is utf-8
49
- if wandb.util.is_unicode_safe(sys.stdout):
50
- return sparkline.sparkify(series)
51
- return None
59
+ Unicode characters from the Braille Patterns block arranged
60
+ to form a subtle clockwise spinning animation.
61
+ """
52
62
 
53
- def abort(
54
- self,
55
- ) -> str:
56
- return "Control-C" if platform.system() != "Windows" else "Ctrl-C"
63
+ _PROGRESS_SYMBOL_COLOR = 0xB2
64
+ """Color from the 256-color palette for the progress symbol."""
65
+
66
+
67
+ class Printer(abc.ABC):
68
+ """An object that shows styled text to the user."""
69
+
70
+ @contextlib.contextmanager
71
+ @abc.abstractmethod
72
+ def dynamic_text(self) -> Iterator[DynamicText | None]:
73
+ """A context manager providing a handle to a block of changeable text.
74
+
75
+ Since `wandb` may be outputting to a terminal, it's important to only
76
+ use this when `wandb` is performing blocking calls, or else text output
77
+ by non-`wandb` code may get overwritten.
57
78
 
79
+ Returns None if dynamic text is not supported, such as if stderr is not
80
+ a TTY and we're not in a Jupyter notebook.
81
+ """
82
+
83
+ @abc.abstractmethod
58
84
  def display(
59
85
  self,
60
- text: Union[str, List[str], Tuple[str]],
86
+ text: str | list[str] | tuple[str],
61
87
  *,
62
- level: Optional[Union[str, int]] = None,
63
- off: Optional[bool] = None,
64
- default_text: Optional[Union[str, List[str], Tuple[str]]] = None,
88
+ level: str | int | None = None,
65
89
  ) -> None:
66
- if off:
67
- return
68
- self._display(text, level=level, default_text=default_text)
90
+ """Display text to the user.
69
91
 
70
- @abstractmethod
71
- def _display(
92
+ Args:
93
+ text: The text to display. If given an iterable of strings, they're
94
+ joined with newlines.
95
+ level: The logging level, for controlling verbosity.
96
+ """
97
+
98
+ @abc.abstractmethod
99
+ def progress_update(
72
100
  self,
73
- text: Union[str, List[str], Tuple[str]],
74
- *,
75
- level: Optional[Union[str, int]] = None,
76
- default_text: Optional[Union[str, List[str], Tuple[str]]] = None,
101
+ text: str,
102
+ percent_done: float | None = None,
77
103
  ) -> None:
78
- raise NotImplementedError
104
+ r"""Set the text on the progress indicator.
105
+
106
+ Args:
107
+ text: The text to set, which must end with \r.
108
+ percent_done: The current progress, between 0 and 1.
109
+ """
110
+
111
+ @abc.abstractmethod
112
+ def progress_close(self, text: str | None = None) -> None:
113
+ """Close the progress indicator.
114
+
115
+ After this, `progress_update` should not be used.
116
+
117
+ Args:
118
+ text: The final text to set on the progress indicator.
119
+ Ignored in Jupyter notebooks.
120
+ """
79
121
 
80
122
  @staticmethod
81
- def _sanitize_level(name_or_level: Optional[Union[str, int]]) -> int:
123
+ def _sanitize_level(name_or_level: str | int | None) -> int:
124
+ """Returns the number corresponding to the logging level.
125
+
126
+ Args:
127
+ name_or_level: The logging level passed to `display`.
128
+
129
+ Raises:
130
+ ValueError: if the input is not a valid logging level.
131
+ """
82
132
  if isinstance(name_or_level, str):
83
133
  try:
84
134
  return _name_to_level[name_or_level.upper()]
@@ -95,65 +145,126 @@ class _Printer:
95
145
 
96
146
  raise ValueError(f"Unknown status level {name_or_level}")
97
147
 
98
- @abstractmethod
148
+ @property
149
+ @abc.abstractmethod
150
+ def supports_html(self) -> bool:
151
+ """Whether text passed to display may contain HTML styling."""
152
+
153
+ @property
154
+ @abc.abstractmethod
155
+ def supports_unicode(self) -> bool:
156
+ """Whether text passed to display may contain arbitrary Unicode."""
157
+
158
+ def sparklines(self, series: list[int | float]) -> str | None:
159
+ """Returns a Unicode art representation of the series of numbers.
160
+
161
+ Also known as "ASCII art", except this uses non-ASCII
162
+ Unicode characters.
163
+
164
+ Returns None if the output doesn't support Unicode.
165
+ """
166
+ if self.supports_unicode:
167
+ return sparkline.sparkify(series)
168
+ else:
169
+ return None
170
+
171
+ @abc.abstractmethod
99
172
  def code(self, text: str) -> str:
100
- raise NotImplementedError
173
+ """Returns the text styled like code."""
101
174
 
102
- @abstractmethod
175
+ @abc.abstractmethod
103
176
  def name(self, text: str) -> str:
104
- raise NotImplementedError
177
+ """Returns the text styled like a run name."""
105
178
 
106
- @abstractmethod
107
- def link(self, link: str, text: Optional[str] = None) -> str:
108
- raise NotImplementedError
179
+ @abc.abstractmethod
180
+ def link(self, link: str, text: str | None = None) -> str:
181
+ """Returns the text styled like a link.
109
182
 
110
- @abstractmethod
111
- def emoji(self, name: str) -> str:
112
- raise NotImplementedError
183
+ Args:
184
+ link: The target link.
185
+ text: The text to show for the link. If not set, or if we're not
186
+ in an environment that supports clickable links,
187
+ this is ignored.
188
+ """
189
+
190
+ @abc.abstractmethod
191
+ def secondary_text(self, text: str) -> str:
192
+ """Returns the text styled to draw less attention."""
193
+
194
+ @abc.abstractmethod
195
+ def loading_symbol(self, tick: int) -> str:
196
+ """Returns a frame of an animated loading symbol.
113
197
 
114
- @abstractmethod
115
- def status(self, text: str, failure: Optional[bool] = None) -> str:
116
- raise NotImplementedError
198
+ May return an empty string.
199
+
200
+ Args:
201
+ tick: An index into the animation.
202
+ """
203
+
204
+ @abc.abstractmethod
205
+ def error(self, text: str) -> str:
206
+ """Returns the text colored like an error."""
207
+
208
+ @abc.abstractmethod
209
+ def emoji(self, name: str) -> str:
210
+ """Returns the string for a named emoji, or an empty string."""
117
211
 
118
- @abstractmethod
212
+ @abc.abstractmethod
119
213
  def files(self, text: str) -> str:
120
- raise NotImplementedError
214
+ """Returns the text styled like a file path."""
121
215
 
122
- @abstractmethod
123
- def grid(self, rows: List[List[str]], title: Optional[str] = None) -> str:
124
- raise NotImplementedError
216
+ @abc.abstractmethod
217
+ def grid(self, rows: list[list[str]], title: str | None = None) -> str:
218
+ """Returns a grid of strings with an optional title."""
125
219
 
126
- @abstractmethod
127
- def panel(self, columns: List[str]) -> str:
128
- raise NotImplementedError
220
+ @abc.abstractmethod
221
+ def panel(self, columns: list[str]) -> str:
222
+ """Returns the column text combined in a compact way."""
129
223
 
130
224
 
131
- class PrinterTerm(_Printer):
225
+ class DynamicText(abc.ABC):
226
+ """A handle to a block of text that's allowed to change."""
227
+
228
+ @abc.abstractmethod
229
+ def set_text(self, text: str) -> None:
230
+ r"""Change the text.
231
+
232
+ Args:
233
+ text: The text to put in the block, with lines separated
234
+ by \n characters. The text should not end in \n unless
235
+ a blank line at the end of the block is desired.
236
+ May include styled output from methods on the Printer
237
+ that created this.
238
+ """
239
+
240
+
241
+ class _PrinterTerm(Printer):
132
242
  def __init__(self) -> None:
133
243
  super().__init__()
134
- self._html = False
135
244
  self._progress = itertools.cycle(["-", "\\", "|", "/"])
136
245
 
137
- def _display(
246
+ @override
247
+ @contextlib.contextmanager
248
+ def dynamic_text(self) -> Iterator[DynamicText | None]:
249
+ with term.dynamic_text() as handle:
250
+ if not handle:
251
+ yield None
252
+ else:
253
+ yield _DynamicTermText(handle)
254
+
255
+ @override
256
+ def display(
138
257
  self,
139
- text: Union[str, List[str], Tuple[str]],
258
+ text: str | list[str] | tuple[str],
140
259
  *,
141
- level: Optional[Union[str, int]] = None,
142
- default_text: Optional[Union[str, List[str], Tuple[str]]] = None,
260
+ level: str | int | None = None,
143
261
  ) -> None:
144
262
  text = "\n".join(text) if isinstance(text, (list, tuple)) else text
145
- if default_text is not None:
146
- default_text = (
147
- "\n".join(default_text)
148
- if isinstance(default_text, (list, tuple))
149
- else default_text
150
- )
151
- text = text or default_text
152
263
  self._display_fn_mapping(level)(text)
153
264
 
154
265
  @staticmethod
155
- def _display_fn_mapping(level: Optional[Union[str, int]]) -> Callable[[str], None]:
156
- level = _Printer._sanitize_level(level)
266
+ def _display_fn_mapping(level: str | int | None = None) -> Callable[[str], None]:
267
+ level = Printer._sanitize_level(level)
157
268
 
158
269
  if level >= CRITICAL:
159
270
  return wandb.termerror
@@ -168,27 +279,43 @@ class PrinterTerm(_Printer):
168
279
  else:
169
280
  return wandb.termlog
170
281
 
171
- def progress_update(self, text: str, percent_done: Optional[float] = None) -> None:
282
+ @override
283
+ def progress_update(self, text: str, percent_done: float | None = None) -> None:
172
284
  wandb.termlog(f"{next(self._progress)} {text}", newline=False)
173
285
 
174
- def progress_close(self, text: Optional[str] = None) -> None:
286
+ @override
287
+ def progress_close(self, text: str | None = None) -> None:
175
288
  text = text or " " * 79
176
289
  wandb.termlog(text)
177
290
 
291
+ @override
292
+ @property
293
+ def supports_html(self) -> bool:
294
+ return False
295
+
296
+ @override
297
+ @property
298
+ def supports_unicode(self) -> bool:
299
+ return wandb.util.is_unicode_safe(sys.stderr)
300
+
301
+ @override
178
302
  def code(self, text: str) -> str:
179
303
  ret: str = click.style(text, bold=True)
180
304
  return ret
181
305
 
306
+ @override
182
307
  def name(self, text: str) -> str:
183
308
  ret: str = click.style(text, fg="yellow")
184
309
  return ret
185
310
 
186
- def link(self, link: str, text: Optional[str] = None) -> str:
311
+ @override
312
+ def link(self, link: str, text: str | None = None) -> str:
187
313
  ret: str = click.style(link, fg="blue", underline=True)
188
314
  # ret = f"\x1b[m{text or link}\x1b[0m"
189
315
  # ret = f"\x1b]8;;{link}\x1b\\{ret}\x1b]8;;\x1b\\"
190
316
  return ret
191
317
 
318
+ @override
192
319
  def emoji(self, name: str) -> str:
193
320
  emojis = dict()
194
321
  if platform.system() != "Windows" and wandb.util.is_unicode_safe(sys.stdout):
@@ -203,16 +330,34 @@ class PrinterTerm(_Printer):
203
330
 
204
331
  return emojis.get(name, "")
205
332
 
206
- def status(self, text: str, failure: Optional[bool] = None) -> str:
207
- color = "red" if failure else "green"
208
- ret: str = click.style(text, fg=color)
209
- return ret
333
+ @override
334
+ def secondary_text(self, text: str) -> str:
335
+ # NOTE: "white" is really a light gray, and is usually distinct
336
+ # from the terminal's foreground color (i.e. default text color)
337
+ return click.style(text, fg="white")
338
+
339
+ @override
340
+ def loading_symbol(self, tick: int) -> str:
341
+ if not self.supports_unicode:
342
+ return ""
343
+
344
+ idx = tick % len(_PROGRESS_SYMBOL_ANIMATION)
345
+ return click.style(
346
+ _PROGRESS_SYMBOL_ANIMATION[idx],
347
+ fg=_PROGRESS_SYMBOL_COLOR,
348
+ )
210
349
 
350
+ @override
351
+ def error(self, text: str) -> str:
352
+ return click.style(text, fg="red")
353
+
354
+ @override
211
355
  def files(self, text: str) -> str:
212
356
  ret: str = click.style(text, fg="magenta", bold=True)
213
357
  return ret
214
358
 
215
- def grid(self, rows: List[List[str]], title: Optional[str] = None) -> str:
359
+ @override
360
+ def grid(self, rows: list[list[str]], title: str | None = None) -> str:
216
361
  max_len = max(len(row[0]) for row in rows)
217
362
  format_row = " ".join(["{:>{max_len}}", "{}" * (len(rows[0]) - 1)])
218
363
  grid = "\n".join([format_row.format(*row, max_len=max_len) for row in rows])
@@ -220,36 +365,44 @@ class PrinterTerm(_Printer):
220
365
  return f"{title}\n{grid}\n"
221
366
  return f"{grid}\n"
222
367
 
223
- def panel(self, columns: List[str]) -> str:
368
+ @override
369
+ def panel(self, columns: list[str]) -> str:
224
370
  return "\n" + "\n".join(columns)
225
371
 
226
372
 
227
- class PrinterJupyter(_Printer):
373
+ class _DynamicTermText(DynamicText):
374
+ def __init__(self, handle: term.DynamicBlock) -> None:
375
+ self._handle = handle
376
+
377
+ @override
378
+ def set_text(self, text: str) -> None:
379
+ self._handle.set_text(text)
380
+
381
+
382
+ class _PrinterJupyter(Printer):
228
383
  def __init__(self) -> None:
229
384
  super().__init__()
230
- self._html = True
231
385
  self._progress = ipython.jupyter_progress_bar()
232
386
 
233
- def _display(
387
+ @override
388
+ @contextlib.contextmanager
389
+ def dynamic_text(self) -> Iterator[DynamicText | None]:
390
+ # TODO: Support dynamic text in Jupyter notebooks.
391
+ yield None
392
+
393
+ @override
394
+ def display(
234
395
  self,
235
- text: Union[str, List[str], Tuple[str]],
396
+ text: str | list[str] | tuple[str],
236
397
  *,
237
- level: Optional[Union[str, int]] = None,
238
- default_text: Optional[Union[str, List[str], Tuple[str]]] = None,
398
+ level: str | int | None = None,
239
399
  ) -> None:
240
400
  text = "<br/>".join(text) if isinstance(text, (list, tuple)) else text
241
- if default_text is not None:
242
- default_text = (
243
- "<br/>".join(default_text)
244
- if isinstance(default_text, (list, tuple))
245
- else default_text
246
- )
247
- text = text or default_text
248
401
  self._display_fn_mapping(level)(text)
249
402
 
250
403
  @staticmethod
251
- def _display_fn_mapping(level: Optional[Union[str, int]]) -> Callable[[str], None]:
252
- level = _Printer._sanitize_level(level)
404
+ def _display_fn_mapping(level: str | int | None) -> Callable[[str], None]:
405
+ level = Printer._sanitize_level(level)
253
406
 
254
407
  if level >= CRITICAL:
255
408
  return ipython.display_html
@@ -264,34 +417,69 @@ class PrinterJupyter(_Printer):
264
417
  else:
265
418
  return ipython.display_html
266
419
 
420
+ @override
421
+ @property
422
+ def supports_html(self) -> bool:
423
+ return True
424
+
425
+ @override
426
+ @property
427
+ def supports_unicode(self) -> bool:
428
+ return True
429
+
430
+ @override
267
431
  def code(self, text: str) -> str:
268
432
  return f"<code>{text}<code>"
269
433
 
434
+ @override
270
435
  def name(self, text: str) -> str:
271
436
  return f'<strong style="color:#cdcd00">{text}</strong>'
272
437
 
273
- def link(self, link: str, text: Optional[str] = None) -> str:
438
+ @override
439
+ def link(self, link: str, text: str | None = None) -> str:
274
440
  return f'<a href={link!r} target="_blank">{text or link}</a>'
275
441
 
442
+ @override
276
443
  def emoji(self, name: str) -> str:
277
444
  return ""
278
445
 
279
- def status(self, text: str, failure: Optional[bool] = None) -> str:
280
- color = "red" if failure else "green"
281
- return f'<strong style="color:{color}">{text}</strong>'
446
+ @override
447
+ def secondary_text(self, text: str) -> str:
448
+ return text
449
+
450
+ @override
451
+ def loading_symbol(self, tick: int) -> str:
452
+ return ""
453
+
454
+ @override
455
+ def error(self, text: str) -> str:
456
+ return f'<strong style="color:red">{text}</strong>'
282
457
 
458
+ @override
283
459
  def files(self, text: str) -> str:
284
460
  return f"<code>{text}</code>"
285
461
 
286
- def progress_update(self, text: str, percent_done: float) -> None:
287
- if self._progress:
288
- self._progress.update(percent_done, text)
462
+ @override
463
+ def progress_update(
464
+ self,
465
+ text: str,
466
+ percent_done: float | None = None,
467
+ ) -> None:
468
+ if not self._progress:
469
+ return
289
470
 
290
- def progress_close(self, _: Optional[str] = None) -> None:
471
+ if percent_done is None:
472
+ percent_done = 1.0
473
+
474
+ self._progress.update(percent_done, text)
475
+
476
+ @override
477
+ def progress_close(self, _: str | None = None) -> None:
291
478
  if self._progress:
292
479
  self._progress.close()
293
480
 
294
- def grid(self, rows: List[List[str]], title: Optional[str] = None) -> str:
481
+ @override
482
+ def grid(self, rows: list[list[str]], title: str | None = None) -> str:
295
483
  format_row = "".join(["<tr>", "<td>{}</td>" * len(rows[0]), "</tr>"])
296
484
  grid = "".join([format_row.format(*row) for row in rows])
297
485
  grid = f'<table class="wandb">{grid}</table>'
@@ -299,15 +487,13 @@ class PrinterJupyter(_Printer):
299
487
  return f"<h3>{title}</h3><br/>{grid}<br/>"
300
488
  return f"{grid}<br/>"
301
489
 
302
- def panel(self, columns: List[str]) -> str:
490
+ @override
491
+ def panel(self, columns: list[str]) -> str:
303
492
  row = "".join([f'<div class="wandb-col">{col}</div>' for col in columns])
304
493
  return f'{ipython.TABLE_STYLES}<div class="wandb-row">{row}</div>'
305
494
 
306
495
 
307
- Printer = Union[PrinterTerm, PrinterJupyter]
308
-
309
-
310
- def get_printer(_jupyter: bool) -> Printer:
311
- if _jupyter:
312
- return PrinterJupyter()
313
- return PrinterTerm()
496
+ def get_printer(jupyter: bool) -> Printer:
497
+ if jupyter:
498
+ return _PrinterJupyter()
499
+ return _PrinterTerm()