wandb 0.18.2__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 (45) 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 +271 -221
  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 -216
  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 -216
  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 +35 -4
  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 +59 -54
  37. wandb/sdk/wandb_run.py +413 -480
  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.2.dist-info → wandb-0.18.4.dist-info}/METADATA +5 -4
  42. {wandb-0.18.2.dist-info → wandb-0.18.4.dist-info}/RECORD +45 -42
  43. {wandb-0.18.2.dist-info → wandb-0.18.4.dist-info}/WHEEL +0 -0
  44. {wandb-0.18.2.dist-info → wandb-0.18.4.dist-info}/entry_points.txt +0 -0
  45. {wandb-0.18.2.dist-info → wandb-0.18.4.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/wandb_run.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import _thread as thread
2
4
  import atexit
3
5
  import functools
@@ -18,20 +20,12 @@ from dataclasses import dataclass, field
18
20
  from datetime import datetime, timedelta, timezone
19
21
  from enum import IntEnum
20
22
  from types import TracebackType
21
- from typing import (
22
- TYPE_CHECKING,
23
- Any,
24
- Callable,
25
- Dict,
26
- List,
27
- NamedTuple,
28
- Optional,
29
- Sequence,
30
- TextIO,
31
- Tuple,
32
- Type,
33
- Union,
34
- )
23
+ from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Sequence, TextIO
24
+
25
+ if sys.version_info < (3, 8):
26
+ from typing_extensions import Literal
27
+ else:
28
+ from typing import Literal
35
29
 
36
30
  import requests
37
31
 
@@ -69,7 +63,11 @@ from wandb.util import (
69
63
  )
70
64
 
71
65
  from . import wandb_config, wandb_metric, wandb_summary
72
- from .artifacts._validators import validate_aliases, validate_tags
66
+ from .artifacts._validators import (
67
+ is_artifact_registry_project,
68
+ validate_aliases,
69
+ validate_tags,
70
+ )
73
71
  from .data_types._dtypes import TypeRegistry
74
72
  from .interface.interface import FilesDict, GlobStr, InterfaceBase, PolicyName
75
73
  from .interface.summary_record import SummaryRecord
@@ -80,6 +78,8 @@ from .lib import (
80
78
  filesystem,
81
79
  ipython,
82
80
  module,
81
+ printer,
82
+ progress,
83
83
  proto_util,
84
84
  redirect,
85
85
  telemetry,
@@ -87,7 +87,6 @@ from .lib import (
87
87
  from .lib.exit_hooks import ExitHooks
88
88
  from .lib.gitlib import GitRepo
89
89
  from .lib.mailbox import MailboxError, MailboxHandle, MailboxProbe, MailboxProgress
90
- from .lib.printer import get_printer
91
90
  from .lib.proto_util import message_to_dict
92
91
  from .lib.reporting import Reporter
93
92
  from .lib.wburls import wburls
@@ -101,26 +100,27 @@ if TYPE_CHECKING:
101
100
  else:
102
101
  from typing_extensions import TypedDict
103
102
 
103
+ import torch # type: ignore [import-not-found]
104
+
104
105
  import wandb.apis.public
105
106
  import wandb.sdk.backend.backend
106
107
  import wandb.sdk.interface.interface_queue
108
+ from wandb.data_types import Table
107
109
  from wandb.proto.wandb_internal_pb2 import (
108
110
  GetSummaryResponse,
109
111
  InternalMessagesResponse,
110
112
  SampledHistoryResponse,
111
113
  )
112
114
 
113
- from .lib.printer import PrinterJupyter, PrinterTerm
114
-
115
115
  class GitSourceDict(TypedDict):
116
116
  remote: str
117
117
  commit: str
118
- entrypoint: List[str]
118
+ entrypoint: list[str]
119
119
  args: Sequence[str]
120
120
 
121
121
  class ArtifactSourceDict(TypedDict):
122
122
  artifact: str
123
- entrypoint: List[str]
123
+ entrypoint: list[str]
124
124
  args: Sequence[str]
125
125
 
126
126
  class ImageSourceDict(TypedDict):
@@ -130,10 +130,10 @@ if TYPE_CHECKING:
130
130
  class JobSourceDict(TypedDict, total=False):
131
131
  _version: str
132
132
  source_type: str
133
- source: Union[GitSourceDict, ArtifactSourceDict, ImageSourceDict]
134
- input_types: Dict[str, Any]
135
- output_types: Dict[str, Any]
136
- runtime: Optional[str]
133
+ source: GitSourceDict | ArtifactSourceDict | ImageSourceDict
134
+ input_types: dict[str, Any]
135
+ output_types: dict[str, Any]
136
+ runtime: str | None
137
137
 
138
138
 
139
139
  logger = logging.getLogger("wandb")
@@ -160,11 +160,11 @@ class RunStatusChecker:
160
160
  """
161
161
 
162
162
  _stop_status_lock: threading.Lock
163
- _stop_status_handle: Optional[MailboxHandle]
163
+ _stop_status_handle: MailboxHandle | None
164
164
  _network_status_lock: threading.Lock
165
- _network_status_handle: Optional[MailboxHandle]
165
+ _network_status_handle: MailboxHandle | None
166
166
  _internal_messages_lock: threading.Lock
167
- _internal_messages_handle: Optional[MailboxHandle]
167
+ _internal_messages_handle: MailboxHandle | None
168
168
 
169
169
  def __init__(
170
170
  self,
@@ -212,7 +212,7 @@ class RunStatusChecker:
212
212
  @staticmethod
213
213
  def _abandon_status_check(
214
214
  lock: threading.Lock,
215
- handle: Optional[MailboxHandle],
215
+ handle: MailboxHandle | None,
216
216
  ):
217
217
  with lock:
218
218
  if handle:
@@ -227,7 +227,7 @@ class RunStatusChecker:
227
227
  request: Any,
228
228
  process: Any,
229
229
  ) -> None:
230
- local_handle: Optional[MailboxHandle] = None
230
+ local_handle: MailboxHandle | None = None
231
231
  join_requested = False
232
232
  while not join_requested:
233
233
  time_probe = time.monotonic()
@@ -365,7 +365,7 @@ class _run_decorator: # noqa: N801
365
365
  @classmethod
366
366
  def _attach(cls, func: Callable) -> Callable:
367
367
  @functools.wraps(func)
368
- def wrapper(self: Type["Run"], *args: Any, **kwargs: Any) -> Any:
368
+ def wrapper(self: type[Run], *args: Any, **kwargs: Any) -> Any:
369
369
  # * `_attach_id` is only assigned in service hence for all non-service cases
370
370
  # it will be a passthrough.
371
371
  # * `_attach_pid` is only assigned in _init (using _attach_pid guarantees single attach):
@@ -398,7 +398,7 @@ class _run_decorator: # noqa: N801
398
398
  def _noop_on_finish(cls, message: str = "", only_warn: bool = False) -> Callable:
399
399
  def decorator_fn(func: Callable) -> Callable:
400
400
  @functools.wraps(func)
401
- def wrapper_fn(self: Type["Run"], *args: Any, **kwargs: Any) -> Any:
401
+ def wrapper_fn(self: type[Run], *args: Any, **kwargs: Any) -> Any:
402
402
  if not getattr(self, "_is_finished", False):
403
403
  return func(self, *args, **kwargs)
404
404
 
@@ -419,7 +419,7 @@ class _run_decorator: # noqa: N801
419
419
  @classmethod
420
420
  def _noop(cls, func: Callable) -> Callable:
421
421
  @functools.wraps(func)
422
- def wrapper(self: Type["Run"], *args: Any, **kwargs: Any) -> Any:
422
+ def wrapper(self: type[Run], *args: Any, **kwargs: Any) -> Any:
423
423
  # `_attach_id` is only assigned in service hence for all service cases
424
424
  # it will be a passthrough. We don't pickle non-service so again a way
425
425
  # to see that we are in non-service case
@@ -458,7 +458,7 @@ class _run_decorator: # noqa: N801
458
458
  class RunStatus:
459
459
  sync_items_total: int = field(default=0)
460
460
  sync_items_pending: int = field(default=0)
461
- sync_time: Optional[datetime] = field(default=None)
461
+ sync_time: datetime | None = field(default=None)
462
462
 
463
463
 
464
464
  class Run:
@@ -528,67 +528,65 @@ class Run:
528
528
  _telemetry_obj_dirty: bool
529
529
  _telemetry_obj_flushed: bytes
530
530
 
531
- _teardown_hooks: List[TeardownHook]
532
- _tags: Optional[Tuple[Any, ...]]
531
+ _teardown_hooks: list[TeardownHook]
532
+ _tags: tuple[Any, ...] | None
533
533
 
534
- _entity: Optional[str]
535
- _project: Optional[str]
536
- _group: Optional[str]
537
- _job_type: Optional[str]
538
- _name: Optional[str]
539
- _notes: Optional[str]
540
- _sweep_id: Optional[str]
534
+ _entity: str | None
535
+ _project: str | None
536
+ _group: str | None
537
+ _job_type: str | None
538
+ _name: str | None
539
+ _notes: str | None
540
+ _sweep_id: str | None
541
541
 
542
- _run_obj: Optional[RunRecord]
542
+ _run_obj: RunRecord | None
543
543
  # Use string literal annotation because of type reference loop
544
- _backend: Optional["wandb.sdk.backend.backend.Backend"]
545
- _internal_run_interface: Optional[
546
- "wandb.sdk.interface.interface_queue.InterfaceQueue"
547
- ]
548
- _wl: Optional[_WandbSetup]
549
-
550
- _out_redir: Optional[redirect.RedirectBase]
551
- _err_redir: Optional[redirect.RedirectBase]
552
- _redirect_cb: Optional[Callable[[str, str], None]]
553
- _redirect_raw_cb: Optional[Callable[[str, str], None]]
554
- _output_writer: Optional["filesystem.CRDedupedFile"]
555
- _quiet: Optional[bool]
544
+ _backend: wandb.sdk.backend.backend.Backend | None
545
+ _internal_run_interface: wandb.sdk.interface.interface_queue.InterfaceQueue | None
546
+ _wl: _WandbSetup | None
547
+
548
+ _out_redir: redirect.RedirectBase | None
549
+ _err_redir: redirect.RedirectBase | None
550
+ _redirect_cb: Callable[[str, str], None] | None
551
+ _redirect_raw_cb: Callable[[str, str], None] | None
552
+ _output_writer: filesystem.CRDedupedFile | None
553
+ _quiet: bool | None
556
554
 
557
555
  _atexit_cleanup_called: bool
558
- _hooks: Optional[ExitHooks]
559
- _exit_code: Optional[int]
556
+ _hooks: ExitHooks | None
557
+ _exit_code: int | None
560
558
 
561
- _run_status_checker: Optional[RunStatusChecker]
559
+ _run_status_checker: RunStatusChecker | None
562
560
 
563
- _sampled_history: Optional["SampledHistoryResponse"]
564
- _final_summary: Optional["GetSummaryResponse"]
565
- _poll_exit_handle: Optional[MailboxHandle]
566
- _poll_exit_response: Optional[PollExitResponse]
567
- _internal_messages_response: Optional["InternalMessagesResponse"]
561
+ _sampled_history: SampledHistoryResponse | None
562
+ _final_summary: GetSummaryResponse | None
563
+ _poll_exit_handle: MailboxHandle | None
564
+ _poll_exit_response: PollExitResponse | None
565
+ _internal_messages_response: InternalMessagesResponse | None
568
566
 
569
- _stdout_slave_fd: Optional[int]
570
- _stderr_slave_fd: Optional[int]
571
- _artifact_slots: List[str]
567
+ _stdout_slave_fd: int | None
568
+ _stderr_slave_fd: int | None
569
+ _artifact_slots: list[str]
572
570
 
573
571
  _init_pid: int
574
572
  _attach_pid: int
575
- _iface_pid: Optional[int]
576
- _iface_port: Optional[int]
573
+ _iface_pid: int | None
574
+ _iface_port: int | None
577
575
 
578
- _attach_id: Optional[str]
576
+ _attach_id: str | None
579
577
  _is_attached: bool
580
578
  _is_finished: bool
581
579
  _settings: Settings
582
580
 
583
- _launch_artifacts: Optional[Dict[str, Any]]
584
- _printer: Union["PrinterTerm", "PrinterJupyter"]
581
+ _launch_artifacts: dict[str, Any] | None
582
+ _printer: printer.Printer
585
583
 
586
584
  def __init__(
587
585
  self,
588
586
  settings: Settings,
589
- config: Optional[Dict[str, Any]] = None,
590
- sweep_config: Optional[Dict[str, Any]] = None,
591
- launch_config: Optional[Dict[str, Any]] = None,
587
+ config: dict[str, Any] | None = None,
588
+ sweep_config: dict[str, Any] | None = None,
589
+ launch_config: dict[str, Any] | None = None,
592
590
  ) -> None:
593
591
  # pid is set, so we know if this run object was initialized by this process
594
592
  self._init_pid = os.getpid()
@@ -608,9 +606,9 @@ class Run:
608
606
  def _init(
609
607
  self,
610
608
  settings: Settings,
611
- config: Optional[Dict[str, Any]] = None,
612
- sweep_config: Optional[Dict[str, Any]] = None,
613
- launch_config: Optional[Dict[str, Any]] = None,
609
+ config: dict[str, Any] | None = None,
610
+ sweep_config: dict[str, Any] | None = None,
611
+ launch_config: dict[str, Any] | None = None,
614
612
  ) -> None:
615
613
  self._settings = settings
616
614
 
@@ -626,7 +624,7 @@ class Run:
626
624
  )
627
625
  self.summary._set_update_callback(self._summary_update_callback)
628
626
  self._step = 0
629
- self._torch_history: Optional[wandb_torch.TorchHistory] = None # type: ignore
627
+ self._torch_history: wandb_torch.TorchHistory | None = None # type: ignore
630
628
 
631
629
  # todo: eventually would be nice to make this configurable using self._settings._start_time
632
630
  # need to test (jhr): if you set start time to 2 days ago and run a test for 15 minutes,
@@ -635,9 +633,9 @@ class Run:
635
633
 
636
634
  _datatypes_set_callback(self._datatypes_callback)
637
635
 
638
- self._printer = get_printer(self._settings._jupyter)
636
+ self._printer = printer.get_printer(self._settings._jupyter)
639
637
  self._wl = None
640
- self._reporter: Optional[Reporter] = None
638
+ self._reporter: Reporter | None = None
641
639
 
642
640
  self._entity = None
643
641
  self._project = None
@@ -663,7 +661,7 @@ class Run:
663
661
  self._quiet = self._settings.quiet
664
662
 
665
663
  self._output_writer = None
666
- self._used_artifact_slots: Dict[str, str] = {}
664
+ self._used_artifact_slots: dict[str, str] = {}
667
665
 
668
666
  # Returned from backend request_run(), set from wandb_init?
669
667
  self._run_obj = None
@@ -699,8 +697,8 @@ class Run:
699
697
  config = config or dict()
700
698
  wandb_key = "_wandb"
701
699
  config.setdefault(wandb_key, dict())
702
- self._launch_artifact_mapping: Dict[str, Any] = {}
703
- self._unique_launch_artifact_sequence_names: Dict[str, Any] = {}
700
+ self._launch_artifact_mapping: dict[str, Any] = {}
701
+ self._unique_launch_artifact_sequence_names: dict[str, Any] = {}
704
702
  if self._settings.save_code and self._settings.program_relpath:
705
703
  config[wandb_key]["code_path"] = LogicalPath(
706
704
  os.path.join("code", self._settings.program_relpath)
@@ -766,7 +764,7 @@ class Run:
766
764
  def _handle_launch_artifact_overrides(self) -> None:
767
765
  if self._settings.launch and (os.environ.get("WANDB_ARTIFACTS") is not None):
768
766
  try:
769
- artifacts: Dict[str, Any] = json.loads(
767
+ artifacts: dict[str, Any] = json.loads(
770
768
  os.environ.get("WANDB_ARTIFACTS", "{}")
771
769
  )
772
770
  except (ValueError, SyntaxError):
@@ -786,7 +784,7 @@ class Run:
786
784
  artifacts = launch_config.get("overrides").get("artifacts")
787
785
  self._initialize_launch_artifact_maps(artifacts)
788
786
 
789
- def _initialize_launch_artifact_maps(self, artifacts: Dict[str, Any]) -> None:
787
+ def _initialize_launch_artifact_maps(self, artifacts: dict[str, Any]) -> None:
790
788
  for key, item in artifacts.items():
791
789
  self._launch_artifact_mapping[key] = item
792
790
  artifact_sequence_tuple_or_slot = key.split(":")
@@ -893,7 +891,7 @@ class Run:
893
891
  except Exception:
894
892
  wandb.termwarn("Cannot find valid git repo associated with this directory.")
895
893
 
896
- def __deepcopy__(self, memo: Dict[int, Any]) -> "Run":
894
+ def __deepcopy__(self, memo: dict[int, Any]) -> Run:
897
895
  return self
898
896
 
899
897
  def __getstate__(self) -> Any:
@@ -927,7 +925,7 @@ class Run:
927
925
  self.__dict__.update(state)
928
926
 
929
927
  @property
930
- def _torch(self) -> "wandb_torch.TorchHistory": # type: ignore
928
+ def _torch(self) -> wandb_torch.TorchHistory: # type: ignore
931
929
  if self._torch_history is None:
932
930
  self._torch_history = wandb_torch.TorchHistory() # type: ignore
933
931
  return self._torch_history
@@ -959,7 +957,7 @@ class Run:
959
957
 
960
958
  @property
961
959
  @_run_decorator._attach
962
- def name(self) -> Optional[str]:
960
+ def name(self) -> str | None:
963
961
  """Display name of the run.
964
962
 
965
963
  Display names are not guaranteed to be unique and may be descriptive.
@@ -982,7 +980,7 @@ class Run:
982
980
 
983
981
  @property
984
982
  @_run_decorator._attach
985
- def notes(self) -> Optional[str]:
983
+ def notes(self) -> str | None:
986
984
  """Notes associated with the run, if there are any.
987
985
 
988
986
  Notes can be a multiline string and can also use markdown and latex equations
@@ -1003,7 +1001,7 @@ class Run:
1003
1001
 
1004
1002
  @property
1005
1003
  @_run_decorator._attach
1006
- def tags(self) -> Optional[Tuple]:
1004
+ def tags(self) -> tuple | None:
1007
1005
  """Tags associated with the run, if there are any."""
1008
1006
  if self._tags:
1009
1007
  return self._tags
@@ -1030,7 +1028,7 @@ class Run:
1030
1028
 
1031
1029
  @property
1032
1030
  @_run_decorator._attach
1033
- def sweep_id(self) -> Optional[str]:
1031
+ def sweep_id(self) -> str | None:
1034
1032
  """ID of the sweep associated with the run, if there is one."""
1035
1033
  if not self._run_obj:
1036
1034
  return None
@@ -1090,6 +1088,7 @@ class Run:
1090
1088
  return self._step
1091
1089
 
1092
1090
  def project_name(self) -> str:
1091
+ # TODO: deprecate this in favor of project
1093
1092
  return self._run_obj.project if self._run_obj else ""
1094
1093
 
1095
1094
  @property
@@ -1148,15 +1147,13 @@ class Run:
1148
1147
  @_run_decorator._attach
1149
1148
  def log_code(
1150
1149
  self,
1151
- root: Optional[str] = ".",
1152
- name: Optional[str] = None,
1153
- include_fn: Union[
1154
- Callable[[str, str], bool], Callable[[str], bool]
1155
- ] = _is_py_requirements_or_dockerfile,
1156
- exclude_fn: Union[
1157
- Callable[[str, str], bool], Callable[[str], bool]
1158
- ] = filenames.exclude_wandb_fn,
1159
- ) -> Optional[Artifact]:
1150
+ root: str | None = ".",
1151
+ name: str | None = None,
1152
+ include_fn: Callable[[str, str], bool]
1153
+ | Callable[[str], bool] = _is_py_requirements_or_dockerfile,
1154
+ exclude_fn: Callable[[str, str], bool]
1155
+ | Callable[[str], bool] = filenames.exclude_wandb_fn,
1156
+ ) -> Artifact | None:
1160
1157
  """Save the current state of your code to a W&B Artifact.
1161
1158
 
1162
1159
  By default, it walks the current directory and logs all files that end with `.py`.
@@ -1229,7 +1226,7 @@ class Run:
1229
1226
 
1230
1227
  return self._log_artifact(art)
1231
1228
 
1232
- def get_url(self) -> Optional[str]:
1229
+ def get_url(self) -> str | None:
1233
1230
  """Return the url for the W&B run, if there is one.
1234
1231
 
1235
1232
  Offline runs will not have a url.
@@ -1239,7 +1236,7 @@ class Run:
1239
1236
  return None
1240
1237
  return self._settings.run_url
1241
1238
 
1242
- def get_project_url(self) -> Optional[str]:
1239
+ def get_project_url(self) -> str | None:
1243
1240
  """Return the url for the W&B project associated with the run, if there is one.
1244
1241
 
1245
1242
  Offline runs will not have a project url.
@@ -1249,7 +1246,7 @@ class Run:
1249
1246
  return None
1250
1247
  return self._settings.project_url
1251
1248
 
1252
- def get_sweep_url(self) -> Optional[str]:
1249
+ def get_sweep_url(self) -> str | None:
1253
1250
  """Return the url for the sweep associated with the run, if there is one."""
1254
1251
  if self._settings._offline:
1255
1252
  wandb.termwarn("URL not available in offline run")
@@ -1258,7 +1255,7 @@ class Run:
1258
1255
 
1259
1256
  @property
1260
1257
  @_run_decorator._attach
1261
- def url(self) -> Optional[str]:
1258
+ def url(self) -> str | None:
1262
1259
  """The W&B url associated with the run."""
1263
1260
  return self.get_url()
1264
1261
 
@@ -1273,9 +1270,9 @@ class Run:
1273
1270
 
1274
1271
  def _label_internal(
1275
1272
  self,
1276
- code: Optional[str] = None,
1277
- repo: Optional[str] = None,
1278
- code_version: Optional[str] = None,
1273
+ code: str | None = None,
1274
+ repo: str | None = None,
1275
+ code_version: str | None = None,
1279
1276
  ) -> None:
1280
1277
  with telemetry.context(run=self) as tel:
1281
1278
  if code and RE_LABEL.match(code):
@@ -1287,9 +1284,9 @@ class Run:
1287
1284
 
1288
1285
  def _label(
1289
1286
  self,
1290
- code: Optional[str] = None,
1291
- repo: Optional[str] = None,
1292
- code_version: Optional[str] = None,
1287
+ code: str | None = None,
1288
+ repo: str | None = None,
1289
+ code_version: str | None = None,
1293
1290
  **kwargs: str,
1294
1291
  ) -> None:
1295
1292
  if self._settings.label_disable:
@@ -1311,7 +1308,7 @@ class Run:
1311
1308
  # update telemetry in the backend immediately for _label() callers
1312
1309
  self._telemetry_flush()
1313
1310
 
1314
- def _label_probe_lines(self, lines: List[str]) -> None:
1311
+ def _label_probe_lines(self, lines: list[str]) -> None:
1315
1312
  if not lines:
1316
1313
  return
1317
1314
  parsed = telemetry._parse_label_lines(lines)
@@ -1379,23 +1376,23 @@ class Run:
1379
1376
  return prefix + f"<iframe src={url!r} style={style!r}></iframe>"
1380
1377
 
1381
1378
  def _repr_mimebundle_(
1382
- self, include: Optional[Any] = None, exclude: Optional[Any] = None
1383
- ) -> Dict[str, str]:
1379
+ self, include: Any | None = None, exclude: Any | None = None
1380
+ ) -> dict[str, str]:
1384
1381
  return {"text/html": self.to_html(hidden=True)}
1385
1382
 
1386
1383
  @_run_decorator._noop_on_finish()
1387
1384
  def _config_callback(
1388
1385
  self,
1389
- key: Optional[Union[Tuple[str, ...], str]] = None,
1390
- val: Optional[Any] = None,
1391
- data: Optional[Dict[str, object]] = None,
1386
+ key: tuple[str, ...] | str | None = None,
1387
+ val: Any | None = None,
1388
+ data: dict[str, object] | None = None,
1392
1389
  ) -> None:
1393
1390
  logger.info(f"config_cb {key} {val} {data}")
1394
1391
  if self._backend and self._backend.interface:
1395
1392
  self._backend.interface.publish_config(key=key, val=val, data=data)
1396
1393
 
1397
1394
  def _config_artifact_callback(
1398
- self, key: str, val: Union[str, Artifact, dict]
1395
+ self, key: str, val: str | Artifact | dict
1399
1396
  ) -> Artifact:
1400
1397
  # artifacts can look like dicts as they are passed into the run config
1401
1398
  # since the run config stores them on the backend as a dict with fields shown
@@ -1446,7 +1443,7 @@ class Run:
1446
1443
  # line = "Waiting for run.summary data..."
1447
1444
  # self._printer.display(line)
1448
1445
 
1449
- def _summary_get_current_summary_callback(self) -> Dict[str, Any]:
1446
+ def _summary_get_current_summary_callback(self) -> dict[str, Any]:
1450
1447
  if self._is_finished:
1451
1448
  # TODO: WB-18420: fetch summary from backend and stage it before run is finished
1452
1449
  wandb.termwarn("Summary data not available in finished run")
@@ -1473,7 +1470,7 @@ class Run:
1473
1470
  files: FilesDict = dict(files=[(GlobStr(glob.escape(fname)), "now")])
1474
1471
  self._backend.interface.publish_files(files)
1475
1472
 
1476
- def _visualization_hack(self, row: Dict[str, Any]) -> Dict[str, Any]:
1473
+ def _visualization_hack(self, row: dict[str, Any]) -> dict[str, Any]:
1477
1474
  # TODO(jhr): move visualize hack somewhere else
1478
1475
  chart_keys = set()
1479
1476
  split_table_set = set()
@@ -1510,9 +1507,9 @@ class Run:
1510
1507
 
1511
1508
  def _partial_history_callback(
1512
1509
  self,
1513
- row: Dict[str, Any],
1514
- step: Optional[int] = None,
1515
- commit: Optional[bool] = None,
1510
+ row: dict[str, Any],
1511
+ step: int | None = None,
1512
+ commit: bool | None = None,
1516
1513
  ) -> None:
1517
1514
  row = row.copy()
1518
1515
  if row:
@@ -1559,19 +1556,19 @@ class Run:
1559
1556
  def _set_library(self, library: _WandbSetup) -> None:
1560
1557
  self._wl = library
1561
1558
 
1562
- def _set_backend(self, backend: "wandb.sdk.backend.backend.Backend") -> None:
1559
+ def _set_backend(self, backend: wandb.sdk.backend.backend.Backend) -> None:
1563
1560
  self._backend = backend
1564
1561
 
1565
1562
  def _set_internal_run_interface(
1566
1563
  self,
1567
- interface: "wandb.sdk.interface.interface_queue.InterfaceQueue",
1564
+ interface: wandb.sdk.interface.interface_queue.InterfaceQueue,
1568
1565
  ) -> None:
1569
1566
  self._internal_run_interface = interface
1570
1567
 
1571
1568
  def _set_reporter(self, reporter: Reporter) -> None:
1572
1569
  self._reporter = reporter
1573
1570
 
1574
- def _set_teardown_hooks(self, hooks: List[TeardownHook]) -> None:
1571
+ def _set_teardown_hooks(self, hooks: list[TeardownHook]) -> None:
1575
1572
  self._teardown_hooks = hooks
1576
1573
 
1577
1574
  def _set_run_obj(self, run_obj: RunRecord) -> None:
@@ -1609,7 +1606,7 @@ class Run:
1609
1606
  )
1610
1607
 
1611
1608
  def _add_singleton(
1612
- self, data_type: str, key: str, value: Dict[Union[int, str], str]
1609
+ self, data_type: str, key: str, value: dict[int | str, str]
1613
1610
  ) -> None:
1614
1611
  """Store a singleton item to wandb config.
1615
1612
 
@@ -1639,9 +1636,9 @@ class Run:
1639
1636
 
1640
1637
  def _log(
1641
1638
  self,
1642
- data: Dict[str, Any],
1643
- step: Optional[int] = None,
1644
- commit: Optional[bool] = None,
1639
+ data: dict[str, Any],
1640
+ step: int | None = None,
1641
+ commit: bool | None = None,
1645
1642
  ) -> None:
1646
1643
  if not isinstance(data, Mapping):
1647
1644
  raise ValueError("wandb.log must be passed a dictionary")
@@ -1680,10 +1677,10 @@ class Run:
1680
1677
  @_run_decorator._attach
1681
1678
  def log(
1682
1679
  self,
1683
- data: Dict[str, Any],
1684
- step: Optional[int] = None,
1685
- commit: Optional[bool] = None,
1686
- sync: Optional[bool] = None,
1680
+ data: dict[str, Any],
1681
+ step: int | None = None,
1682
+ commit: bool | None = None,
1683
+ sync: bool | None = None,
1687
1684
  ) -> None:
1688
1685
  """Upload run data.
1689
1686
 
@@ -1936,10 +1933,10 @@ class Run:
1936
1933
  @_run_decorator._attach
1937
1934
  def save(
1938
1935
  self,
1939
- glob_str: Optional[Union[str, os.PathLike]] = None,
1940
- base_path: Optional[Union[str, os.PathLike]] = None,
1936
+ glob_str: str | os.PathLike | None = None,
1937
+ base_path: str | os.PathLike | None = None,
1941
1938
  policy: PolicyName = "live",
1942
- ) -> Union[bool, List[str]]:
1939
+ ) -> bool | list[str]:
1943
1940
  """Sync one or more files to W&B.
1944
1941
 
1945
1942
  Relative paths are relative to the current working directory.
@@ -2044,8 +2041,8 @@ class Run:
2044
2041
  self,
2045
2042
  glob_path: pathlib.PurePath,
2046
2043
  base_path: pathlib.PurePath,
2047
- policy: "PolicyName",
2048
- ) -> List[str]:
2044
+ policy: PolicyName,
2045
+ ) -> list[str]:
2049
2046
  # Can't use is_relative_to() because that's added in Python 3.9,
2050
2047
  # but we support down to Python 3.7.
2051
2048
  if not str(glob_path).startswith(str(base_path)):
@@ -2128,10 +2125,10 @@ class Run:
2128
2125
  def restore(
2129
2126
  self,
2130
2127
  name: str,
2131
- run_path: Optional[str] = None,
2128
+ run_path: str | None = None,
2132
2129
  replace: bool = False,
2133
- root: Optional[str] = None,
2134
- ) -> Union[None, TextIO]:
2130
+ root: str | None = None,
2131
+ ) -> None | TextIO:
2135
2132
  return restore(
2136
2133
  name,
2137
2134
  run_path or self._get_path(),
@@ -2141,9 +2138,7 @@ class Run:
2141
2138
 
2142
2139
  @_run_decorator._noop
2143
2140
  @_run_decorator._attach
2144
- def finish(
2145
- self, exit_code: Optional[int] = None, quiet: Optional[bool] = None
2146
- ) -> None:
2141
+ def finish(self, exit_code: int | None = None, quiet: bool | None = None) -> None:
2147
2142
  """Mark a run as finished, and finish uploading all data.
