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.
- 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 +122 -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.4.dist-info}/METADATA +4 -3
- {wandb-0.18.3.dist-info → wandb-0.18.4.dist-info}/RECORD +45 -43
- wandb/bin/nvidia_gpu_stats.exe +0 -0
- {wandb-0.18.3.dist-info → wandb-0.18.4.dist-info}/WHEEL +0 -0
- {wandb-0.18.3.dist-info → wandb-0.18.4.dist-info}/entry_points.txt +0 -0
- {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 | 
            -
             | 
| 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()
         |