wandb 0.18.3__py3-none-win_amd64.whl → 0.18.5__py3-none-win_amd64.whl

Sign up to get free protection for your applications and to get access to all the features.
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 +126 -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.5.dist-info}/METADATA +4 -3
  42. {wandb-0.18.3.dist-info → wandb-0.18.5.dist-info}/RECORD +45 -43
  43. wandb/bin/nvidia_gpu_stats.exe +0 -0
  44. {wandb-0.18.3.dist-info → wandb-0.18.5.dist-info}/WHEEL +0 -0
  45. {wandb-0.18.3.dist-info → wandb-0.18.5.dist-info}/entry_points.txt +0 -0
  46. {wandb-0.18.3.dist-info → wandb-0.18.5.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()