2148
2143
 
2149
2144
  This is used when creating multiple runs in the same process. We automatically
@@ -2157,8 +2152,8 @@ class Run:
2157
2152
 
2158
2153
  def _finish(
2159
2154
  self,
2160
- exit_code: Optional[int] = None,
2161
- quiet: Optional[bool] = None,
2155
+ exit_code: int | None = None,
2156
+ quiet: bool | None = None,
2162
2157
  ) -> None:
2163
2158
  logger.info(f"finishing run {self._get_path()}")
2164
2159
  with telemetry.context(run=self) as tel:
@@ -2209,7 +2204,7 @@ class Run:
2209
2204
 
2210
2205
  @_run_decorator._noop
2211
2206
  @_run_decorator._attach
2212
- def join(self, exit_code: Optional[int] = None) -> None:
2207
+ def join(self, exit_code: int | None = None) -> None:
2213
2208
  """Deprecated alias for `finish()` - use finish instead."""
2214
2209
  if hasattr(self, "_telemetry_obj"):
2215
2210
  deprecate.deprecate(
@@ -2248,10 +2243,10 @@ class Run:
2248
2243
  @staticmethod
2249
2244
  def plot_table(
2250
2245
  vega_spec_name: str,
2251
- data_table: "wandb.Table",
2252
- fields: Dict[str, Any],
2253
- string_fields: Optional[Dict[str, Any]] = None,
2254
- split_table: Optional[bool] = False,
2246
+ data_table: Table,
2247
+ fields: dict[str, Any],
2248
+ string_fields: dict[str, Any] | None = None,
2249
+ split_table: bool | None = False,
2255
2250
  ) -> CustomChart:
2256
2251
  """Create a custom plot on a table.
2257
2252
 
@@ -2263,6 +2258,8 @@ class Run:
2263
2258
  visualization needs
2264
2259
  string_fields: a dict that provides values for any string constants
2265
2260
  the custom visualization needs
2261
+ split_table: a boolean that indicates whether the table should be in
2262
+ a separate section in the UI
2266
2263
  """
2267
2264
  return custom_chart(
2268
2265
  vega_spec_name, data_table, fields, string_fields or {}, split_table
@@ -2289,6 +2286,8 @@ class Run:
2289
2286
  define_metric=self.define_metric,
2290
2287
  plot_table=self.plot_table,
2291
2288
  alert=self.alert,
2289
+ watch=self.watch,
2290
+ unwatch=self.unwatch,
2292
2291
  mark_preempting=self.mark_preempting,
2293
2292
  log_model=self.log_model,
2294
2293
  use_model=self.use_model,
@@ -2297,9 +2296,9 @@ class Run:
2297
2296
 
2298
2297
  def _redirect(
2299
2298
  self,
2300
- stdout_slave_fd: Optional[int],
2301
- stderr_slave_fd: Optional[int],
2302
- console: Optional[str] = None,
2299
+ stdout_slave_fd: int | None,
2300
+ stderr_slave_fd: int | None,
2301
+ console: str | None = None,
2303
2302
  ) -> None:
2304
2303
  if console is None:
2305
2304
  console = self._settings.console
@@ -2414,7 +2413,7 @@ class Run:
2414
2413
  self._err_redir.uninstall()
2415
2414
  logger.info("restore done")
2416
2415
 
2417
- def _atexit_cleanup(self, exit_code: Optional[int] = None) -> None:
2416
+ def _atexit_cleanup(self, exit_code: int | None = None) -> None:
2418
2417
  if self._backend is None:
2419
2418
  logger.warning("process exited without backend configured")
2420
2419
  return
@@ -2525,7 +2524,7 @@ class Run:
2525
2524
  self,
2526
2525
  ) -> None:
2527
2526
  def _telemetry_import_hook(
2528
- run: "Run",
2527
+ run: Run,
2529
2528
  module: Any,
2530
2529
  ) -> None:
2531
2530
  with telemetry.context(run=run) as tel:
@@ -2568,7 +2567,7 @@ class Run:
2568
2567
 
2569
2568
  StagedLaunchInputs().apply(self)
2570
2569
 
2571
- def _make_job_source_reqs(self) -> Tuple[List[str], Dict[str, Any], Dict[str, Any]]:
2570
+ def _make_job_source_reqs(self) -> tuple[list[str], dict[str, Any], dict[str, Any]]:
2572
2571
  from wandb.util import working_set
2573
2572
 
2574
2573
  installed_packages_list = sorted(f"{d.key}=={d.version}" for d in working_set())
@@ -2580,10 +2579,10 @@ class Run:
2580
2579
  def _construct_job_artifact(
2581
2580
  self,
2582
2581
  name: str,
2583
- source_dict: "JobSourceDict",
2584
- installed_packages_list: List[str],
2585
- patch_path: Optional[os.PathLike] = None,
2586
- ) -> "Artifact":
2582
+ source_dict: JobSourceDict,
2583
+ installed_packages_list: list[str],
2584
+ patch_path: os.PathLike | None = None,
2585
+ ) -> Artifact:
2587
2586
  job_artifact = job_builder.JobArtifact(name)
