wandb 0.19.9__py3-none-any.whl → 0.19.10__py3-none-any.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 (127) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +4 -1
  3. wandb/_pydantic/__init__.py +14 -7
  4. wandb/_pydantic/base.py +44 -9
  5. wandb/_pydantic/utils.py +66 -0
  6. wandb/_pydantic/v1_compat.py +78 -56
  7. wandb/apis/public/__init__.py +2 -2
  8. wandb/apis/public/api.py +114 -2
  9. wandb/apis/public/artifacts.py +365 -673
  10. wandb/apis/public/automations.py +69 -0
  11. wandb/apis/public/integrations.py +168 -0
  12. wandb/apis/public/projects.py +29 -0
  13. wandb/apis/public/utils.py +107 -1
  14. wandb/automations/__init__.py +81 -0
  15. wandb/automations/_filters/__init__.py +40 -0
  16. wandb/automations/_filters/expressions.py +179 -0
  17. wandb/automations/_filters/operators.py +267 -0
  18. wandb/automations/_filters/run_metrics.py +183 -0
  19. wandb/automations/_generated/__init__.py +184 -0
  20. wandb/automations/_generated/create_filter_trigger.py +21 -0
  21. wandb/automations/_generated/create_generic_webhook_integration.py +43 -0
  22. wandb/automations/_generated/delete_trigger.py +19 -0
  23. wandb/automations/_generated/enums.py +33 -0
  24. wandb/automations/_generated/fragments.py +343 -0
  25. wandb/automations/_generated/generic_webhook_integrations_by_entity.py +22 -0
  26. wandb/automations/_generated/get_triggers.py +24 -0
  27. wandb/automations/_generated/get_triggers_by_entity.py +24 -0
  28. wandb/automations/_generated/input_types.py +104 -0
  29. wandb/automations/_generated/integrations_by_entity.py +22 -0
  30. wandb/automations/_generated/operations.py +710 -0
  31. wandb/automations/_generated/slack_integrations_by_entity.py +22 -0
  32. wandb/automations/_generated/update_filter_trigger.py +21 -0
  33. wandb/automations/_utils.py +123 -0
  34. wandb/automations/_validators.py +73 -0
  35. wandb/automations/actions.py +205 -0
  36. wandb/automations/automations.py +109 -0
  37. wandb/automations/events.py +235 -0
  38. wandb/automations/integrations.py +26 -0
  39. wandb/automations/scopes.py +76 -0
  40. wandb/beta/workflows.py +9 -10
  41. wandb/bin/gpu_stats +0 -0
  42. wandb/cli/cli.py +3 -3
  43. wandb/integration/keras/keras.py +2 -1
  44. wandb/integration/langchain/wandb_tracer.py +2 -1
  45. wandb/jupyter.py +137 -118
  46. wandb/old/summary.py +0 -2
  47. wandb/proto/v3/wandb_internal_pb2.py +293 -292
  48. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  49. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  50. wandb/proto/v4/wandb_internal_pb2.py +292 -292
  51. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  52. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  53. wandb/proto/v5/wandb_internal_pb2.py +292 -292
  54. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  55. wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
  56. wandb/proto/v6/wandb_base_pb2.py +41 -0
  57. wandb/proto/v6/wandb_internal_pb2.py +393 -0
  58. wandb/proto/v6/wandb_server_pb2.py +78 -0
  59. wandb/proto/v6/wandb_settings_pb2.py +58 -0
  60. wandb/proto/v6/wandb_telemetry_pb2.py +52 -0
  61. wandb/proto/wandb_base_pb2.py +2 -0
  62. wandb/proto/wandb_deprecated.py +8 -0
  63. wandb/proto/wandb_internal_pb2.py +3 -1
  64. wandb/proto/wandb_server_pb2.py +2 -0
  65. wandb/proto/wandb_settings_pb2.py +2 -0
  66. wandb/proto/wandb_telemetry_pb2.py +2 -0
  67. wandb/sdk/artifacts/_generated/__init__.py +248 -0
  68. wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +43 -0
  69. wandb/sdk/artifacts/_generated/artifact_version_files.py +36 -0
  70. wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +36 -0
  71. wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +25 -0
  72. wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +35 -0
  73. wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +35 -0
  74. wandb/sdk/artifacts/_generated/enums.py +17 -0
  75. wandb/sdk/artifacts/_generated/fragments.py +186 -0
  76. wandb/sdk/artifacts/_generated/input_types.py +16 -0
  77. wandb/sdk/artifacts/_generated/move_artifact_collection.py +35 -0
  78. wandb/sdk/artifacts/_generated/operations.py +510 -0
  79. wandb/sdk/artifacts/_generated/project_artifact_collection.py +101 -0
  80. wandb/sdk/artifacts/_generated/project_artifact_collections.py +33 -0
  81. wandb/sdk/artifacts/_generated/project_artifact_type.py +24 -0
  82. wandb/sdk/artifacts/_generated/project_artifact_types.py +24 -0
  83. wandb/sdk/artifacts/_generated/project_artifacts.py +42 -0
  84. wandb/sdk/artifacts/_generated/run_input_artifacts.py +51 -0
  85. wandb/sdk/artifacts/_generated/run_output_artifacts.py +51 -0
  86. wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +35 -0
  87. wandb/sdk/artifacts/_generated/update_artifact_sequence.py +35 -0
  88. wandb/sdk/artifacts/_graphql_fragments.py +56 -79
  89. wandb/sdk/artifacts/artifact.py +40 -13
  90. wandb/sdk/artifacts/artifact_manifest_entry.py +2 -1
  91. wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
  92. wandb/sdk/data_types/base_types/media.py +2 -3
  93. wandb/sdk/data_types/base_types/wb_value.py +34 -11
  94. wandb/sdk/data_types/html.py +36 -9
  95. wandb/sdk/data_types/image.py +12 -12
  96. wandb/sdk/data_types/table.py +5 -0
  97. wandb/sdk/data_types/trace_tree.py +2 -0
  98. wandb/sdk/data_types/utils.py +1 -1
  99. wandb/sdk/data_types/video.py +14 -26
  100. wandb/sdk/interface/interface.py +2 -0
  101. wandb/sdk/internal/profiler.py +6 -5
  102. wandb/sdk/internal/run.py +13 -6
  103. wandb/sdk/lib/apikey.py +25 -4
  104. wandb/sdk/lib/asyncio_compat.py +1 -1
  105. wandb/sdk/lib/deprecate.py +13 -22
  106. wandb/sdk/lib/disabled.py +2 -1
  107. wandb/sdk/lib/printer.py +37 -8
  108. wandb/sdk/lib/printer_asyncio.py +46 -0
  109. wandb/sdk/lib/redirect.py +10 -5
  110. wandb/sdk/service/server_sock.py +19 -14
  111. wandb/sdk/service/service.py +9 -7
  112. wandb/sdk/service/streams.py +5 -0
  113. wandb/sdk/verify/verify.py +6 -3
  114. wandb/sdk/wandb_init.py +185 -65
  115. wandb/sdk/wandb_login.py +13 -4
  116. wandb/sdk/wandb_run.py +382 -286
  117. wandb/sdk/wandb_settings.py +21 -3
  118. wandb/sdk/wandb_setup.py +49 -0
  119. wandb/util.py +29 -29
  120. {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/METADATA +5 -5
  121. {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/RECORD +124 -71
  122. wandb/_globals.py +0 -19
  123. wandb/sdk/internal/_generated/base.py +0 -226
  124. wandb/sdk/internal/_generated/typing_compat.py +0 -14
  125. {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/WHEEL +0 -0
  126. {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/entry_points.txt +0 -0
  127. {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/wandb_run.py CHANGED
@@ -14,13 +14,26 @@ import sys
14
14
  import threading
15
15
  import time
16
16
  import traceback
17
- import warnings
18
17
  from collections.abc import Mapping
19
18
  from dataclasses import dataclass, field
20
19
  from datetime import datetime, timedelta, timezone
21
20
  from enum import IntEnum
22
21
  from types import TracebackType
23
- from typing import TYPE_CHECKING, Any, Callable, Literal, NamedTuple, Sequence, TextIO
22
+ from typing import (
23
+ TYPE_CHECKING,
24
+ Any,
25
+ Callable,
26
+ Literal,
27
+ NamedTuple,
28
+ Sequence,
29
+ TextIO,
30
+ TypeVar,
31
+ )
32
+
33
+ if sys.version_info < (3, 10):
34
+ from typing_extensions import Concatenate, ParamSpec
35
+ else:
36
+ from typing import Concatenate, ParamSpec
24
37
 
25
38
  import requests
26
39
 
@@ -28,13 +41,13 @@ import wandb
28
41
  import wandb.env
29
42
  import wandb.util
30
43
  from wandb import trigger
31
- from wandb._globals import _datatypes_set_callback
32
44
  from wandb.apis import internal, public
33
45
  from wandb.apis.public import Api as PublicApi
34
46
  from wandb.errors import CommError, UnsupportedError, UsageError
35
47
  from wandb.errors.links import url_registry
36
48
  from wandb.integration.torch import wandb_torch
37
49
  from wandb.plot import CustomChart, Visualize
50
+ from wandb.proto.wandb_deprecated import Deprecated
38
51
  from wandb.proto.wandb_internal_pb2 import (
39
52
  MetadataRequest,
40
53
  MetricRecord,
@@ -361,7 +374,13 @@ class RunStatusChecker:
361
374
  self._internal_messages_thread.join()
362
375
 
363
376
 
364
- def _log_to_run(func: Callable) -> Callable:
377
+ _P = ParamSpec("_P")
378
+ _T = TypeVar("_T")
379
+
380
+
381
+ def _log_to_run(
382
+ func: Callable[Concatenate[Run, _P], _T],
383
+ ) -> Callable[Concatenate[Run, _P], _T]:
365
384
  """Decorate a Run method to set the run ID in the logging context.
366
385
 
367
386
  Any logs during the execution of the method go to the run's log file
@@ -375,7 +394,7 @@ def _log_to_run(func: Callable) -> Callable:
375
394
  """
376
395
 
377
396
  @functools.wraps(func)
378
- def wrapper(self: Run, *args, **kwargs) -> Any:
397
+ def wrapper(self: Run, *args, **kwargs) -> _T:
379
398
  # In "attach" usage, many properties of the Run are not initially
380
399
  # populated.
381
400
  if hasattr(self, "_settings"):
@@ -389,100 +408,118 @@ def _log_to_run(func: Callable) -> Callable:
389
408
  return wrapper
390
409
 
391
410
 
392
- class _run_decorator: # noqa: N801
393
- _is_attaching: str = ""
394
-
395
- class Dummy: ...
396
-
397
- @classmethod
398
- def _attach(cls, func: Callable) -> Callable:
399
- @functools.wraps(func)
400
- def wrapper(self: type[Run], *args: Any, **kwargs: Any) -> Any:
401
- # * `_attach_id` is only assigned in service hence for all non-service cases
402
- # it will be a passthrough.
403
- # * `_attach_pid` is only assigned in _init (using _attach_pid guarantees single attach):
404
- # - for non-fork case the object is shared through pickling so will be None.
405
- # - for fork case the new process share mem space hence the value would be of parent process.
406
- if (
407
- getattr(self, "_attach_id", None)
408
- and getattr(self, "_attach_pid", None) != os.getpid()
409
- ):
410
- if cls._is_attaching:
411
- message = (
412
- f"Trying to attach `{func.__name__}` "
413
- f"while in the middle of attaching `{cls._is_attaching}`"
414
- )
415
- raise RuntimeError(message)
416
- cls._is_attaching = func.__name__
417
- try:
418
- wandb._attach(run=self) # type: ignore
419
- except Exception as e:
420
- # In case the attach fails we will raise the exception that caused the issue.
421
- # This exception should be caught and fail the execution of the program.
422
- cls._is_attaching = ""
423
- raise e
424
- cls._is_attaching = ""
425
- return func(self, *args, **kwargs)
411
+ _is_attaching: str = ""
412
+
413
+
414
+ def _attach(
415
+ func: Callable[Concatenate[Run, _P], _T],
416
+ ) -> Callable[Concatenate[Run, _P], _T]:
417
+ """Decorate a Run method to auto-attach when in a new process.
426
418
 
427
- return wrapper
419
+ When in a forked process or using a pickled Run instance, this automatically
420
+ connects to the service process to "attach" to the existing run.
421
+ """
428
422
 
429
- @classmethod
430
- def _noop_on_finish(cls, message: str = "", only_warn: bool = False) -> Callable:
431
- def decorator_fn(func: Callable) -> Callable:
432
- @functools.wraps(func)
433
- def wrapper_fn(self: type[Run], *args: Any, **kwargs: Any) -> Any:
434
- if not getattr(self, "_is_finished", False):
435
- return func(self, *args, **kwargs)
423
+ @functools.wraps(func)
424
+ def wrapper(self: Run, *args, **kwargs) -> _T:
425
+ global _is_attaching
436
426
 
437
- default_message = (
438
- f"Run ({self.id}) is finished. The call to `{func.__name__}` will be ignored. "
439
- f"Please make sure that you are using an active run."
427
+ # The _attach_id attribute is only None when running in the "disable
428
+ # service" mode.
429
+ #
430
+ # Since it is set early in `__init__` and included in the run's pickled
431
+ # state, the attribute always exists.
432
+ is_using_service = self._attach_id is not None
433
+
434
+ # The _attach_pid attribute is not pickled, so it might not exist.
435
+ # It is set when the run is initialized.
436
+ attach_pid = getattr(self, "_attach_pid", None)
437
+
438
+ if is_using_service and attach_pid != os.getpid():
439
+ if _is_attaching:
440
+ raise RuntimeError(
441
+ f"Trying to attach `{func.__name__}`"
442
+ f" while in the middle of attaching `{_is_attaching}`"
440
443
  )
441
- resolved_message = message or default_message
442
- if only_warn:
443
- warnings.warn(resolved_message, UserWarning, stacklevel=2)
444
- else:
445
- raise UsageError(resolved_message)
446
-
447
- return wrapper_fn
448
-
449
- return decorator_fn
450
-
451
- @classmethod
452
- def _noop(cls, func: Callable) -> Callable:
453
- @functools.wraps(func)
454
- def wrapper(self: type[Run], *args: Any, **kwargs: Any) -> Any:
455
- # `_attach_id` is only assigned in service hence for all service cases
456
- # it will be a passthrough. We don't pickle non-service so again a way
457
- # to see that we are in non-service case
458
- if getattr(self, "_attach_id", None) is None:
459
- # `_init_pid` is only assigned in __init__ (this will be constant check for mp):
460
- # - for non-fork case the object is shared through pickling,
461
- # and we don't pickle non-service so will be None
462
- # - for fork case the new process share mem space hence the value would be of parent process.
463
- _init_pid = getattr(self, "_init_pid", None)
464
- if _init_pid != os.getpid():
465
- message = (
466
- f"`{func.__name__}` ignored (called from pid={os.getpid()}, "
467
- f"`init` called from pid={_init_pid}). "
468
- f"See: {url_registry.url('multiprocess')}"
469
- )
470
- # - if this process was pickled in non-service case,
471
- # we ignore the attributes (since pickle is not supported)
472
- # - for fork case will use the settings of the parent process
473
- # - only point of inconsistent behavior from forked and non-forked cases
474
- settings = getattr(self, "_settings", None)
475
- if settings and settings.strict:
476
- wandb.termerror(message, repeat=False)
477
- raise UnsupportedError(
478
- f"`{func.__name__}` does not support multiprocessing"
479
- )
480
- wandb.termwarn(message, repeat=False)
481
- return cls.Dummy()
482
444
 
445
+ _is_attaching = func.__name__
446
+ try:
447
+ wandb._attach(run=self) # type: ignore
448
+ finally:
449
+ _is_attaching = ""
450
+
451
+ return func(self, *args, **kwargs)
452
+
453
+ return wrapper
454
+
455
+
456
+ def _raise_if_finished(
457
+ func: Callable[Concatenate[Run, _P], _T],
458
+ ) -> Callable[Concatenate[Run, _P], _T]:
459
+ """Decorate a Run method to raise an error after the run is finished."""
460
+
461
+ @functools.wraps(func)
462
+ def wrapper_fn(self: Run, *args, **kwargs) -> _T:
463
+ if not getattr(self, "_is_finished", False):
464
+ return func(self, *args, **kwargs)
465
+
466
+ message = (
467
+ f"Run ({self.id}) is finished. The call to"
468
+ f" `{func.__name__}` will be ignored."
469
+ f" Please make sure that you are using an active run."
470
+ )
471
+
472
+ raise UsageError(message)
473
+
474
+ return wrapper_fn
475
+
476
+
477
+ def _noop_if_forked_with_no_service(
478
+ func: Callable[Concatenate[Run, _P], None],
479
+ ) -> Callable[Concatenate[Run, _P], None]:
480
+ """Do nothing if called in a forked process and service is disabled.
481
+
482
+ Disabling the service is a very old and barely supported setting.
483
+ """
484
+
485
+ @functools.wraps(func)
486
+ def wrapper(self: Run, *args, **kwargs) -> None:
487
+ # The _attach_id attribute is only None when running in the "disable
488
+ # service" mode.
489
+ #
490
+ # Since it is set early in `__init__` and included in the run's pickled
491
+ # state, the attribute always exists.
492
+ is_using_service = self._attach_id is not None
493
+
494
+ # This is the PID in which the Run object was constructed. The attribute
495
+ # always exists because it is set early in `__init__` and is included
496
+ # in the pickled state in `__getstate__` and `__setstate__`.
497
+ #
498
+ # It is not equal to the current PID if the process was forked or if
499
+ # the Run object was pickled and sent to another process.
500
+ init_pid = self._init_pid
501
+
502
+ if is_using_service or init_pid == os.getpid():
483
503
  return func(self, *args, **kwargs)
484
504
 
485
- return wrapper
505
+ message = (
506
+ f"`{func.__name__}` ignored (called from pid={os.getpid()},"
507
+ f" `init` called from pid={init_pid})."
508
+ f" See: {url_registry.url('multiprocess')}"
509
+ )
510
+
511
+ # This attribute may not exist because it is not included in the run's
512
+ # pickled state.
513
+ settings = getattr(self, "_settings", None)
514
+ if settings and settings.strict:
515
+ wandb.termerror(message, repeat=False)
516
+ raise UnsupportedError(
517
+ f"`{func.__name__}` does not support multiprocessing"
518
+ )
519
+ wandb.termwarn(message, repeat=False)
520
+ return None
521
+
522
+ return wrapper
486
523
 
487
524
 
488
525
  @dataclass
@@ -606,6 +643,7 @@ class Run:
606
643
  ) -> None:
607
644
  # pid is set, so we know if this run object was initialized by this process
608
645
  self._init_pid = os.getpid()
646
+ self._attach_id = None
609
647
 
610
648
  if settings._noop:
611
649
  # TODO: properly handle setting for disabled mode
@@ -647,14 +685,13 @@ class Run:
647
685
 
648
686
  self._step = 0
649
687
  self._starting_step = 0
688
+ self._start_runtime = 0
650
689
  # TODO: eventually would be nice to make this configurable using self._settings._start_time
651
690
  # need to test (jhr): if you set start time to 2 days ago and run a test for 15 minutes,
652
691
  # does the total time get calculated right (not as 2 days and 15 minutes)?
653
692
  self._start_time = time.time()
654
693
 
655
- _datatypes_set_callback(self._datatypes_callback)
656
-
657
- self._printer = printer.new_printer()
694
+ self._printer = printer.new_printer(settings)
658
695
 
659
696
  self._torch_history: wandb_torch.TorchHistory | None = None # type: ignore
660
697
 
@@ -776,8 +813,9 @@ class Run:
776
813
  self._unique_launch_artifact_sequence_names[sequence_name] = item
777
814
 
778
815
  def _telemetry_callback(self, telem_obj: telemetry.TelemetryRecord) -> None:
779
- if not hasattr(self, "_telemetry_obj"):
816
+ if not hasattr(self, "_telemetry_obj") or self._is_finished:
780
817
  return
818
+
781
819
  self._telemetry_obj.MergeFrom(telem_obj)
782
820
  self._telemetry_obj_dirty = True
783
821
  self._telemetry_flush()
@@ -846,47 +884,45 @@ class Run:
846
884
 
847
885
  @property
848
886
  @_log_to_run
849
- @_run_decorator._attach
887
+ @_attach
850
888
  def settings(self) -> Settings:
851
889
  """A frozen copy of run's Settings object."""
852
890
  return self._settings.model_copy(deep=True)
853
891
 
854
892
  @property
855
893
  @_log_to_run
856
- @_run_decorator._attach
894
+ @_attach
857
895
  def dir(self) -> str:
858
896
  """The directory where files associated with the run are saved."""
859
897
  return self._settings.files_dir
860
898
 
861
899
  @property
862
900
  @_log_to_run
863
- @_run_decorator._attach
901
+ @_attach
864
902
  def config(self) -> wandb_config.Config:
865
903
  """Config object associated with this run."""
866
904
  return self._config
867
905
 
868
906
  @property
869
907
  @_log_to_run
870
- @_run_decorator._attach
908
+ @_attach
871
909
  def config_static(self) -> wandb_config.ConfigStatic:
872
910
  return wandb_config.ConfigStatic(self._config)
873
911
 
874
912
  @property
875
913
  @_log_to_run
876
- @_run_decorator._attach
914
+ @_attach
877
915
  def name(self) -> str | None:
878
916
  """Display name of the run.
879
917
 
880
918
  Display names are not guaranteed to be unique and may be descriptive.
881
919
  By default, they are randomly generated.
882
920
  """
883
- if self._settings.run_name:
884
- return self._settings.run_name
885
- return None
921
+ return self._settings.run_name
886
922
 
887
923
  @name.setter
888
924
  @_log_to_run
889
- @_run_decorator._noop_on_finish()
925
+ @_raise_if_finished
890
926
  def name(self, name: str) -> None:
891
927
  with telemetry.context(run=self) as tel:
892
928
  tel.feature.set_run_name = True
@@ -896,18 +932,18 @@ class Run:
896
932
 
897
933
  @property
898
934
  @_log_to_run
899
- @_run_decorator._attach
935
+ @_attach
900
936
  def notes(self) -> str | None:
901
937
  """Notes associated with the run, if there are any.
902
938
 
903
- Notes can be a multiline string and can also use markdown and latex equations
904
- inside `$$`, like `$x + 3$`.
939
+ Notes can be a multiline string and can also use markdown and latex
940
+ equations inside `$$`, like `$x + 3$`.
905
941
  """
906
942
  return self._settings.run_notes
907
943
 
908
944
  @notes.setter
909
945
  @_log_to_run
910
- @_run_decorator._noop_on_finish()
946
+ @_raise_if_finished
911
947
  def notes(self, notes: str) -> None:
912
948
  self._settings.run_notes = notes
913
949
  if self._backend and self._backend.interface:
@@ -915,14 +951,14 @@ class Run:
915
951
 
916
952
  @property
917
953
  @_log_to_run
918
- @_run_decorator._attach
954
+ @_attach
919
955
  def tags(self) -> tuple | None:
920
956
  """Tags associated with the run, if there are any."""
921
957
  return self._settings.run_tags or ()
922
958
 
923
959
  @tags.setter
924
960
  @_log_to_run
925
- @_run_decorator._noop_on_finish()
961
+ @_raise_if_finished
926
962
  def tags(self, tags: Sequence) -> None:
927
963
  with telemetry.context(run=self) as tel:
928
964
  tel.feature.set_run_tags = True
@@ -932,18 +968,17 @@ class Run:
932
968
 
933
969
  @property
934
970
  @_log_to_run
935
- @_run_decorator._attach
971
+ @_attach
936
972
  def id(self) -> str:
937
973
  """Identifier for this run."""
938
- if TYPE_CHECKING:
939
- assert self._settings.run_id is not None
974
+ assert self._settings.run_id is not None
940
975
  return self._settings.run_id
941
976
 
942
977
  @property
943
978
  @_log_to_run
944
- @_run_decorator._attach
979
+ @_attach
945
980
  def sweep_id(self) -> str | None:
946
- """ID of the sweep associated with the run, if there is one."""
981
+ """Identifier for the sweep associated with the run, if there is one."""
947
982
  return self._settings.sweep_id
948
983
 
949
984
  def _get_path(self) -> str:
@@ -959,7 +994,7 @@ class Run:
959
994
 
960
995
  @property
961
996
  @_log_to_run
962
- @_run_decorator._attach
997
+ @_attach
963
998
  def path(self) -> str:
964
999
  """Path to the run.
965
1000
 
@@ -970,28 +1005,28 @@ class Run:
970
1005
 
971
1006
  @property
972
1007
  @_log_to_run
973
- @_run_decorator._attach
1008
+ @_attach
974
1009
  def start_time(self) -> float:
975
1010
  """Unix timestamp (in seconds) of when the run started."""
976
1011
  return self._start_time
977
1012
 
978
1013
  @property
979
1014
  @_log_to_run
980
- @_run_decorator._attach
1015
+ @_attach
981
1016
  def starting_step(self) -> int:
982
1017
  """The first step of the run."""
983
1018
  return self._starting_step
984
1019
 
985
1020
  @property
986
1021
  @_log_to_run
987
- @_run_decorator._attach
1022
+ @_attach
988
1023
  def resumed(self) -> bool:
989
1024
  """True if the run was resumed, False otherwise."""
990
1025
  return self._settings.resumed
991
1026
 
992
1027
  @property
993
1028
  @_log_to_run
994
- @_run_decorator._attach
1029
+ @_attach
995
1030
  def step(self) -> int:
996
1031
  """Current value of the step.
997
1032
 
@@ -1001,34 +1036,34 @@ class Run:
1001
1036
 
1002
1037
  @property
1003
1038
  @_log_to_run
1004
- @_run_decorator._attach
1039
+ @_attach
1005
1040
  def mode(self) -> str:
1006
1041
  """For compatibility with `0.9.x` and earlier, deprecate eventually."""
1007
- if hasattr(self, "_telemetry_obj"):
1008
- deprecate.deprecate(
1009
- field_name=deprecate.Deprecated.run__mode,
1010
- warning_message=(
1011
- "The mode property of wandb.run is deprecated "
1012
- "and will be removed in a future release."
1013
- ),
1014
- )
1042
+ deprecate.deprecate(
1043
+ field_name=Deprecated.run__mode,
1044
+ warning_message=(
1045
+ "The mode property of wandb.run is deprecated "
1046
+ "and will be removed in a future release."
1047
+ ),
1048
+ run=self,
1049
+ )
1015
1050
  return "dryrun" if self._settings._offline else "run"
1016
1051
 
1017
1052
  @property
1018
1053
  @_log_to_run
1019
- @_run_decorator._attach
1054
+ @_attach
1020
1055
  def offline(self) -> bool:
1021
1056
  return self._settings._offline
1022
1057
 
1023
1058
  @property
1024
1059
  @_log_to_run
1025
- @_run_decorator._attach
1060
+ @_attach
1026
1061
  def disabled(self) -> bool:
1027
1062
  return self._settings._noop
1028
1063
 
1029
1064
  @property
1030
1065
  @_log_to_run
1031
- @_run_decorator._attach
1066
+ @_attach
1032
1067
  def group(self) -> str:
1033
1068
  """Name of the group associated with the run.
1034
1069
 
@@ -1043,24 +1078,67 @@ class Run:
1043
1078
 
1044
1079
  @property
1045
1080
  @_log_to_run
1046
- @_run_decorator._attach
1081
+ @_attach
1047
1082
  def job_type(self) -> str:
1048
1083
  return self._settings.run_job_type or ""
1049
1084
 
1050
1085
  def project_name(self) -> str:
1051
- # TODO: deprecate this in favor of project
1052
- return self._settings.project or ""
1086
+ """Name of the W&B project associated with the run.
1087
+
1088
+ Note: this method is deprecated and will be removed in a future release.
1089
+ Please use `run.project` instead.
1090
+ """
1091
+ deprecate.deprecate(
1092
+ field_name=Deprecated.run__project_name,
1093
+ warning_message=(
1094
+ "The project_name method is deprecated and will be removed in a"
1095
+ " future release. Please use `run.project` instead."
1096
+ ),
1097
+ )
1098
+ return self.project
1053
1099
 
1054
1100
  @property
1055
1101
  @_log_to_run
1056
- @_run_decorator._attach
1102
+ @_attach
1057
1103
  def project(self) -> str:
1058
1104
  """Name of the W&B project associated with the run."""
1059
- return self.project_name()
1105
+ assert self._settings.project is not None
1106
+ return self._settings.project
1107
+
1108
+ @_log_to_run
1109
+ def get_project_url(self) -> str | None:
1110
+ """URL of the W&B project associated with the run, if there is one.
1111
+
1112
+ Offline runs do not have a project URL.
1060
1113
 
1061
- @_run_decorator._noop_on_finish()
1114
+ Note: this method is deprecated and will be removed in a future release.
1115
+ Please use `run.project_url` instead.
1116
+ """
1117
+ deprecate.deprecate(
1118
+ field_name=Deprecated.run__get_project_url,
1119
+ warning_message=(
1120
+ "The get_project_url method is deprecated and will be removed in a"
1121
+ " future release. Please use `run.project_url` instead."
1122
+ ),
1123
+ )
1124
+ return self.project_url
1125
+
1126
+ @property
1062
1127
  @_log_to_run
1063
- @_run_decorator._attach
1128
+ @_attach
1129
+ def project_url(self) -> str | None:
1130
+ """URL of the W&B project associated with the run, if there is one.
1131
+
1132
+ Offline runs do not have a project URL.
1133
+ """
1134
+ if self._settings._offline:
1135
+ wandb.termwarn("URL not available in offline run")
1136
+ return None
1137
+ return self._settings.project_url
1138
+
1139
+ @_raise_if_finished
1140
+ @_log_to_run
1141
+ @_attach
1064
1142
  def log_code(
1065
1143
  self,
1066
1144
  root: str | None = ".",
@@ -1147,19 +1225,30 @@ class Run:
1147
1225
  return self._log_artifact(art)
1148
1226
 
1149
1227
  @_log_to_run
1150
- def get_project_url(self) -> str | None:
1151
- """Return the url for the W&B project associated with the run, if there is one.
1228
+ def get_sweep_url(self) -> str | None:
1229
+ """The URL of the sweep associated with the run, if there is one.
1152
1230
 
1153
- Offline runs will not have a project url.
1231
+ Offline runs do not have a sweep URL.
1232
+
1233
+ Note: this method is deprecated and will be removed in a future release.
1234
+ Please use `run.sweep_url` instead.
1154
1235
  """
1155
- if self._settings._offline:
1156
- wandb.termwarn("URL not available in offline run")
1157
- return None
1158
- return self._settings.project_url
1236
+ deprecate.deprecate(
1237
+ field_name=Deprecated.run__get_sweep_url,
1238
+ warning_message=(
1239
+ "The get_sweep_url method is deprecated and will be removed in a"
1240
+ " future release. Please use `run.sweep_url` instead."
1241
+ ),
1242
+ )
1243
+ return self.sweep_url
1159
1244
 
1160
- @_log_to_run
1161
- def get_sweep_url(self) -> str | None:
1162
- """Return the url for the sweep associated with the run, if there is one."""
1245
+ @property
1246
+ @_attach
1247
+ def sweep_url(self) -> str | None:
1248
+ """URL of the sweep associated with the run, if there is one.
1249
+
1250
+ Offline runs do not have a sweep URL.
1251
+ """
1163
1252
  if self._settings._offline:
1164
1253
  wandb.termwarn("URL not available in offline run")
1165
1254
  return None
@@ -1167,7 +1256,27 @@ class Run:
1167
1256
 
1168
1257
  @_log_to_run
1169
1258
  def get_url(self) -> str | None:
1170
- """Return the url for the W&B run, if there is one.
1259
+ """URL of the W&B run, if there is one.
1260
+
1261
+ Offline runs do not have a URL.
1262
+
1263
+ Note: this method is deprecated and will be removed in a future release.
1264
+ Please use `run.url` instead.
1265
+ """
1266
+ deprecate.deprecate(
1267
+ field_name=Deprecated.run__get_url,
1268
+ warning_message=(
1269
+ "The get_url method is deprecated and will be removed in a"
1270
+ " future release. Please use `run.url` instead."
1271
+ ),
1272
+ )
1273
+ return self.url
1274
+
1275
+ @property
1276
+ @_log_to_run
1277
+ @_attach
1278
+ def url(self) -> str | None:
1279
+ """The url for the W&B run, if there is one.
1171
1280
 
1172
1281
  Offline runs will not have a url.
1173
1282
  """
@@ -1178,14 +1287,7 @@ class Run:
1178
1287
 
1179
1288
  @property
1180
1289
  @_log_to_run
1181
- @_run_decorator._attach
1182
- def url(self) -> str | None:
1183
- """The W&B url associated with the run."""
1184
- return self.get_url()
1185
-
1186
- @property
1187
- @_log_to_run
1188
- @_run_decorator._attach
1290
+ @_attach
1189
1291
  def entity(self) -> str:
1190
1292
  """The name of the W&B entity associated with the run.
1191
1293
 
@@ -1280,7 +1382,7 @@ class Run:
1280
1382
  self._label_probe_lines(lines)
1281
1383
 
1282
1384
  @_log_to_run
1283
- @_run_decorator._attach
1385
+ @_attach
1284
1386
  def display(self, height: int = 420, hidden: bool = False) -> bool:
1285
1387
  """Display this run in jupyter."""
1286
1388
  if self._settings.silent:
@@ -1300,7 +1402,7 @@ class Run:
1300
1402
  return False
1301
1403
 
1302
1404
  @_log_to_run
1303
- @_run_decorator._attach
1405
+ @_attach
1304
1406
  def to_html(self, height: int = 420, hidden: bool = False) -> str:
1305
1407
  """Generate HTML containing an iframe displaying the current run."""
1306
1408
  url = self._settings.run_url + "?jupyter=true"
@@ -1317,7 +1419,7 @@ class Run:
1317
1419
  return {"text/html": self.to_html(hidden=True)}
1318
1420
 
1319
1421
  @_log_to_run
1320
- @_run_decorator._noop_on_finish()
1422
+ @_raise_if_finished
1321
1423
  def _config_callback(
1322
1424
  self,
1323
1425
  key: tuple[str, ...] | str | None = None,
@@ -1339,6 +1441,8 @@ class Run:
1339
1441
  assert isinstance(val, dict)
1340
1442
  public_api = self._public_api()
1341
1443
  artifact = Artifact._from_id(val["id"], public_api.client)
1444
+
1445
+ assert artifact
1342
1446
  return self.use_artifact(artifact, use_as=key)
1343
1447
  elif _is_artifact_string(val):
1344
1448
  # this will never fail, but is required to make mypy happy
@@ -1357,6 +1461,7 @@ class Run:
1357
1461
  # in the future we'll need to support using artifacts from
1358
1462
  # different instances of wandb.
1359
1463
 
1464
+ assert artifact
1360
1465
  return self.use_artifact(artifact, use_as=key)
1361
1466
  elif _is_artifact_object(val):
1362
1467
  return self.use_artifact(val, use_as=key)
@@ -1369,7 +1474,7 @@ class Run:
1369
1474
  self._config_callback(key=("_wandb", key), val=val)
1370
1475
 
1371
1476
  @_log_to_run
1372
- @_run_decorator._noop_on_finish()
1477
+ @_raise_if_finished
1373
1478
  def _summary_update_callback(self, summary_record: SummaryRecord) -> None:
1374
1479
  with telemetry.context(run=self) as tel:
1375
1480
  tel.feature.set_summary = True
@@ -1400,7 +1505,16 @@ class Run:
1400
1505
  self._backend.interface._publish_metric(metric_record)
1401
1506
 
1402
1507
  @_log_to_run
1403
- def _datatypes_callback(self, fname: str) -> None:
1508
+ def _publish_file(self, fname: str) -> None:
1509
+ """Mark a run file to be uploaded with the run.
1510
+
1511
+ This is a W&B-internal function: it can be used by other internal
1512
+ wandb code.
1513
+
1514
+ Args:
1515
+ fname: The path to the file in the run's files directory, relative
1516
+ to the run's files directory.
1517
+ """
1404
1518
  if not self._backend or not self._backend.interface:
1405
1519
  return
1406
1520
  files: FilesDict = dict(files=[(GlobStr(fname), "now")])
@@ -1502,7 +1616,7 @@ class Run:
1502
1616
  self._backend.interface.publish_output(name, data)
1503
1617
 
1504
1618
  @_log_to_run
1505
- @_run_decorator._noop_on_finish(only_warn=True)
1619
+ @_raise_if_finished
1506
1620
  def _console_raw_callback(self, name: str, data: str) -> None:
1507
1621
  # logger.info("console callback: %s, %s", name, data)
1508
1622
 
@@ -1548,6 +1662,9 @@ class Run:
1548
1662
  if run_obj.start_time:
1549
1663
  self._start_time = run_obj.start_time.ToMicroseconds() / 1e6
1550
1664
 
1665
+ if run_obj.runtime:
1666
+ self._start_runtime = run_obj.runtime
1667
+
1551
1668
  # Grab the config from resuming
1552
1669
  if run_obj.config:
1553
1670
  c_dict = config_util.dict_no_value_from_proto_list(run_obj.config.update)
@@ -1683,9 +1800,9 @@ class Run:
1683
1800
  self._step += 1
1684
1801
 
1685
1802
  @_log_to_run
1686
- @_run_decorator._noop
1687
- @_run_decorator._noop_on_finish()
1688
- @_run_decorator._attach
1803
+ @_noop_if_forked_with_no_service
1804
+ @_raise_if_finished
1805
+ @_attach
1689
1806
  def log(
1690
1807
  self,
1691
1808
  data: dict[str, Any],
@@ -1931,10 +2048,11 @@ class Run:
1931
2048
 
1932
2049
  if sync is not None:
1933
2050
  deprecate.deprecate(
1934
- field_name=deprecate.Deprecated.run__log_sync,
2051
+ field_name=Deprecated.run__log_sync,
1935
2052
  warning_message=(
1936
2053
  "`sync` argument is deprecated and does not affect the behaviour of `wandb.log`"
1937
2054
  ),
2055
+ run=self,
1938
2056
  )
1939
2057
  if self._settings._shared and step is not None:
1940
2058
  wandb.termwarn(
@@ -1946,8 +2064,8 @@ class Run:
1946
2064
  self._log(data=data, step=step, commit=commit)
1947
2065
 
1948
2066
  @_log_to_run
1949
- @_run_decorator._noop_on_finish()
1950
- @_run_decorator._attach
2067
+ @_raise_if_finished
2068
+ @_attach
1951
2069
  def save(
1952
2070
  self,
1953
2071
  glob_str: str | os.PathLike | None = None,
@@ -2004,11 +2122,12 @@ class Run:
2004
2122
  if glob_str is None:
2005
2123
  # noop for historical reasons, run.save() may be called in legacy code
2006
2124
  deprecate.deprecate(
2007
- field_name=deprecate.Deprecated.run__save_no_args,
2125
+ field_name=Deprecated.run__save_no_args,
2008
2126
  warning_message=(
2009
2127
  "Calling wandb.run.save without any arguments is deprecated."
2010
2128
  "Changes to attributes are automatically persisted."
2011
2129
  ),
2130
+ run=self,
2012
2131
  )
2013
2132
  return True
2014
2133
 
@@ -2132,7 +2251,7 @@ class Run:
2132
2251
  return [str(f) for f in globbed_files]
2133
2252
 
2134
2253
  @_log_to_run
2135
- @_run_decorator._attach
2254
+ @_attach
2136
2255
  def restore(
2137
2256
  self,
2138
2257
  name: str,
@@ -2148,8 +2267,8 @@ class Run:
2148
2267
  )
2149
2268
 
2150
2269
  @_log_to_run
2151
- @_run_decorator._noop
2152
- @_run_decorator._attach
2270
+ @_noop_if_forked_with_no_service
2271
+ @_attach
2153
2272
  def finish(
2154
2273
  self,
2155
2274
  exit_code: int | None = None,
@@ -2173,11 +2292,12 @@ class Run:
2173
2292
  """
2174
2293
  if quiet is not None:
2175
2294
  deprecate.deprecate(
2176
- field_name=deprecate.Deprecated.run__finish_quiet,
2295
+ field_name=Deprecated.run__finish_quiet,
2177
2296
  warning_message=(
2178
2297
  "The `quiet` argument to `wandb.run.finish()` is deprecated, "
2179
2298
  "use `wandb.Settings(quiet=...)` to set this instead."
2180
2299
  ),
2300
+ run=self,
2181
2301
  )
2182
2302
  return self._finish(exit_code)
2183
2303
 
@@ -2186,6 +2306,11 @@ class Run:
2186
2306
  self,
2187
2307
  exit_code: int | None = None,
2188
2308
  ) -> None:
2309
+ if self._is_finished:
2310
+ return
2311
+
2312
+ assert self._wl
2313
+
2189
2314
  logger.info(f"finishing run {self._get_path()}")
2190
2315
  with telemetry.context(run=self) as tel:
2191
2316
  tel.feature.finish = True
@@ -2199,6 +2324,7 @@ class Run:
2199
2324
  # Early-stage hooks may use methods that require _is_finished
2200
2325
  # to be False, so we set this after running those hooks.
2201
2326
  self._is_finished = True
2327
+ self._wl.remove_active_run(self)
2202
2328
 
2203
2329
  try:
2204
2330
  self._atexit_cleanup(exit_code=exit_code)
@@ -2214,31 +2340,32 @@ class Run:
2214
2340
  #
2215
2341
  # TODO: Why not do this in _atexit_cleanup()?
2216
2342
  if self._settings.run_id:
2217
- assert self._wl
2218
2343
  service = self._wl.assert_service()
2219
2344
  service.inform_finish(run_id=self._settings.run_id)
2220
2345
 
2221
2346
  finally:
2222
- module.unset_globals()
2347
+ if wandb.run is self:
2348
+ module.unset_globals()
2223
2349
  wandb._sentry.end_session()
2224
2350
 
2225
2351
  @_log_to_run
2226
- @_run_decorator._noop
2227
- @_run_decorator._attach
2352
+ @_noop_if_forked_with_no_service
2353
+ @_attach
2228
2354
  def join(self, exit_code: int | None = None) -> None:
2229
2355
  """Deprecated alias for `finish()` - use finish instead."""
2230
2356
  if hasattr(self, "_telemetry_obj"):
2231
2357
  deprecate.deprecate(
2232
- field_name=deprecate.Deprecated.run__join,
2358
+ field_name=Deprecated.run__join,
2233
2359
  warning_message=(
2234
2360
  "wandb.run.join() is deprecated, please use wandb.run.finish()."
2235
2361
  ),
2362
+ run=self,
2236
2363
  )
2237
2364
  self._finish(exit_code=exit_code)
2238
2365
 
2239
2366
  @_log_to_run
2240
- @_run_decorator._noop_on_finish()
2241
- @_run_decorator._attach
2367
+ @_raise_if_finished
2368
+ @_attach
2242
2369
  def status(
2243
2370
  self,
2244
2371
  ) -> RunStatus:
@@ -2270,25 +2397,6 @@ class Run:
2270
2397
  }
2271
2398
  self._config_callback(val=config, key=("_wandb", "visualize", visualize_key))
2272
2399
 
2273
- def _set_globals(self) -> None:
2274
- module.set_global(
2275
- run=self,
2276
- config=self.config,
2277
- log=self.log,
2278
- summary=self.summary,
2279
- save=self.save,
2280
- use_artifact=self.use_artifact,
2281
- log_artifact=self.log_artifact,
2282
- define_metric=self.define_metric,
2283
- alert=self.alert,
2284
- watch=self.watch,
2285
- unwatch=self.unwatch,
2286
- mark_preempting=self.mark_preempting,
2287
- log_model=self.log_model,
2288
- use_model=self.use_model,
2289
- link_model=self.link_model,
2290
- )
2291
-
2292
2400
  def _redirect(
2293
2401
  self,
2294
2402
  stdout_slave_fd: int | None,
@@ -2327,6 +2435,7 @@ class Run:
2327
2435
  lambda data: self._console_callback("stdout", data),
2328
2436
  self._output_writer.write, # type: ignore
2329
2437
  ],
2438
+ flush_periodically=(self._settings.mode == "online"),
2330
2439
  )
2331
2440
  err_redir = redirect.Redirect(
2332
2441
  src="stderr",
@@ -2334,6 +2443,7 @@ class Run:
2334
2443
  lambda data: self._console_callback("stderr", data),
2335
2444
  self._output_writer.write, # type: ignore
2336
2445
  ],
2446
+ flush_periodically=(self._settings.mode == "online"),
2337
2447
  )
2338
2448
  if os.name == "nt":
2339
2449
 
@@ -2359,6 +2469,7 @@ class Run:
2359
2469
  lambda data: self._console_callback("stdout", data),
2360
2470
  self._output_writer.write, # type: ignore
2361
2471
  ],
2472
+ flush_periodically=(self._settings.mode == "online"),
2362
2473
  )
2363
2474
  err_redir = redirect.StreamWrapper(
2364
2475
  src="stderr",
@@ -2366,6 +2477,7 @@ class Run:
2366
2477
  lambda data: self._console_callback("stderr", data),
2367
2478
  self._output_writer.write, # type: ignore
2368
2479
  ],
2480
+ flush_periodically=(self._settings.mode == "online"),
2369
2481
  )
2370
2482
  elif console == "wrap_raw":
2371
2483
  logger.info("Wrapping output streams.")
@@ -2468,13 +2580,7 @@ class Run:
2468
2580
  self._output_writer = None
2469
2581
 
2470
2582
  def _on_start(self) -> None:
2471
- # would like to move _set_global to _on_ready to unify _on_start and _on_attach
2472
- # (we want to do the set globals after attach)
2473
- # TODO(console) However _console_start calls Redirect that uses `wandb.run` hence breaks
2474
- # TODO(jupyter) However _header calls _header_run_info that uses wandb.jupyter that uses
2475
- # `wandb.run` and hence breaks
2476
- self._set_globals()
2477
- self._header(settings=self._settings, printer=self._printer)
2583
+ self._header()
2478
2584
 
2479
2585
  if self._settings.save_code and self._settings.code_dir is not None:
2480
2586
  self.log_code(self._settings.code_dir)
@@ -2504,7 +2610,6 @@ class Run:
2504
2610
  with telemetry.context(run=self) as tel:
2505
2611
  tel.feature.attach = True
2506
2612
 
2507
- self._set_globals()
2508
2613
  self._is_attached = True
2509
2614
  self._on_ready()
2510
2615
 
@@ -2536,6 +2641,9 @@ class Run:
2536
2641
 
2537
2642
  def _on_ready(self) -> None:
2538
2643
  """Event triggered when run is ready for the user."""
2644
+ assert self._wl
2645
+ self._wl.add_active_run(self)
2646
+
2539
2647
  self._register_telemetry_import_hooks()
2540
2648
 
2541
2649
  # start reporting any telemetry changes
@@ -2627,6 +2735,7 @@ class Run:
2627
2735
  docker_image_name=docker_image_name,
2628
2736
  )
2629
2737
 
2738
+ assert job_artifact
2630
2739
  artifact = self.log_artifact(job_artifact)
2631
2740
 
2632
2741
  if not artifact:
@@ -2739,8 +2848,8 @@ class Run:
2739
2848
  unregister_post_import_hook(module_name, run_id)
2740
2849
 
2741
2850
  @_log_to_run
2742
- @_run_decorator._noop_on_finish()
2743
- @_run_decorator._attach
2851
+ @_raise_if_finished
2852
+ @_attach
2744
2853
  def define_metric(
2745
2854
  self,
2746
2855
  name: str,
@@ -2779,14 +2888,14 @@ class Run:
2779
2888
  """
2780
2889
  if summary and "copy" in summary:
2781
2890
  deprecate.deprecate(
2782
- deprecate.Deprecated.run__define_metric_copy,
2891
+ Deprecated.run__define_metric_copy,
2783
2892
  "define_metric(summary='copy') is deprecated and will be removed.",
2784
2893
  self,
2785
2894
  )
2786
2895
 
2787
2896
  if (summary and "best" in summary) or goal is not None:
2788
2897
  deprecate.deprecate(
2789
- deprecate.Deprecated.run__define_metric_best_goal,
2898
+ Deprecated.run__define_metric_best_goal,
2790
2899
  "define_metric(summary='best', goal=...) is deprecated and will be removed. "
2791
2900
  "Use define_metric(summary='min') or define_metric(summary='max') instead.",
2792
2901
  self,
@@ -2881,7 +2990,7 @@ class Run:
2881
2990
  return m
2882
2991
 
2883
2992
  @_log_to_run
2884
- @_run_decorator._attach
2993
+ @_attach
2885
2994
  def watch(
2886
2995
  self,
2887
2996
  models: torch.nn.Module | Sequence[torch.nn.Module],
@@ -2919,7 +3028,7 @@ class Run:
2919
3028
  wandb.sdk._watch(self, models, criterion, log, log_freq, idx, log_graph)
2920
3029
 
2921
3030
  @_log_to_run
2922
- @_run_decorator._attach
3031
+ @_attach
2923
3032
  def unwatch(
2924
3033
  self, models: torch.nn.Module | Sequence[torch.nn.Module] | None = None
2925
3034
  ) -> None:
@@ -2970,8 +3079,8 @@ class Run:
2970
3079
  pass
2971
3080
 
2972
3081
  @_log_to_run
2973
- @_run_decorator._noop_on_finish()
2974
- @_run_decorator._attach
3082
+ @_raise_if_finished
3083
+ @_attach
2975
3084
  def link_artifact(
2976
3085
  self,
2977
3086
  artifact: Artifact,
@@ -3036,8 +3145,8 @@ class Run:
3036
3145
  wandb.termerror(response.error_message)
3037
3146
 
3038
3147
  @_log_to_run
3039
- @_run_decorator._noop_on_finish()
3040
- @_run_decorator._attach
3148
+ @_raise_if_finished
3149
+ @_attach
3041
3150
  def use_artifact(
3042
3151
  self,
3043
3152
  artifact_or_name: str | Artifact,
@@ -3156,8 +3265,8 @@ class Run:
3156
3265
  return artifact
3157
3266
 
3158
3267
  @_log_to_run
3159
- @_run_decorator._noop_on_finish()
3160
- @_run_decorator._attach
3268
+ @_raise_if_finished
3269
+ @_attach
3161
3270
  def log_artifact(
3162
3271
  self,
3163
3272
  artifact_or_path: Artifact | StrPath,
@@ -3199,8 +3308,8 @@ class Run:
3199
3308
  )
3200
3309
 
3201
3310
  @_log_to_run
3202
- @_run_decorator._noop_on_finish()
3203
- @_run_decorator._attach
3311
+ @_raise_if_finished
3312
+ @_attach
3204
3313
  def upsert_artifact(
3205
3314
  self,
3206
3315
  artifact_or_path: Artifact | str,
@@ -3254,8 +3363,8 @@ class Run:
3254
3363
  )
3255
3364
 
3256
3365
  @_log_to_run
3257
- @_run_decorator._noop_on_finish()
3258
- @_run_decorator._attach
3366
+ @_raise_if_finished
3367
+ @_attach
3259
3368
  def finish_artifact(
3260
3369
  self,
3261
3370
  artifact_or_path: Artifact | str,
@@ -3456,8 +3565,8 @@ class Run:
3456
3565
  return artifact, _resolve_aliases(aliases)
3457
3566
 
3458
3567
  @_log_to_run
3459
- @_run_decorator._noop_on_finish()
3460
- @_run_decorator._attach
3568
+ @_raise_if_finished
3569
+ @_attach
3461
3570
  def log_model(
3462
3571
  self,
3463
3572
  path: StrPath,
@@ -3508,8 +3617,8 @@ class Run:
3508
3617
  )
3509
3618
 
3510
3619
  @_log_to_run
3511
- @_run_decorator._noop_on_finish()
3512
- @_run_decorator._attach
3620
+ @_raise_if_finished
3621
+ @_attach
3513
3622
  def use_model(self, name: str) -> FilePathStr:
3514
3623
  """Download the files logged in a model artifact 'name'.
3515
3624
 
@@ -3549,6 +3658,10 @@ class Run:
3549
3658
  Returns:
3550
3659
  path: (str) path to downloaded model artifact file(s).
3551
3660
  """
3661
+ if self._settings._offline:
3662
+ # Downloading artifacts is not supported when offline.
3663
+ raise RuntimeError("`use_model` not supported in offline mode.")
3664
+
3552
3665
  artifact = self.use_artifact(artifact_or_name=name)
3553
3666
  if "model" not in str(artifact.type.lower()):
3554
3667
  raise AssertionError(
@@ -3566,8 +3679,8 @@ class Run:
3566
3679
  return path
3567
3680
 
3568
3681
  @_log_to_run
3569
- @_run_decorator._noop_on_finish()
3570
- @_run_decorator._attach
3682
+ @_raise_if_finished
3683
+ @_attach
3571
3684
  def link_model(
3572
3685
  self,
3573
3686
  path: StrPath,
@@ -3668,8 +3781,8 @@ class Run:
3668
3781
  self.link_artifact(artifact=artifact, target_path=target_path, aliases=aliases)
3669
3782
 
3670
3783
  @_log_to_run
3671
- @_run_decorator._noop_on_finish()
3672
- @_run_decorator._attach
3784
+ @_raise_if_finished
3785
+ @_attach
3673
3786
  def alert(
3674
3787
  self,
3675
3788
  title: str,
@@ -3720,8 +3833,8 @@ class Run:
3720
3833
  return not exception_raised
3721
3834
 
3722
3835
  @_log_to_run
3723
- @_run_decorator._noop_on_finish()
3724
- @_run_decorator._attach
3836
+ @_raise_if_finished
3837
+ @_attach
3725
3838
  def mark_preempting(self) -> None:
3726
3839
  """Mark this run as preempting.
3727
3840
 
@@ -3732,8 +3845,8 @@ class Run:
3732
3845
 
3733
3846
  @property
3734
3847
  @_log_to_run
3735
- @_run_decorator._noop_on_finish()
3736
- @_run_decorator._attach
3848
+ @_raise_if_finished
3849
+ @_attach
3737
3850
  def _system_metrics(self) -> dict[str, list[tuple[datetime, float]]]:
3738
3851
  """Returns a dictionary of system metrics.
3739
3852
 
@@ -3781,8 +3894,8 @@ class Run:
3781
3894
 
3782
3895
  @property
3783
3896
  @_log_to_run
3784
- @_run_decorator._attach
3785
- @_run_decorator._noop_on_finish()
3897
+ @_attach
3898
+ @_raise_if_finished
3786
3899
  def _metadata(self) -> Metadata | None:
3787
3900
  """The metadata associated with this run.
3788
3901
 
@@ -3820,8 +3933,8 @@ class Run:
3820
3933
  return None
3821
3934
 
3822
3935
  @_log_to_run
3823
- @_run_decorator._noop_on_finish()
3824
- @_run_decorator._attach
3936
+ @_raise_if_finished
3937
+ @_attach
3825
3938
  def _metadata_callback(
3826
3939
  self,
3827
3940
  metadata: MetadataRequest,
@@ -3841,59 +3954,41 @@ class Run:
3841
3954
  # ------------------------------------------------------------------------------
3842
3955
  # HEADER
3843
3956
  # ------------------------------------------------------------------------------
3844
- # Note: All the header methods are static methods since we want to share the printing logic
3845
- # with the service execution path that doesn't have access to the run instance
3846
- @staticmethod
3847
- def _header(
3848
- *,
3849
- settings: Settings,
3850
- printer: printer.Printer,
3851
- ) -> None:
3852
- Run._header_wandb_version_info(settings=settings, printer=printer)
3853
- Run._header_sync_info(settings=settings, printer=printer)
3854
- Run._header_run_info(settings=settings, printer=printer)
3957
+ def _header(self) -> None:
3958
+ self._header_wandb_version_info()
3959
+ self._header_sync_info()
3960
+ self._header_run_info()
3855
3961
 
3856
- @staticmethod
3857
- def _header_wandb_version_info(
3858
- *,
3859
- settings: Settings,
3860
- printer: printer.Printer,
3861
- ) -> None:
3862
- if settings.quiet or settings.silent:
3962
+ def _header_wandb_version_info(self) -> None:
3963
+ if self._settings.quiet or self._settings.silent:
3863
3964
  return
3864
3965
 
3865
3966
  # TODO: add this to a higher verbosity level
3866
- printer.display(f"Tracking run with wandb version {wandb.__version__}")
3967
+ self._printer.display(f"Tracking run with wandb version {wandb.__version__}")
3867
3968
 
3868
- @staticmethod
3869
- def _header_sync_info(
3870
- *,
3871
- settings: Settings,
3872
- printer: printer.Printer,
3873
- ) -> None:
3874
- if settings._offline:
3875
- printer.display(
3969
+ def _header_sync_info(self) -> None:
3970
+ if self._settings._offline:
3971
+ self._printer.display(
3876
3972
  [
3877
- f"W&B syncing is set to {printer.code('`offline`')} in this directory. ",
3878
- f"Run {printer.code('`wandb online`')} or set {printer.code('WANDB_MODE=online')} "
3879
- "to enable cloud syncing.",
3973
+ f"W&B syncing is set to {self._printer.code('`offline`')}"
3974
+ f" in this directory. Run {self._printer.code('`wandb online`')}"
3975
+ f" or set {self._printer.code('WANDB_MODE=online')}"
3976
+ " to enable cloud syncing.",
3880
3977
  ]
3881
3978
  )
3882
3979
  else:
3883
- info = [f"Run data is saved locally in {printer.files(settings.sync_dir)}"]
3884
- if not printer.supports_html:
3980
+ sync_dir = self._settings.sync_dir
3981
+ info = [f"Run data is saved locally in {self._printer.files(sync_dir)}"]
3982
+ if not self._printer.supports_html:
3885
3983
  info.append(
3886
- f"Run {printer.code('`wandb offline`')} to turn off syncing."
3984
+ f"Run {self._printer.code('`wandb offline`')} to turn off syncing."
3887
3985
  )
3888
- if not settings.quiet and not settings.silent:
3889
- printer.display(info)
3986
+ if not self._settings.quiet and not self._settings.silent:
3987
+ self._printer.display(info)
3988
+
3989
+ def _header_run_info(self) -> None:
3990
+ settings, printer = self._settings, self._printer
3890
3991
 
3891
- @staticmethod
3892
- def _header_run_info(
3893
- *,
3894
- settings: Settings,
3895
- printer: printer.Printer,
3896
- ) -> None:
3897
3992
  if settings._offline or settings.silent:
3898
3993
  return
3899
3994
 
@@ -3911,12 +4006,13 @@ class Run:
3911
4006
  return
3912
4007
 
3913
4008
  if printer.supports_html:
3914
- if not wandb.jupyter.maybe_display(): # type: ignore
4009
+ import wandb.jupyter
4010
+
4011
+ if not wandb.jupyter.display_if_magic_is_used(self):
3915
4012
  run_line = f"<strong>{printer.link(run_url, run_name)}</strong>"
3916
4013
  project_line, sweep_line = "", ""
3917
4014
 
3918
- # TODO(settings): make settings the source of truth
3919
- if not wandb.jupyter.quiet(): # type: ignore
4015
+ if not settings.quiet:
3920
4016
  doc_html = printer.link(url_registry.url("developer-guide"), "docs")
3921
4017
 
3922
4018
  project_html = printer.link(project_url, "Weights & Biases")