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.
- wandb/__init__.py +16 -7
- wandb/__init__.pyi +96 -63
- wandb/analytics/sentry.py +91 -88
- wandb/apis/public/api.py +18 -4
- wandb/apis/public/runs.py +53 -2
- wandb/bin/gpu_stats.exe +0 -0
- wandb/bin/wandb-core +0 -0
- wandb/cli/beta.py +178 -0
- wandb/cli/cli.py +5 -171
- wandb/data_types.py +3 -0
- wandb/env.py +74 -73
- wandb/errors/term.py +300 -43
- wandb/proto/v3/wandb_internal_pb2.py +263 -223
- wandb/proto/v3/wandb_server_pb2.py +57 -37
- wandb/proto/v3/wandb_settings_pb2.py +2 -2
- wandb/proto/v4/wandb_internal_pb2.py +226 -218
- wandb/proto/v4/wandb_server_pb2.py +41 -37
- wandb/proto/v4/wandb_settings_pb2.py +2 -2
- wandb/proto/v5/wandb_internal_pb2.py +226 -218
- wandb/proto/v5/wandb_server_pb2.py +41 -37
- wandb/proto/v5/wandb_settings_pb2.py +2 -2
- wandb/sdk/__init__.py +3 -3
- wandb/sdk/artifacts/_validators.py +41 -8
- wandb/sdk/artifacts/artifact.py +32 -1
- wandb/sdk/artifacts/artifact_file_cache.py +1 -2
- wandb/sdk/data_types/_dtypes.py +7 -3
- wandb/sdk/data_types/video.py +15 -6
- wandb/sdk/interface/interface.py +2 -0
- wandb/sdk/internal/internal_api.py +126 -5
- wandb/sdk/internal/sender.py +16 -3
- wandb/sdk/launch/inputs/internal.py +1 -1
- wandb/sdk/lib/module.py +12 -0
- wandb/sdk/lib/printer.py +291 -105
- wandb/sdk/lib/progress.py +274 -0
- wandb/sdk/service/streams.py +21 -11
- wandb/sdk/wandb_init.py +58 -54
- wandb/sdk/wandb_run.py +380 -454
- wandb/sdk/wandb_settings.py +2 -0
- wandb/sdk/wandb_watch.py +17 -11
- wandb/util.py +6 -2
- {wandb-0.18.3.dist-info → wandb-0.18.5.dist-info}/METADATA +4 -3
- {wandb-0.18.3.dist-info → wandb-0.18.5.dist-info}/RECORD +45 -43
- wandb/bin/nvidia_gpu_stats.exe +0 -0
- {wandb-0.18.3.dist-info → wandb-0.18.5.dist-info}/WHEEL +0 -0
- {wandb-0.18.3.dist-info → wandb-0.18.5.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
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
|
7
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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:
|
86
|
+
text: str | list[str] | tuple[str],
|
61
87
|
*,
|
62
|
-
level:
|
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
|
-
|
67
|
-
return
|
68
|
-
self._display(text, level=level, default_text=default_text)
|
90
|
+
"""Display text to the user.
|
69
91
|
|
70
|
-
|
71
|
-
|
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:
|
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
|
-
|
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:
|
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
|
-
@
|
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
|
-
|
173
|
+
"""Returns the text styled like code."""
|
101
174
|
|
102
|
-
@abstractmethod
|
175
|
+
@abc.abstractmethod
|
103
176
|
def name(self, text: str) -> str:
|
104
|
-
|
177
|
+
"""Returns the text styled like a run name."""
|
105
178
|
|
106
|
-
@abstractmethod
|
107
|
-
def link(self, link: str, text:
|
108
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
214
|
+
"""Returns the text styled like a file path."""
|
121
215
|
|
122
|
-
@abstractmethod
|
123
|
-
def grid(self, rows:
|
124
|
-
|
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:
|
128
|
-
|
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
|
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
|
-
|
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:
|
258
|
+
text: str | list[str] | tuple[str],
|
140
259
|
*,
|
141
|
-
level:
|
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:
|
156
|
-
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
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
|
-
|
368
|
+
@override
|
369
|
+
def panel(self, columns: list[str]) -> str:
|
224
370
|
return "\n" + "\n".join(columns)
|
225
371
|
|
226
372
|
|
227
|
-
class
|
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
|
-
|
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:
|
396
|
+
text: str | list[str] | tuple[str],
|
236
397
|
*,
|
237
|
-
level:
|
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:
|
252
|
-
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
|
-
|
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
|
-
|
280
|
-
|
281
|
-
return
|
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
|
-
|
287
|
-
|
288
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
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()
|