2588
2587
  if patch_path and os.path.exists(patch_path):
2589
2588
  job_artifact.add_file(FilePathStr(str(patch_path)), "diff.patch")
@@ -2596,12 +2595,12 @@ class Run:
2596
2595
 
2597
2596
  def _create_image_job(
2598
2597
  self,
2599
- input_types: Dict[str, Any],
2600
- output_types: Dict[str, Any],
2601
- installed_packages_list: List[str],
2602
- docker_image_name: Optional[str] = None,
2603
- args: Optional[List[str]] = None,
2604
- ) -> Optional["Artifact"]:
2598
+ input_types: dict[str, Any],
2599
+ output_types: dict[str, Any],
2600
+ installed_packages_list: list[str],
2601
+ docker_image_name: str | None = None,
2602
+ args: list[str] | None = None,
2603
+ ) -> Artifact | None:
2605
2604
  docker_image_name = docker_image_name or os.getenv("WANDB_DOCKER")
2606
2605
 
2607
2606
  if not docker_image_name:
@@ -2624,7 +2623,7 @@ class Run:
2624
2623
  return job_artifact
2625
2624
 
2626
2625
  def _log_job_artifact_with_image(
2627
- self, docker_image_name: str, args: Optional[List[str]] = None
2626
+ self, docker_image_name: str, args: list[str] | None = None
2628
2627
  ) -> Artifact:
2629
2628
  packages, in_types, out_types = self._make_job_source_reqs()
2630
2629
  job_artifact = self._create_image_job(
@@ -2653,16 +2652,20 @@ class Run:
2653
2652
  handle = self._backend.interface.deliver_poll_exit()
2654
2653
  probe_handle.set_mailbox_handle(handle)
2655
2654
 
2656
- def _on_progress_exit(self, progress_handle: MailboxProgress) -> None:
2655
+ def _on_progress_exit(
2656
+ self,
2657
+ progress_printer: progress.ProgressPrinter,
2658
+ progress_handle: MailboxProgress,
2659
+ ) -> None:
2657
2660
  probe_handles = progress_handle.get_probe_handles()
2658
- assert probe_handles and len(probe_handles) == 1
2661
+ if not probe_handles or len(probe_handles) != 1:
2662
+ return
2659
2663
 
2660
2664
  result = probe_handles[0].get_probe_result()
2661
2665
  if not result:
2662
2666
  return
2663
- self._footer_file_pusher_status_info(
2664
- result.response.poll_exit_response, printer=self._printer
2665
- )
2667
+
2668
+ progress_printer.update([result.response.poll_exit_response])
2666
2669
 
2667
2670
  def _on_finish(self) -> None:
2668
2671
  trigger.call("on_finished")
@@ -2677,16 +2680,28 @@ class Run:
2677
2680
  exit_handle = self._backend.interface.deliver_exit(self._exit_code)
2678
2681
  exit_handle.add_probe(on_probe=self._on_probe_exit)
2679
2682
 
2680
- # wait for the exit to complete
2681
- _ = exit_handle.wait(timeout=-1, on_progress=self._on_progress_exit)
2683
+ with progress.progress_printer(
2684
+ self._printer,
2685
+ self._settings,
2686
+ ) as progress_printer:
2687
+ # Wait for the run to complete.
2688
+ _ = exit_handle.wait(
2689
+ timeout=-1,
2690
+ on_progress=functools.partial(
2691
+ self._on_progress_exit,
2692
+ progress_printer,
2693
+ ),
2694
+ )
2682
2695
 
2696
+ # Print some final statistics.
2683
2697
  poll_exit_handle = self._backend.interface.deliver_poll_exit()
2684
- # wait for them, it's ok to do this serially but this can be improved
2685
2698
  result = poll_exit_handle.wait(timeout=-1)
2686
2699
  assert result
2687
- self._footer_file_pusher_status_info(
2688
- result.response.poll_exit_response, printer=self._printer
2700
+ progress.print_sync_dedupe_stats(
2701
+ self._printer,
2702
+ result.response.poll_exit_response,
2689
2703
  )
2704
+
2690
2705
  self._poll_exit_response = result.response.poll_exit_response
2691
2706
  internal_messages_handle = self._backend.interface.deliver_internal_messages()
2692
2707
  result = internal_messages_handle.wait(timeout=-1)
@@ -2727,12 +2742,12 @@ class Run:
2727
2742
  def define_metric(
2728
2743
  self,
2729
2744
  name: str,
2730
- step_metric: Union[str, wandb_metric.Metric, None] = None,
2731
- step_sync: Optional[bool] = None,
2732
- hidden: Optional[bool] = None,
2733
- summary: Optional[str] = None,
2734
- goal: Optional[str] = None,
2735
- overwrite: Optional[bool] = None,
2745
+ step_metric: str | wandb_metric.Metric | None = None,
2746
+ step_sync: bool | None = None,
2747
+ hidden: bool | None = None,
2748
+ summary: str | None = None,
2749
+ goal: str | None = None,
2750
+ overwrite: bool | None = None,
2736
2751
  ) -> wandb_metric.Metric:
2737
2752
  """Customize metrics logged with `wandb.log()`.
2738
2753
 
@@ -2788,12 +2803,12 @@ class Run:
2788
2803
  def _define_metric(
2789
2804
  self,
2790
2805
  name: str,
2791
- step_metric: Union[str, wandb_metric.Metric, None] = None,
2792
- step_sync: Optional[bool] = None,
2793
- hidden: Optional[bool] = None,
2794
- summary: Optional[str] = None,
2795
- goal: Optional[str] = None,
2796
- overwrite: Optional[bool] = None,
2806
+ step_metric: str | wandb_metric.Metric | None = None,
2807
+ step_sync: bool | None = None,
2808
+ hidden: bool | None = None,
2809
+ summary: str | None = None,
2810
+ goal: str | None = None,
2811
+ overwrite: bool | None = None,
2797
2812
  ) -> wandb_metric.Metric:
2798
2813
  if not name:
2799
2814
  raise wandb.Error("define_metric() requires non-empty name argument")
@@ -2819,7 +2834,7 @@ class Run:
2819
2834
  raise wandb.Error(
2820
2835
  f"Unhandled define_metric() arg: name (glob suffixes only): {name}"
2821
2836
  )
2822
- summary_ops: Optional[Sequence[str]] = None
2837
+ summary_ops: Sequence[str] | None = None
2823
2838
  if summary:
2824
2839
  summary_items = [s.lower() for s in summary.split(",")]
2825
2840
  summary_ops = []
@@ -2832,7 +2847,7 @@ class Run:
2832
2847
  with telemetry.context(run=self) as tel:
2833
2848
  tel.feature.metric_summary = True
2834
2849
  # TODO: deprecate goal
2835
- goal_cleaned: Optional[str] = None
2850
+ goal_cleaned: str | None = None
2836
2851
  if goal is not None:
2837
2852
  goal_cleaned = goal[:3].lower()
2838
2853
  valid_goal = {"min", "max"}
@@ -2863,26 +2878,57 @@ class Run:
2863
2878
  m._commit()
2864
2879
  return m
2865
2880
 
2866
- # TODO(jhr): annotate this
2867
2881
  @_run_decorator._attach
2868
- def watch( # type: ignore
2882
+ def watch(
2869
2883
  self,
2870
- models,
2871
- criterion=None,
2872
- log="gradients",
2873
- log_freq=100,
2874
- idx=None,
2875
- log_graph=False,
2884
+ models: torch.nn.Module | Sequence[torch.nn.Module],
2885
+ criterion: torch.F | None = None,
2886
+ log: Literal["gradients", "parameters", "all"] | None = "gradients",
2887
+ log_freq: int = 1000,
2888
+ idx: int | None = None,
2889
+ log_graph: bool = False,
2876
2890
  ) -> None:
2877
- wandb.watch(models, criterion, log, log_freq, idx, log_graph) # type: ignore
2891
+ """Hooks into the given PyTorch model(s) to monitor gradients and the model's computational graph.
2892
+
2893
+ This function can track parameters, gradients, or both during training. It should be
2894
+ extended to support arbitrary machine learning models in the future.
2895
+
2896
+ Args:
2897
+ models (Union[torch.nn.Module, Sequence[torch.nn.Module]]):
2898
+ A single model or a sequence of models to be monitored.
2899
+ criterion (Optional[torch.F]):
2900
+ The loss function being optimized (optional).
2901
+ log (Optional[Literal["gradients", "parameters", "all"]]):
2902
+ Specifies whether to log "gradients", "parameters", or "all".
2903
+ Set to None to disable logging. (default="gradients")
2904
+ log_freq (int):
2905
+ Frequency (in batches) to log gradients and parameters. (default=1000)
2906
+ idx (Optional[int]):
2907
+ Index used when tracking multiple models with `wandb.watch`. (default=None)
2908
+ log_graph (bool):
2909
+ Whether to log the model's computational graph. (default=False)
2910
+
2911
+ Raises:
2912
+ ValueError:
2913
+ If `wandb.init` has not been called or if any of the models are not instances
2914
+ of `torch.nn.Module`.
2915
+ """
2916
+ wandb.sdk._watch(self, models, criterion, log, log_freq, idx, log_graph)
2878
2917
 
2879
- # TODO(jhr): annotate this
2880
2918
  @_run_decorator._attach
2881
- def unwatch(self, models=None) -> None: # type: ignore
2882
- wandb.unwatch(models=models) # type: ignore
2919
+ def unwatch(
2920
+ self, models: torch.nn.Module | Sequence[torch.nn.Module] | None = None
2921
+ ) -> None:
2922
+ """Remove pytorch model topology, gradient and parameter hooks.
2923
+
2924
+ Args:
2925
+ models (torch.nn.Module | Sequence[torch.nn.Module]):
2926
+ Optional list of pytorch models that have had watch called on them
2927
+ """
2928
+ wandb.sdk._unwatch(self, models=models)
2883
2929
 
2884
2930
  # TODO(kdg): remove all artifact swapping logic
2885
- def _swap_artifact_name(self, artifact_name: str, use_as: Optional[str]) -> str:
2931
+ def _swap_artifact_name(self, artifact_name: str, use_as: str | None) -> str:
2886
2932
  artifact_key_string = use_as or artifact_name
2887
2933
  replacement_artifact_info = self._launch_artifact_mapping.get(
2888
2934
  artifact_key_string
@@ -2925,7 +2971,7 @@ class Run:
2925
2971
  self,
2926
2972
  artifact: Artifact,
2927
2973
  target_path: str,
2928
- aliases: Optional[List[str]] = None,
2974
+ aliases: list[str] | None = None,
2929
2975
  ) -> None:
2930
2976
  """Link the given artifact to a portfolio (a promoted collection of artifacts).
2931
2977
 
@@ -2947,41 +2993,54 @@ class Run:
2947
2993
  if aliases is None:
2948
2994
  aliases = []
2949
2995
 
2950
- if self._backend and self._backend.interface:
2951
- if artifact.is_draft() and not artifact._is_draft_save_started():
2952
- artifact = self._log_artifact(artifact)
2953
- if not self._settings._offline:
2954
- handle = self._backend.interface.deliver_link_artifact(
2955
- self,
2956
- artifact,
2957
- portfolio,
2958
- aliases,
2959
- entity,
2960
- project,
2961
- )
2962
- if artifact._ttl_duration_seconds is not None:
2963
- wandb.termwarn(
2964
- "Artifact TTL will be disabled for source artifacts that are linked to portfolios."
2965
- )
2966
- result = handle.wait(timeout=-1)
2967
- if result is None:
2968
- handle.abandon()
2969
- else:
2970
- response = result.response.link_artifact_response
2971
- if response.error_message:
2972
- wandb.termerror(response.error_message)
2973
- else:
2974
- # TODO: implement offline mode + sync
2975
- raise NotImplementedError
2996
+ if not self._backend or not self._backend.interface:
2997
+ return
2998
+
2999
+ if artifact.is_draft() and not artifact._is_draft_save_started():
3000
+ artifact = self._log_artifact(artifact)
3001
+
3002
+ if self._settings._offline:
3003
+ # TODO: implement offline mode + sync
3004
+ raise NotImplementedError
3005
+
3006
+ # Wait until the artifact is committed before trying to link it.
3007
+ artifact.wait()
3008
+
3009
+ organization = ""
3010
+ if is_artifact_registry_project(project):
3011
+ organization = entity
3012
+ # In a Registry linking, the entity is used to fetch the organization of the artifact
3013
+ # therefore the source artifact's entity is passed to the backend
3014
+ entity = artifact._source_entity
3015
+ handle = self._backend.interface.deliver_link_artifact(
3016
+ self,
3017
+ artifact,
3018
+ portfolio,
3019
+ aliases,
3020
+ entity,
3021
+ project,
3022
+ organization,
3023
+ )
3024
+ if artifact._ttl_duration_seconds is not None:
3025
+ wandb.termwarn(
3026
+ "Artifact TTL will be disabled for source artifacts that are linked to portfolios."
3027
+ )
3028
+ result = handle.wait(timeout=-1)
3029
+ if result is None:
3030
+ handle.abandon()
3031
+ else:
3032
+ response = result.response.link_artifact_response
3033
+ if response.error_message:
3034
+ wandb.termerror(response.error_message)
2976
3035
 
2977
3036
  @_run_decorator._noop_on_finish()
2978
3037
  @_run_decorator._attach
2979
3038
  def use_artifact(
2980
3039
  self,
2981
- artifact_or_name: Union[str, Artifact],
2982
- type: Optional[str] = None,
2983
- aliases: Optional[List[str]] = None,
2984
- use_as: Optional[str] = None,
3040
+ artifact_or_name: str | Artifact,
3041
+ type: str | None = None,
3042
+ aliases: list[str] | None = None,
3043
+ use_as: str | None = None,
2985
3044
  ) -> Artifact:
2986
3045
  """Declare an artifact as an input to a run.
2987
3046
 
@@ -2989,8 +3048,9 @@ class Run:
2989
3048
 
2990
3049
  Arguments:
2991
3050
  artifact_or_name: (str or Artifact) An artifact name.
2992
- May be prefixed with entity/project/. Valid names
2993
- can be in the following forms:
3051
+ May be prefixed with project/ or entity/project/.
3052
+ If no entity is specified in the name, the Run or API setting's entity is used.
3053
+ Valid names can be in the following forms:
2994
3054
  - name:version
2995
3055
  - name:alias
2996
3056
  You can also pass an Artifact object created by calling `wandb.Artifact`
@@ -3087,11 +3147,11 @@ class Run:
3087
3147
  @_run_decorator._attach
3088
3148
  def log_artifact(
3089
3149
  self,
3090
- artifact_or_path: Union[Artifact, StrPath],
3091
- name: Optional[str] = None,
3092
- type: Optional[str] = None,
3093
- aliases: Optional[List[str]] = None,
3094
- tags: Optional[List[str]] = None,
3150
+ artifact_or_path: Artifact | StrPath,
3151
+ name: str | None = None,
3152
+ type: str | None = None,
3153
+ aliases: list[str] | None = None,
3154
+ tags: list[str] | None = None,
3095
3155
  ) -> Artifact:
3096
3156
  """Declare an artifact as an output of a run.
3097
3157
 
@@ -3129,11 +3189,11 @@ class Run:
3129
3189
  @_run_decorator._attach
3130
3190
  def upsert_artifact(
3131
3191
  self,
3132
- artifact_or_path: Union[Artifact, str],
3133
- name: Optional[str] = None,
3134
- type: Optional[str] = None,
3135
- aliases: Optional[List[str]] = None,
3136
- distributed_id: Optional[str] = None,
3192
+ artifact_or_path: Artifact | str,
3193
+ name: str | None = None,
3194
+ type: str | None = None,
3195
+ aliases: list[str] | None = None,
3196
+ distributed_id: str | None = None,
3137
3197
  ) -> Artifact:
3138
3198
  """Declare (or append to) a non-finalized artifact as output of a run.
3139
3199
 
@@ -3183,11 +3243,11 @@ class Run:
3183
3243
  @_run_decorator._attach
3184
3244
  def finish_artifact(
3185
3245
  self,
3186
- artifact_or_path: Union[Artifact, str],
3187
- name: Optional[str] = None,
3188
- type: Optional[str] = None,
3189
- aliases: Optional[List[str]] = None,
3190
- distributed_id: Optional[str] = None,
3246
+ artifact_or_path: Artifact | str,
3247
+ name: str | None = None,
3248
+ type: str | None = None,
3249
+ aliases: list[str] | None = None,
3250
+ distributed_id: str | None = None,
3191
3251
  ) -> Artifact:
3192
3252
  """Finishes a non-finalized artifact as output of a run.
3193
3253
 
@@ -3235,12 +3295,12 @@ class Run:
3235
3295
 
3236
3296
  def _log_artifact(
3237
3297
  self,
3238
- artifact_or_path: Union[Artifact, StrPath],
3239
- name: Optional[str] = None,
3240
- type: Optional[str] = None,
3241
- aliases: Optional[List[str]] = None,
3242
- tags: Optional[List[str]] = None,
3243
- distributed_id: Optional[str] = None,
3298
+ artifact_or_path: Artifact | StrPath,
3299
+ name: str | None = None,
3300
+ type: str | None = None,
3301
+ aliases: list[str] | None = None,
3302
+ tags: list[str] | None = None,
3303
+ distributed_id: str | None = None,
3244
3304
  finalize: bool = True,
3245
3305
  is_user_created: bool = False,
3246
3306
  use_after_commit: bool = False,
@@ -3301,7 +3361,7 @@ class Run:
3301
3361
  )
3302
3362
  return artifact
3303
3363
 
3304
- def _public_api(self, overrides: Optional[Dict[str, str]] = None) -> PublicApi:
3364
+ def _public_api(self, overrides: dict[str, str] | None = None) -> PublicApi:
3305
3365
  overrides = {"run": self._run_id}
3306
3366
  if not (self._settings._offline or self._run_obj is None):
3307
3367
  overrides["entity"] = self._run_obj.entity
@@ -3342,11 +3402,11 @@ class Run:
3342
3402
 
3343
3403
  def _prepare_artifact(
3344
3404
  self,
3345
- artifact_or_path: Union[Artifact, StrPath],
3346
- name: Optional[str] = None,
3347
- type: Optional[str] = None,
3348
- aliases: Optional[List[str]] = None,
3349
- ) -> Tuple[Artifact, List[str]]:
3405
+ artifact_or_path: Artifact | StrPath,
3406
+ name: str | None = None,
3407
+ type: str | None = None,
3408
+ aliases: list[str] | None = None,
3409
+ ) -> tuple[Artifact, list[str]]:
3350
3410
  if isinstance(artifact_or_path, (str, os.PathLike)):
3351
3411
  name = name or f"run-{self._run_id}-{os.path.basename(artifact_or_path)}"
3352
3412
  artifact = wandb.Artifact(name, type or "unspecified")
@@ -3377,8 +3437,8 @@ class Run:
3377
3437
  def log_model(
3378
3438
  self,
3379
3439
  path: StrPath,
3380
- name: Optional[str] = None,
3381
- aliases: Optional[List[str]] = None,
3440
+ name: str | None = None,
3441
+ aliases: list[str] | None = None,
3382
3442
  ) -> None:
3383
3443
  """Logs a model artifact containing the contents inside the 'path' to a run and marks it as an output to this run.
3384
3444
 
@@ -3482,8 +3542,8 @@ class Run:
3482
3542
  self,
3483
3543
  path: StrPath,
3484
3544
  registered_model_name: str,
3485
- name: Optional[str] = None,
3486
- aliases: Optional[List[str]] = None,
3545
+ name: str | None = None,
3546
+ aliases: list[str] | None = None,
3487
3547
  ) -> None:
3488
3548
  """Log a model artifact version and link it to a registered model in the model registry.
3489
3549
 
@@ -3576,8 +3636,8 @@ class Run:
3576
3636
  self,
3577
3637
  title: str,
3578
3638
  text: str,
3579
- level: Optional[Union[str, "AlertLevel"]] = None,
3580
- wait_duration: Union[int, float, timedelta, None] = None,
3639
+ level: str | AlertLevel | None = None,
3640
+ wait_duration: int | float | timedelta | None = None,
3581
3641
  ) -> None:
3582
3642
  """Launch an alert with the given title and text.
3583
3643
 
@@ -3605,12 +3665,12 @@ class Run:
3605
3665
  if self._backend and self._backend.interface:
3606
3666
  self._backend.interface.publish_alert(title, text, level_str, wait_duration)
3607
3667
 
3608
- def __enter__(self) -> "Run":
3668
+ def __enter__(self) -> Run:
3609
3669
  return self
3610
3670
 
3611
3671
  def __exit__(
3612
3672
  self,
3613
- exc_type: Type[BaseException],
3673
+ exc_type: type[BaseException],
3614
3674
  exc_val: BaseException,
3615
3675
  exc_tb: TracebackType,
3616
3676
  ) -> bool:
@@ -3634,7 +3694,7 @@ class Run:
3634
3694
  @property
3635
3695
  @_run_decorator._noop_on_finish()
3636
3696
  @_run_decorator._attach
3637
- def _system_metrics(self) -> Dict[str, List[Tuple[datetime, float]]]:
3697
+ def _system_metrics(self) -> dict[str, list[tuple[datetime, float]]]:
3638
3698
  """Returns a dictionary of system metrics.
3639
3699
 
3640
3700
  Returns:
@@ -3643,7 +3703,7 @@ class Run:
3643
3703
 
3644
3704
  def pb_to_dict(
3645
3705
  system_metrics_pb: wandb.proto.wandb_internal_pb2.GetSystemMetricsResponse,
3646
- ) -> Dict[str, List[Tuple[datetime, float]]]:
3706
+ ) -> dict[str, list[tuple[datetime, float]]]:
3647
3707
  res = {}
3648
3708
 
3649
3709
  for metric, records in system_metrics_pb.system_metrics.items():
@@ -3684,8 +3744,8 @@ class Run:
3684
3744
  @staticmethod
3685
3745
  def _header(
3686
3746
  *,
3687
- settings: "Settings",
3688
- printer: Union["PrinterTerm", "PrinterJupyter"],
3747
+ settings: Settings,
3748
+ printer: printer.Printer,
3689
3749
  ) -> None:
3690
3750
  Run._header_wandb_version_info(settings=settings, printer=printer)
3691
3751
  Run._header_sync_info(settings=settings, printer=printer)
@@ -3694,22 +3754,20 @@ class Run:
3694
3754
  @staticmethod
3695
3755
  def _header_wandb_version_info(
3696
3756
  *,
3697
- settings: "Settings",
3698
- printer: Union["PrinterTerm", "PrinterJupyter"],
3757
+ settings: Settings,
3758
+ printer: printer.Printer,
3699
3759
  ) -> None:
3700
3760
  if settings.quiet or settings.silent:
3701
3761
  return
3702
3762
 
3703
3763
  # TODO: add this to a higher verbosity level
3704
- printer.display(
3705
- f"Tracking run with wandb version {wandb.__version__}", off=False
3706
- )
3764
+ printer.display(f"Tracking run with wandb version {wandb.__version__}")
3707
3765
 
3708
3766
  @staticmethod
3709
3767
  def _header_sync_info(
3710
3768
  *,
3711
- settings: "Settings",
3712
- printer: Union["PrinterTerm", "PrinterJupyter"],
3769
+ settings: Settings,
3770
+ printer: printer.Printer,
3713
3771
  ) -> None:
3714
3772
  if settings._offline:
3715
3773
  printer.display(
@@ -3721,17 +3779,18 @@ class Run:
3721
3779
  )
3722
3780
  else:
3723
3781
  info = [f"Run data is saved locally in {printer.files(settings.sync_dir)}"]
3724
- if not printer._html:
3782
+ if not printer.supports_html:
3725
3783
  info.append(
3726
3784
  f"Run {printer.code('`wandb offline`')} to turn off syncing."
3727
3785
  )
3728
- printer.display(info, off=settings.quiet or settings.silent)
3786
+ if not settings.quiet and not settings.silent:
3787
+ printer.display(info)
3729
3788
 
3730
3789
  @staticmethod
3731
3790
  def _header_run_info(
3732
3791
  *,
3733
- settings: "Settings",
3734
- printer: Union["PrinterTerm", "PrinterJupyter"],
3792
+ settings: Settings,
3793
+ printer: printer.Printer,
3735
3794
  ) -> None:
3736
3795
  if settings._offline or settings.silent:
3737
3796
  return
@@ -3749,7 +3808,7 @@ class Run:
3749
3808
  if not run_name:
3750
3809
  return
3751
3810
 
3752
- if printer._html:
3811
+ if printer.supports_html:
3753
3812
  if not wandb.jupyter.maybe_display(): # type: ignore
3754
3813
  run_line = f"<strong>{printer.link(run_url, run_name)}</strong>"
3755
3814
  project_line, sweep_line = "", ""
@@ -3768,10 +3827,8 @@ class Run:
3768
3827
  [f"{run_state_str} {run_line} {project_line}", sweep_line],
3769
3828
  )
3770
3829
 
3771
- else:
3772
- printer.display(
3773
- f"{run_state_str} {printer.name(run_name)}", off=not run_name
3774
- )
3830
+ elif run_name:
3831
+ printer.display(f"{run_state_str} {printer.name(run_name)}")
3775
3832
 
3776
3833
  if not settings.quiet:
3777
3834
  # TODO: add verbosity levels and add this to higher levels
@@ -3787,11 +3844,13 @@ class Run:
3787
3844
  )
3788
3845
 
3789
3846
  # TODO(settings) use `wandb_settings` (if self.settings.anonymous == "true":)
3790
- if Api().api.settings().get("anonymous") == "true":
3847
+ if run_name and Api().api.settings().get("anonymous") == "true":
3791
3848
  printer.display(
3792
- "Do NOT share these links with anyone. They can be used to claim your runs.",
3849
+ (
3850
+ "Do NOT share these links with anyone."
3851
+ " They can be used to claim your runs."
3852
+ ),
3793
3853
  level="warn",
3794
- off=not run_name,
3795
3854
  )
3796
3855
 
3797
3856
  # ------------------------------------------------------------------------------
@@ -3801,15 +3860,15 @@ class Run:
3801
3860
  # with the service execution path that doesn't have access to the run instance
3802
3861
  @staticmethod
3803
3862
  def _footer(
3804
- sampled_history: Optional["SampledHistoryResponse"] = None,
3805
- final_summary: Optional["GetSummaryResponse"] = None,
3806
- poll_exit_response: Optional[PollExitResponse] = None,
3807
- internal_messages_response: Optional["InternalMessagesResponse"] = None,
3808
- reporter: Optional[Reporter] = None,
3809
- quiet: Optional[bool] = None,
3863
+ sampled_history: SampledHistoryResponse | None = None,
3864
+ final_summary: GetSummaryResponse | None = None,
3865
+ poll_exit_response: PollExitResponse | None = None,
3866
+ internal_messages_response: InternalMessagesResponse | None = None,
3867
+ reporter: Reporter | None = None,
3868
+ quiet: bool | None = None,
3810
3869
  *,
3811
- settings: "Settings",
3812
- printer: Union["PrinterTerm", "PrinterJupyter"],
3870
+ settings: Settings,
3871
+ printer: printer.Printer,
3813
3872
  ) -> None:
3814
3873
  Run._footer_history_summary_info(
3815
3874
  history=sampled_history,
@@ -3841,177 +3900,51 @@ class Run:
3841
3900
  reporter=reporter, quiet=quiet, settings=settings, printer=printer
3842
3901
  )
3843
3902
 
3844
- # fixme: Temporary hack until we move to rich which allows multiple spinners
3845
3903
  @staticmethod
3846
- def _footer_file_pusher_status_info(
3847
- poll_exit_responses: Optional[
3848
- Union[PollExitResponse, List[Optional[PollExitResponse]]]
3849
- ] = None,
3850
- *,
3851
- printer: Union["PrinterTerm", "PrinterJupyter"],
3852
- ) -> None:
3853
- if not poll_exit_responses:
3854
- return
3855
- if isinstance(poll_exit_responses, PollExitResponse):
3856
- Run._footer_single_run_file_pusher_status_info(
3857
- poll_exit_responses, printer=printer
3858
- )
3859
- elif isinstance(poll_exit_responses, list):
3860
- poll_exit_responses_list = poll_exit_responses
3861
- assert all(
3862
- response is None or isinstance(response, PollExitResponse)
3863
- for response in poll_exit_responses_list
3864
- )
3865
- if len(poll_exit_responses_list) == 0:
3866
- return
3867
- elif len(poll_exit_responses_list) == 1:
3868
- Run._footer_single_run_file_pusher_status_info(
3869
- poll_exit_responses_list[0], printer=printer
3870
- )
3871
- else:
3872
- Run._footer_multiple_runs_file_pusher_status_info(
3873
- poll_exit_responses_list, printer=printer
3874
- )
3875
- else:
3876
- logger.error(
3877
- f"Got the type `{type(poll_exit_responses)}` for `poll_exit_responses`. "
3878
- "Expected either None, PollExitResponse or a List[Union[PollExitResponse, None]]"
3879
- )
3880
-
3881
- @staticmethod
3882
- def _footer_single_run_file_pusher_status_info(
3883
- poll_exit_response: Optional[PollExitResponse] = None,
3904
+ def _footer_sync_info(
3905
+ poll_exit_response: PollExitResponse | None = None,
3906
+ quiet: bool | None = None,
3884
3907
  *,
3885
- printer: Union["PrinterTerm", "PrinterJupyter"],
3908
+ settings: Settings,
3909
+ printer: printer.Printer,
3886
3910
  ) -> None:
3887
- # todo: is this same as settings._offline?
3888
- if not poll_exit_response:
3911
+ if settings.silent:
3889
3912
  return
3890
3913
 
3891
- stats = poll_exit_response.pusher_stats
3892
-
3893
- megabyte = wandb.util.POW_2_BYTES[2][1]
3894
- line = (
3895
- f"{stats.uploaded_bytes / megabyte:.3f} MB"
3896
- f" of {stats.total_bytes / megabyte:.3f} MB uploaded"
3897
- )
3898
- if stats.deduped_bytes > 0:
3899
- line += f" ({stats.deduped_bytes / megabyte:.3f} MB deduped)"
3900
- line += "\r"
3901
-
3902
- if stats.total_bytes > 0:
3903
- printer.progress_update(line, stats.uploaded_bytes / stats.total_bytes)
3904
- else:
3905
- printer.progress_update(line, 1.0)
3906
-
3907
- if poll_exit_response.done:
3908
- printer.progress_close()
3909
-
3910
- if stats.total_bytes > 0:
3911
- dedupe_fraction = stats.deduped_bytes / float(stats.total_bytes)
3912
- else:
3913
- dedupe_fraction = 0
3914
-
3915
- if stats.deduped_bytes > 0.01:
3914
+ if settings._offline:
3915
+ if not quiet and not settings.quiet:
3916
3916
  printer.display(
3917
- f"W&B sync reduced upload amount by {dedupe_fraction:.1%}"
3917
+ [
3918
+ "You can sync this run to the cloud by running:",
3919
+ printer.code(f"wandb sync {settings.sync_dir}"),
3920
+ ],
3918
3921
  )
3919
-
3920
- @staticmethod
3921
- def _footer_multiple_runs_file_pusher_status_info(
3922
- poll_exit_responses: List[Optional[PollExitResponse]],
3923
- *,
3924
- printer: Union["PrinterTerm", "PrinterJupyter"],
3925
- ) -> None:
3926
- # todo: is this same as settings._offline?
3927
- if not all(poll_exit_responses):
3928
3922
  return
3929
3923
 
3930
- megabyte = wandb.util.POW_2_BYTES[2][1]
3931
- total_files: int = sum(
3932
- sum(
3933
- [
3934
- response.file_counts.wandb_count,
3935
- response.file_counts.media_count,
3936
- response.file_counts.artifact_count,
3937
- response.file_counts.other_count,
3938
- ]
3924
+ info = []
3925
+ if settings.run_name and settings.run_url:
3926
+ info.append(
3927
+ f"{printer.emoji('rocket')} View run {printer.name(settings.run_name)} at: {printer.link(settings.run_url)}"
3939
3928
  )
3940
- for response in poll_exit_responses
3941
- if response is not None and response.file_counts is not None
3942
- )
3943
- uploaded = sum(
3944
- response.pusher_stats.uploaded_bytes
3945
- for response in poll_exit_responses
3946
- if response is not None and response.pusher_stats is not None
3947
- )
3948
- total = sum(
3949
- response.pusher_stats.total_bytes
3950
- for response in poll_exit_responses
3951
- if response is not None and response.pusher_stats is not None
3952
- )
3953
-
3954
- line = (
3955
- f"Processing {len(poll_exit_responses)} runs with {total_files} files "
3956
- f"({uploaded/megabyte :.2f} MB/{total/megabyte :.2f} MB)\r"
3957
- )
3958
- # line = "{}{:<{max_len}}\r".format(line, " ", max_len=(80 - len(line)))
3959
- printer.progress_update(line) # type:ignore[call-arg]
3960
-
3961
- done = all(
3962
- [
3963
- poll_exit_response.done
3964
- for poll_exit_response in poll_exit_responses
3965
- if poll_exit_response
3966
- ]
3967
- )
3968
- if done:
3969
- printer.progress_close()
3970
-
3971
- @staticmethod
3972
- def _footer_sync_info(
3973
- poll_exit_response: Optional[PollExitResponse] = None,
3974
- quiet: Optional[bool] = None,
3975
- *,
3976
- settings: "Settings",
3977
- printer: Union["PrinterTerm", "PrinterJupyter"],
3978
- ) -> None:
3979
- if settings.silent:
3980
- return
3981
-
3982
- if settings._offline:
3983
- printer.display(
3984
- [
3985
- "You can sync this run to the cloud by running:",
3986
- printer.code(f"wandb sync {settings.sync_dir}"),
3987
- ],
3988
- off=(quiet or settings.quiet),
3929
+ if settings.project_url:
3930
+ info.append(
3931
+ f"{printer.emoji('star')} View project at: {printer.link(settings.project_url)}"
3989
3932
  )
3990
- else:
3991
- info = []
3992
- if settings.run_name and settings.run_url:
3993
- info.append(
3994
- f"{printer.emoji('rocket')} View run {printer.name(settings.run_name)} at: {printer.link(settings.run_url)}"
3995
- )
3996
- if settings.project_url:
3997
- info.append(
3998
- f"{printer.emoji('star')} View project at: {printer.link(settings.project_url)}"
3999
- )
4000
- if poll_exit_response and poll_exit_response.file_counts:
4001
- logger.info("logging synced files")
4002
- file_counts = poll_exit_response.file_counts
4003
- info.append(
4004
- f"Synced {file_counts.wandb_count} W&B file(s), {file_counts.media_count} media file(s), "
4005
- f"{file_counts.artifact_count} artifact file(s) and {file_counts.other_count} other file(s)",
4006
- )
4007
- printer.display(info)
3933
+ if poll_exit_response and poll_exit_response.file_counts:
3934
+ logger.info("logging synced files")
3935
+ file_counts = poll_exit_response.file_counts
3936
+ info.append(
3937
+ f"Synced {file_counts.wandb_count} W&B file(s), {file_counts.media_count} media file(s), "
3938
+ f"{file_counts.artifact_count} artifact file(s) and {file_counts.other_count} other file(s)",
3939
+ )
3940
+ printer.display(info)
4008
3941
 
4009
3942
  @staticmethod
4010
3943
  def _footer_log_dir_info(
4011
- quiet: Optional[bool] = None,
3944
+ quiet: bool | None = None,
4012
3945
  *,
4013
- settings: "Settings",
4014
- printer: Union["PrinterTerm", "PrinterJupyter"],
3946
+ settings: Settings,
3947
+ printer: printer.Printer,
4015
3948
  ) -> None:
4016
3949
  if (quiet or settings.quiet) or settings.silent:
4017
3950
  return
@@ -4025,12 +3958,12 @@ class Run:
4025
3958
 
4026
3959
  @staticmethod
4027
3960
  def _footer_history_summary_info(
4028
- history: Optional["SampledHistoryResponse"] = None,
4029
- summary: Optional["GetSummaryResponse"] = None,
4030
- quiet: Optional[bool] = None,
3961
+ history: SampledHistoryResponse | None = None,
3962
+ summary: GetSummaryResponse | None = None,
3963
+ quiet: bool | None = None,
4031
3964
  *,
4032
- settings: "Settings",
4033
- printer: Union["PrinterTerm", "PrinterJupyter"],
3965
+ settings: Settings,
3966
+ printer: printer.Printer,
4034
3967
  ) -> None:
4035
3968
  if (quiet or settings.quiet) or settings.silent:
4036
3969
  return
@@ -4096,11 +4029,11 @@ class Run:
4096
4029
 
4097
4030
  @staticmethod
4098
4031
  def _footer_internal_messages(
4099
- internal_messages_response: Optional["InternalMessagesResponse"] = None,
4100
- quiet: Optional[bool] = None,
4032
+ internal_messages_response: InternalMessagesResponse | None = None,
4033
+ quiet: bool | None = None,
4101
4034
  *,
4102
- settings: "Settings",
4103
- printer: Union["PrinterTerm", "PrinterJupyter"],
4035
+ settings: Settings,
4036
+ printer: printer.Printer,
4104
4037
  ) -> None:
4105
4038
  if (quiet or settings.quiet) or settings.silent:
4106
4039
  return
@@ -4114,9 +4047,9 @@ class Run:
4114
4047
  @staticmethod
4115
4048
  def _footer_notify_wandb_core(
4116
4049
  *,
4117
- quiet: Optional[bool] = None,
4118
- settings: "Settings",
4119
- printer: Union["PrinterTerm", "PrinterJupyter"],
4050
+ quiet: bool | None = None,
4051
+ settings: Settings,
4052
+ printer: printer.Printer,
4120
4053
  ) -> None:
4121
4054
  """Prints a message advertising the upcoming core release."""
4122
4055
  if quiet or not settings._require_legacy_service:
@@ -4131,11 +4064,11 @@ class Run:
4131
4064
 
4132
4065
  @staticmethod
4133
4066
  def _footer_reporter_warn_err(
4134
- reporter: Optional[Reporter] = None,
4135
- quiet: Optional[bool] = None,
4067
+ reporter: Reporter | None = None,
4068
+ quiet: bool | None = None,
4136
4069
  *,
4137
- settings: "Settings",
4138
- printer: Union["PrinterTerm", "PrinterJupyter"],
4070
+ settings: Settings,
4071
+ printer: printer.Printer,
4139
4072
  ) -> None:
4140
4073
  if (quiet or settings.quiet) or settings.silent:
4141
4074
  return
@@ -4161,10 +4094,10 @@ class Run:
4161
4094
  # We define this outside of the run context to support restoring before init
4162
4095
  def restore(
4163
4096
  name: str,
4164
- run_path: Optional[str] = None,
4097
+ run_path: str | None = None,
4165
4098
  replace: bool = False,
4166
- root: Optional[str] = None,
4167
- ) -> Union[None, TextIO]:
4099
+ root: str | None = None,
4100
+ ) -> None | TextIO:
4168
4101
  """Download the specified file from cloud storage.
4169
4102
 
4170
4103
  File is placed into the current directory or run directory.
@@ -4222,7 +4155,7 @@ except AttributeError:
4222
4155
  pass
4223
4156
 
4224
4157
 
4225
- def finish(exit_code: Optional[int] = None, quiet: Optional[bool] = None) -> None:
4158
+ def finish(exit_code: int | None = None, quiet: bool | None = None) -> None:
4226
4159
  """Mark a run as finished, and finish uploading all data.
4227
4160
 
4228
4161
  This is used when creating multiple runs in the same process.