wandb 0.19.9__py3-none-win32.whl → 0.19.11__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 (156) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +6 -3
  3. wandb/_pydantic/__init__.py +14 -8
  4. wandb/_pydantic/base.py +51 -36
  5. wandb/_pydantic/utils.py +73 -0
  6. wandb/_pydantic/v1_compat.py +79 -57
  7. wandb/apis/public/__init__.py +2 -2
  8. wandb/apis/public/api.py +684 -4
  9. wandb/apis/public/artifacts.py +377 -677
  10. wandb/apis/public/automations.py +69 -0
  11. wandb/apis/public/integrations.py +180 -0
  12. wandb/apis/public/projects.py +29 -0
  13. wandb/apis/public/registries/__init__.py +0 -0
  14. wandb/apis/public/registries/_freezable_list.py +179 -0
  15. wandb/apis/public/{registries.py → registries/registries_search.py} +22 -129
  16. wandb/apis/public/registries/registry.py +357 -0
  17. wandb/apis/public/registries/utils.py +140 -0
  18. wandb/apis/public/runs.py +58 -56
  19. wandb/apis/public/utils.py +107 -1
  20. wandb/automations/__init__.py +73 -0
  21. wandb/automations/_filters/__init__.py +40 -0
  22. wandb/automations/_filters/expressions.py +181 -0
  23. wandb/automations/_filters/operators.py +258 -0
  24. wandb/automations/_filters/run_metrics.py +332 -0
  25. wandb/automations/_generated/__init__.py +177 -0
  26. wandb/automations/_generated/create_automation.py +17 -0
  27. wandb/automations/_generated/create_generic_webhook_integration.py +43 -0
  28. wandb/automations/_generated/delete_automation.py +17 -0
  29. wandb/automations/_generated/enums.py +33 -0
  30. wandb/automations/_generated/fragments.py +358 -0
  31. wandb/automations/_generated/generic_webhook_integrations_by_entity.py +22 -0
  32. wandb/automations/_generated/get_automations.py +24 -0
  33. wandb/automations/_generated/get_automations_by_entity.py +26 -0
  34. wandb/automations/_generated/input_types.py +104 -0
  35. wandb/automations/_generated/integrations_by_entity.py +22 -0
  36. wandb/automations/_generated/operations.py +647 -0
  37. wandb/automations/_generated/slack_integrations_by_entity.py +22 -0
  38. wandb/automations/_generated/update_automation.py +17 -0
  39. wandb/automations/_utils.py +237 -0
  40. wandb/automations/_validators.py +165 -0
  41. wandb/automations/actions.py +220 -0
  42. wandb/automations/automations.py +87 -0
  43. wandb/automations/events.py +287 -0
  44. wandb/automations/integrations.py +45 -0
  45. wandb/automations/scopes.py +78 -0
  46. wandb/beta/workflows.py +9 -10
  47. wandb/bin/gpu_stats.exe +0 -0
  48. wandb/bin/wandb-core +0 -0
  49. wandb/cli/cli.py +3 -3
  50. wandb/env.py +11 -0
  51. wandb/integration/keras/keras.py +2 -1
  52. wandb/integration/langchain/wandb_tracer.py +2 -1
  53. wandb/jupyter.py +137 -118
  54. wandb/old/settings.py +4 -1
  55. wandb/old/summary.py +0 -2
  56. wandb/proto/v3/wandb_internal_pb2.py +297 -292
  57. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  58. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  59. wandb/proto/v4/wandb_internal_pb2.py +292 -292
  60. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  61. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  62. wandb/proto/v5/wandb_internal_pb2.py +292 -292
  63. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  64. wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
  65. wandb/proto/v6/wandb_base_pb2.py +41 -0
  66. wandb/proto/v6/wandb_internal_pb2.py +393 -0
  67. wandb/proto/v6/wandb_server_pb2.py +78 -0
  68. wandb/proto/v6/wandb_settings_pb2.py +58 -0
  69. wandb/proto/v6/wandb_telemetry_pb2.py +52 -0
  70. wandb/proto/wandb_base_pb2.py +2 -0
  71. wandb/proto/wandb_deprecated.py +8 -0
  72. wandb/proto/wandb_internal_pb2.py +3 -1
  73. wandb/proto/wandb_server_pb2.py +2 -0
  74. wandb/proto/wandb_settings_pb2.py +2 -0
  75. wandb/proto/wandb_telemetry_pb2.py +2 -0
  76. wandb/sdk/artifacts/_generated/__init__.py +289 -0
  77. wandb/sdk/artifacts/_generated/add_aliases.py +21 -0
  78. wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +43 -0
  79. wandb/sdk/artifacts/_generated/artifact_version_files.py +36 -0
  80. wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +36 -0
  81. wandb/sdk/artifacts/_generated/delete_aliases.py +21 -0
  82. wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +25 -0
  83. wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +35 -0
  84. wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +35 -0
  85. wandb/sdk/artifacts/_generated/enums.py +17 -0
  86. wandb/sdk/artifacts/_generated/fetch_linked_artifacts.py +67 -0
  87. wandb/sdk/artifacts/_generated/fragments.py +221 -0
  88. wandb/sdk/artifacts/_generated/input_types.py +28 -0
  89. wandb/sdk/artifacts/_generated/move_artifact_collection.py +35 -0
  90. wandb/sdk/artifacts/_generated/operations.py +611 -0
  91. wandb/sdk/artifacts/_generated/project_artifact_collection.py +101 -0
  92. wandb/sdk/artifacts/_generated/project_artifact_collections.py +33 -0
  93. wandb/sdk/artifacts/_generated/project_artifact_type.py +24 -0
  94. wandb/sdk/artifacts/_generated/project_artifact_types.py +24 -0
  95. wandb/sdk/artifacts/_generated/project_artifacts.py +42 -0
  96. wandb/sdk/artifacts/_generated/run_input_artifacts.py +51 -0
  97. wandb/sdk/artifacts/_generated/run_output_artifacts.py +51 -0
  98. wandb/sdk/artifacts/_generated/update_artifact.py +26 -0
  99. wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +35 -0
  100. wandb/sdk/artifacts/_generated/update_artifact_sequence.py +35 -0
  101. wandb/sdk/artifacts/_graphql_fragments.py +57 -79
  102. wandb/sdk/artifacts/_validators.py +120 -1
  103. wandb/sdk/artifacts/artifact.py +419 -215
  104. wandb/sdk/artifacts/artifact_file_cache.py +4 -6
  105. wandb/sdk/artifacts/artifact_manifest_entry.py +13 -3
  106. wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
  107. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +182 -1
  108. wandb/sdk/artifacts/storage_policy.py +3 -0
  109. wandb/sdk/data_types/base_types/media.py +2 -3
  110. wandb/sdk/data_types/base_types/wb_value.py +34 -11
  111. wandb/sdk/data_types/html.py +36 -9
  112. wandb/sdk/data_types/image.py +12 -12
  113. wandb/sdk/data_types/table.py +5 -0
  114. wandb/sdk/data_types/trace_tree.py +2 -0
  115. wandb/sdk/data_types/utils.py +1 -1
  116. wandb/sdk/data_types/video.py +59 -57
  117. wandb/sdk/interface/interface.py +4 -3
  118. wandb/sdk/internal/internal_api.py +21 -31
  119. wandb/sdk/internal/profiler.py +6 -5
  120. wandb/sdk/internal/run.py +13 -6
  121. wandb/sdk/internal/sender.py +5 -2
  122. wandb/sdk/launch/sweeps/utils.py +8 -0
  123. wandb/sdk/lib/apikey.py +25 -4
  124. wandb/sdk/lib/asyncio_compat.py +1 -1
  125. wandb/sdk/lib/deprecate.py +13 -22
  126. wandb/sdk/lib/disabled.py +2 -1
  127. wandb/sdk/lib/printer.py +37 -8
  128. wandb/sdk/lib/printer_asyncio.py +46 -0
  129. wandb/sdk/lib/redirect.py +10 -5
  130. wandb/sdk/projects/_generated/__init__.py +47 -0
  131. wandb/sdk/projects/_generated/delete_project.py +22 -0
  132. wandb/sdk/projects/_generated/enums.py +4 -0
  133. wandb/sdk/projects/_generated/fetch_registry.py +22 -0
  134. wandb/sdk/projects/_generated/fragments.py +41 -0
  135. wandb/sdk/projects/_generated/input_types.py +13 -0
  136. wandb/sdk/projects/_generated/operations.py +88 -0
  137. wandb/sdk/projects/_generated/rename_project.py +27 -0
  138. wandb/sdk/projects/_generated/upsert_registry_project.py +27 -0
  139. wandb/sdk/service/server_sock.py +19 -14
  140. wandb/sdk/service/service.py +18 -8
  141. wandb/sdk/service/streams.py +5 -0
  142. wandb/sdk/verify/verify.py +6 -3
  143. wandb/sdk/wandb_init.py +217 -70
  144. wandb/sdk/wandb_login.py +13 -4
  145. wandb/sdk/wandb_run.py +419 -295
  146. wandb/sdk/wandb_settings.py +27 -10
  147. wandb/sdk/wandb_setup.py +61 -0
  148. wandb/util.py +33 -29
  149. {wandb-0.19.9.dist-info → wandb-0.19.11.dist-info}/METADATA +5 -5
  150. {wandb-0.19.9.dist-info → wandb-0.19.11.dist-info}/RECORD +153 -83
  151. wandb/_globals.py +0 -19
  152. wandb/sdk/internal/_generated/base.py +0 -226
  153. wandb/sdk/internal/_generated/typing_compat.py +0 -14
  154. {wandb-0.19.9.dist-info → wandb-0.19.11.dist-info}/WHEEL +0 -0
  155. {wandb-0.19.9.dist-info → wandb-0.19.11.dist-info}/entry_points.txt +0 -0
  156. {wandb-0.19.9.dist-info → wandb-0.19.11.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
+
426
413
 
427
- return wrapper
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.
428
418
 
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)
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
+ """
422
+
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
1060
1107
 
1061
- @_run_decorator._noop_on_finish()
1062
1108
  @_log_to_run
1063
- @_run_decorator._attach
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.
1113
+
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
1127
+ @_log_to_run
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 = ".",
@@ -1144,22 +1222,40 @@ class Run:
1144
1222
  )
1145
1223
  return None
1146
1224
 
1147
- return self._log_artifact(art)
1225
+ artifact = self._log_artifact(art)
1148
1226
 
1149
- @_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.
1227
+ self._config.update(
1228
+ {"_wandb": {"code_path": artifact.name}},
1229
+ allow_val_change=True,
1230
+ )
1152
1231
 
1153
- Offline runs will not have a project url.
1154
- """
1155
- if self._settings._offline:
1156
- wandb.termwarn("URL not available in offline run")
1157
- return None
1158
- return self._settings.project_url
1232
+ return artifact
1159
1233
 
1160
1234
  @_log_to_run
1161
1235
  def get_sweep_url(self) -> str | None:
1162
- """Return the url for the sweep associated with the run, if there is one."""
1236
+ """The URL of the sweep associated with the run, if there is one.
1237
+
1238
+ Offline runs do not have a sweep URL.
1239
+
1240
+ Note: this method is deprecated and will be removed in a future release.
1241
+ Please use `run.sweep_url` instead.
1242
+ """
1243
+ deprecate.deprecate(
1244
+ field_name=Deprecated.run__get_sweep_url,
1245
+ warning_message=(
1246
+ "The get_sweep_url method is deprecated and will be removed in a"
1247
+ " future release. Please use `run.sweep_url` instead."
1248
+ ),
1249
+ )
1250
+ return self.sweep_url
1251
+
1252
+ @property
1253
+ @_attach
1254
+ def sweep_url(self) -> str | None:
1255
+ """URL of the sweep associated with the run, if there is one.
1256
+
1257
+ Offline runs do not have a sweep URL.
1258
+ """
1163
1259
  if self._settings._offline:
1164
1260
  wandb.termwarn("URL not available in offline run")
1165
1261
  return None
@@ -1167,7 +1263,27 @@ class Run:
1167
1263
 
1168
1264
  @_log_to_run
1169
1265
  def get_url(self) -> str | None:
1170
- """Return the url for the W&B run, if there is one.
1266
+ """URL of the W&B run, if there is one.
1267
+
1268
+ Offline runs do not have a URL.
1269
+
1270
+ Note: this method is deprecated and will be removed in a future release.
1271
+ Please use `run.url` instead.
1272
+ """
1273
+ deprecate.deprecate(
1274
+ field_name=Deprecated.run__get_url,
1275
+ warning_message=(
1276
+ "The get_url method is deprecated and will be removed in a"
1277
+ " future release. Please use `run.url` instead."
1278
+ ),
1279
+ )
1280
+ return self.url
1281
+
1282
+ @property
1283
+ @_log_to_run
1284
+ @_attach
1285
+ def url(self) -> str | None:
1286
+ """The url for the W&B run, if there is one.
1171
1287
 
1172
1288
  Offline runs will not have a url.
1173
1289
  """
@@ -1178,14 +1294,7 @@ class Run:
1178
1294
 
1179
1295
  @property
1180
1296
  @_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
1297
+ @_attach
1189
1298
  def entity(self) -> str:
1190
1299
  """The name of the W&B entity associated with the run.
1191
1300
 
@@ -1280,7 +1389,7 @@ class Run:
1280
1389
  self._label_probe_lines(lines)
1281
1390
 
1282
1391
  @_log_to_run
1283
- @_run_decorator._attach
1392
+ @_attach
1284
1393
  def display(self, height: int = 420, hidden: bool = False) -> bool:
1285
1394
  """Display this run in jupyter."""
1286
1395
  if self._settings.silent:
@@ -1300,7 +1409,7 @@ class Run:
1300
1409
  return False
1301
1410
 
1302
1411
  @_log_to_run
1303
- @_run_decorator._attach
1412
+ @_attach
1304
1413
  def to_html(self, height: int = 420, hidden: bool = False) -> str:
1305
1414
  """Generate HTML containing an iframe displaying the current run."""
1306
1415
  url = self._settings.run_url + "?jupyter=true"
@@ -1317,7 +1426,7 @@ class Run:
1317
1426
  return {"text/html": self.to_html(hidden=True)}
1318
1427
 
1319
1428
  @_log_to_run
1320
- @_run_decorator._noop_on_finish()
1429
+ @_raise_if_finished
1321
1430
  def _config_callback(
1322
1431
  self,
1323
1432
  key: tuple[str, ...] | str | None = None,
@@ -1339,6 +1448,8 @@ class Run:
1339
1448
  assert isinstance(val, dict)
1340
1449
  public_api = self._public_api()
1341
1450
  artifact = Artifact._from_id(val["id"], public_api.client)
1451
+
1452
+ assert artifact
1342
1453
  return self.use_artifact(artifact, use_as=key)
1343
1454
  elif _is_artifact_string(val):
1344
1455
  # this will never fail, but is required to make mypy happy
@@ -1357,6 +1468,7 @@ class Run:
1357
1468
  # in the future we'll need to support using artifacts from
1358
1469
  # different instances of wandb.
1359
1470
 
1471
+ assert artifact
1360
1472
  return self.use_artifact(artifact, use_as=key)
1361
1473
  elif _is_artifact_object(val):
1362
1474
  return self.use_artifact(val, use_as=key)
@@ -1369,7 +1481,7 @@ class Run:
1369
1481
  self._config_callback(key=("_wandb", key), val=val)
1370
1482
 
1371
1483
  @_log_to_run
1372
- @_run_decorator._noop_on_finish()
1484
+ @_raise_if_finished
1373
1485
  def _summary_update_callback(self, summary_record: SummaryRecord) -> None:
1374
1486
  with telemetry.context(run=self) as tel:
1375
1487
  tel.feature.set_summary = True
@@ -1400,7 +1512,16 @@ class Run:
1400
1512
  self._backend.interface._publish_metric(metric_record)
1401
1513
 
1402
1514
  @_log_to_run
1403
- def _datatypes_callback(self, fname: str) -> None:
1515
+ def _publish_file(self, fname: str) -> None:
1516
+ """Mark a run file to be uploaded with the run.
1517
+
1518
+ This is a W&B-internal function: it can be used by other internal
1519
+ wandb code.
1520
+
1521
+ Args:
1522
+ fname: The path to the file in the run's files directory, relative
1523
+ to the run's files directory.
1524
+ """
1404
1525
  if not self._backend or not self._backend.interface:
1405
1526
  return
1406
1527
  files: FilesDict = dict(files=[(GlobStr(fname), "now")])
@@ -1502,7 +1623,7 @@ class Run:
1502
1623
  self._backend.interface.publish_output(name, data)
1503
1624
 
1504
1625
  @_log_to_run
1505
- @_run_decorator._noop_on_finish(only_warn=True)
1626
+ @_raise_if_finished
1506
1627
  def _console_raw_callback(self, name: str, data: str) -> None:
1507
1628
  # logger.info("console callback: %s, %s", name, data)
1508
1629
 
@@ -1548,6 +1669,9 @@ class Run:
1548
1669
  if run_obj.start_time:
1549
1670
  self._start_time = run_obj.start_time.ToMicroseconds() / 1e6
1550
1671
 
1672
+ if run_obj.runtime:
1673
+ self._start_runtime = run_obj.runtime
1674
+
1551
1675
  # Grab the config from resuming
1552
1676
  if run_obj.config:
1553
1677
  c_dict = config_util.dict_no_value_from_proto_list(run_obj.config.update)
@@ -1683,9 +1807,9 @@ class Run:
1683
1807
  self._step += 1
1684
1808
 
1685
1809
  @_log_to_run
1686
- @_run_decorator._noop
1687
- @_run_decorator._noop_on_finish()
1688
- @_run_decorator._attach
1810
+ @_noop_if_forked_with_no_service
1811
+ @_raise_if_finished
1812
+ @_attach
1689
1813
  def log(
1690
1814
  self,
1691
1815
  data: dict[str, Any],
@@ -1931,10 +2055,11 @@ class Run:
1931
2055
 
1932
2056
  if sync is not None:
1933
2057
  deprecate.deprecate(
1934
- field_name=deprecate.Deprecated.run__log_sync,
2058
+ field_name=Deprecated.run__log_sync,
1935
2059
  warning_message=(
1936
2060
  "`sync` argument is deprecated and does not affect the behaviour of `wandb.log`"
1937
2061
  ),
2062
+ run=self,
1938
2063
  )
1939
2064
  if self._settings._shared and step is not None:
1940
2065
  wandb.termwarn(
@@ -1946,8 +2071,8 @@ class Run:
1946
2071
  self._log(data=data, step=step, commit=commit)
1947
2072
 
1948
2073
  @_log_to_run
1949
- @_run_decorator._noop_on_finish()
1950
- @_run_decorator._attach
2074
+ @_raise_if_finished
2075
+ @_attach
1951
2076
  def save(
1952
2077
  self,
1953
2078
  glob_str: str | os.PathLike | None = None,
@@ -2004,11 +2129,12 @@ class Run:
2004
2129
  if glob_str is None:
2005
2130
  # noop for historical reasons, run.save() may be called in legacy code
2006
2131
  deprecate.deprecate(
2007
- field_name=deprecate.Deprecated.run__save_no_args,
2132
+ field_name=Deprecated.run__save_no_args,
2008
2133
  warning_message=(
2009
2134
  "Calling wandb.run.save without any arguments is deprecated."
2010
2135
  "Changes to attributes are automatically persisted."
2011
2136
  ),
2137
+ run=self,
2012
2138
  )
2013
2139
  return True
2014
2140
 
@@ -2132,7 +2258,7 @@ class Run:
2132
2258
  return [str(f) for f in globbed_files]
2133
2259
 
2134
2260
  @_log_to_run
2135
- @_run_decorator._attach
2261
+ @_attach
2136
2262
  def restore(
2137
2263
  self,
2138
2264
  name: str,
@@ -2148,8 +2274,8 @@ class Run:
2148
2274
  )
2149
2275
 
2150
2276
  @_log_to_run
2151
- @_run_decorator._noop
2152
- @_run_decorator._attach
2277
+ @_noop_if_forked_with_no_service
2278
+ @_attach
2153
2279
  def finish(
2154
2280
  self,
2155
2281
  exit_code: int | None = None,
@@ -2173,11 +2299,12 @@ class Run:
2173
2299
  """
2174
2300
  if quiet is not None:
2175
2301
  deprecate.deprecate(
2176
- field_name=deprecate.Deprecated.run__finish_quiet,
2302
+ field_name=Deprecated.run__finish_quiet,
2177
2303
  warning_message=(
2178
2304
  "The `quiet` argument to `wandb.run.finish()` is deprecated, "
2179
2305
  "use `wandb.Settings(quiet=...)` to set this instead."
2180
2306
  ),
2307
+ run=self,
2181
2308
  )
2182
2309
  return self._finish(exit_code)
2183
2310
 
@@ -2186,6 +2313,11 @@ class Run:
2186
2313
  self,
2187
2314
  exit_code: int | None = None,
2188
2315
  ) -> None:
2316
+ if self._is_finished:
2317
+ return
2318
+
2319
+ assert self._wl
2320
+
2189
2321
  logger.info(f"finishing run {self._get_path()}")
2190
2322
  with telemetry.context(run=self) as tel:
2191
2323
  tel.feature.finish = True
@@ -2199,6 +2331,7 @@ class Run:
2199
2331
  # Early-stage hooks may use methods that require _is_finished
2200
2332
  # to be False, so we set this after running those hooks.
2201
2333
  self._is_finished = True
2334
+ self._wl.remove_active_run(self)
2202
2335
 
2203
2336
  try:
2204
2337
  self._atexit_cleanup(exit_code=exit_code)
@@ -2214,31 +2347,32 @@ class Run:
2214
2347
  #
2215
2348
  # TODO: Why not do this in _atexit_cleanup()?
2216
2349
  if self._settings.run_id:
2217
- assert self._wl
2218
2350
  service = self._wl.assert_service()
2219
2351
  service.inform_finish(run_id=self._settings.run_id)
2220
2352
 
2221
2353
  finally:
2222
- module.unset_globals()
2354
+ if wandb.run is self:
2355
+ module.unset_globals()
2223
2356
  wandb._sentry.end_session()
2224
2357
 
2225
2358
  @_log_to_run
2226
- @_run_decorator._noop
2227
- @_run_decorator._attach
2359
+ @_noop_if_forked_with_no_service
2360
+ @_attach
2228
2361
  def join(self, exit_code: int | None = None) -> None:
2229
2362
  """Deprecated alias for `finish()` - use finish instead."""
2230
2363
  if hasattr(self, "_telemetry_obj"):
2231
2364
  deprecate.deprecate(
2232
- field_name=deprecate.Deprecated.run__join,
2365
+ field_name=Deprecated.run__join,
2233
2366
  warning_message=(
2234
2367
  "wandb.run.join() is deprecated, please use wandb.run.finish()."
2235
2368
  ),
2369
+ run=self,
2236
2370
  )
2237
2371
  self._finish(exit_code=exit_code)
2238
2372
 
2239
2373
  @_log_to_run
2240
- @_run_decorator._noop_on_finish()
2241
- @_run_decorator._attach
2374
+ @_raise_if_finished
2375
+ @_attach
2242
2376
  def status(
2243
2377
  self,
2244
2378
  ) -> RunStatus:
@@ -2270,25 +2404,6 @@ class Run:
2270
2404
  }
2271
2405
  self._config_callback(val=config, key=("_wandb", "visualize", visualize_key))
2272
2406
 
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
2407
  def _redirect(
2293
2408
  self,
2294
2409
  stdout_slave_fd: int | None,
@@ -2327,6 +2442,7 @@ class Run:
2327
2442
  lambda data: self._console_callback("stdout", data),
2328
2443
  self._output_writer.write, # type: ignore
2329
2444
  ],
2445
+ flush_periodically=(self._settings.mode == "online"),
2330
2446
  )
2331
2447
  err_redir = redirect.Redirect(
2332
2448
  src="stderr",
@@ -2334,6 +2450,7 @@ class Run:
2334
2450
  lambda data: self._console_callback("stderr", data),
2335
2451
  self._output_writer.write, # type: ignore
2336
2452
  ],
2453
+ flush_periodically=(self._settings.mode == "online"),
2337
2454
  )
2338
2455
  if os.name == "nt":
2339
2456
 
@@ -2359,6 +2476,7 @@ class Run:
2359
2476
  lambda data: self._console_callback("stdout", data),
2360
2477
  self._output_writer.write, # type: ignore
2361
2478
  ],
2479
+ flush_periodically=(self._settings.mode == "online"),
2362
2480
  )
2363
2481
  err_redir = redirect.StreamWrapper(
2364
2482
  src="stderr",
@@ -2366,6 +2484,7 @@ class Run:
2366
2484
  lambda data: self._console_callback("stderr", data),
2367
2485
  self._output_writer.write, # type: ignore
2368
2486
  ],
2487
+ flush_periodically=(self._settings.mode == "online"),
2369
2488
  )
2370
2489
  elif console == "wrap_raw":
2371
2490
  logger.info("Wrapping output streams.")
@@ -2468,13 +2587,7 @@ class Run:
2468
2587
  self._output_writer = None
2469
2588
 
2470
2589
  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)
2590
+ self._header()
2478
2591
 
2479
2592
  if self._settings.save_code and self._settings.code_dir is not None:
2480
2593
  self.log_code(self._settings.code_dir)
@@ -2504,7 +2617,6 @@ class Run:
2504
2617
  with telemetry.context(run=self) as tel:
2505
2618
  tel.feature.attach = True
2506
2619
 
2507
- self._set_globals()
2508
2620
  self._is_attached = True
2509
2621
  self._on_ready()
2510
2622
 
@@ -2536,6 +2648,9 @@ class Run:
2536
2648
 
2537
2649
  def _on_ready(self) -> None:
2538
2650
  """Event triggered when run is ready for the user."""
2651
+ assert self._wl
2652
+ self._wl.add_active_run(self)
2653
+
2539
2654
  self._register_telemetry_import_hooks()
2540
2655
 
2541
2656
  # start reporting any telemetry changes
@@ -2627,6 +2742,7 @@ class Run:
2627
2742
  docker_image_name=docker_image_name,
2628
2743
  )
2629
2744
 
2745
+ assert job_artifact
2630
2746
  artifact = self.log_artifact(job_artifact)
2631
2747
 
2632
2748
  if not artifact:
@@ -2739,8 +2855,8 @@ class Run:
2739
2855
  unregister_post_import_hook(module_name, run_id)
2740
2856
 
2741
2857
  @_log_to_run
2742
- @_run_decorator._noop_on_finish()
2743
- @_run_decorator._attach
2858
+ @_raise_if_finished
2859
+ @_attach
2744
2860
  def define_metric(
2745
2861
  self,
2746
2862
  name: str,
@@ -2779,14 +2895,14 @@ class Run:
2779
2895
  """
2780
2896
  if summary and "copy" in summary:
2781
2897
  deprecate.deprecate(
2782
- deprecate.Deprecated.run__define_metric_copy,
2898
+ Deprecated.run__define_metric_copy,
2783
2899
  "define_metric(summary='copy') is deprecated and will be removed.",
2784
2900
  self,
2785
2901
  )
2786
2902
 
2787
2903
  if (summary and "best" in summary) or goal is not None:
2788
2904
  deprecate.deprecate(
2789
- deprecate.Deprecated.run__define_metric_best_goal,
2905
+ Deprecated.run__define_metric_best_goal,
2790
2906
  "define_metric(summary='best', goal=...) is deprecated and will be removed. "
2791
2907
  "Use define_metric(summary='min') or define_metric(summary='max') instead.",
2792
2908
  self,
@@ -2881,7 +2997,7 @@ class Run:
2881
2997
  return m
2882
2998
 
2883
2999
  @_log_to_run
2884
- @_run_decorator._attach
3000
+ @_attach
2885
3001
  def watch(
2886
3002
  self,
2887
3003
  models: torch.nn.Module | Sequence[torch.nn.Module],
@@ -2919,7 +3035,7 @@ class Run:
2919
3035
  wandb.sdk._watch(self, models, criterion, log, log_freq, idx, log_graph)
2920
3036
 
2921
3037
  @_log_to_run
2922
- @_run_decorator._attach
3038
+ @_attach
2923
3039
  def unwatch(
2924
3040
  self, models: torch.nn.Module | Sequence[torch.nn.Module] | None = None
2925
3041
  ) -> None:
@@ -2970,14 +3086,14 @@ class Run:
2970
3086
  pass
2971
3087
 
2972
3088
  @_log_to_run
2973
- @_run_decorator._noop_on_finish()
2974
- @_run_decorator._attach
3089
+ @_raise_if_finished
3090
+ @_attach
2975
3091
  def link_artifact(
2976
3092
  self,
2977
3093
  artifact: Artifact,
2978
3094
  target_path: str,
2979
3095
  aliases: list[str] | None = None,
2980
- ) -> None:
3096
+ ) -> Artifact | None:
2981
3097
  """Link the given artifact to a portfolio (a promoted collection of artifacts).
2982
3098
 
2983
3099
  The linked artifact will be visible in the UI for the specified portfolio.
@@ -2991,7 +3107,7 @@ class Run:
2991
3107
  The alias "latest" will always be applied to the latest version of an artifact that is linked.
2992
3108
 
2993
3109
  Returns:
2994
- None
3110
+ The linked artifact if linking was successful, otherwise None.
2995
3111
 
2996
3112
  """
2997
3113
  portfolio, project, entity = wandb.util._parse_entity_project_item(target_path)
@@ -2999,7 +3115,7 @@ class Run:
2999
3115
  aliases = []
3000
3116
 
3001
3117
  if not self._backend or not self._backend.interface:
3002
- return
3118
+ return None
3003
3119
 
3004
3120
  if artifact.is_draft() and not artifact._is_draft_save_started():
3005
3121
  artifact = self._log_artifact(artifact)
@@ -3013,12 +3129,13 @@ class Run:
3013
3129
 
3014
3130
  organization = ""
3015
3131
  if is_artifact_registry_project(project):
3016
- organization = entity
3132
+ organization = entity or self.settings.organization or ""
3017
3133
  # In a Registry linking, the entity is used to fetch the organization of the artifact
3018
3134
  # therefore the source artifact's entity is passed to the backend
3019
3135
  entity = artifact._source_entity
3136
+ project = project or self.project
3137
+ entity = entity or self.entity
3020
3138
  handle = self._backend.interface.deliver_link_artifact(
3021
- self,
3022
3139
  artifact,
3023
3140
  portfolio,
3024
3141
  aliases,
@@ -3034,10 +3151,28 @@ class Run:
3034
3151
  response = result.response.link_artifact_response
3035
3152
  if response.error_message:
3036
3153
  wandb.termerror(response.error_message)
3154
+ if response.version_index is None:
3155
+ wandb.termerror(
3156
+ "Error fetching the linked artifact's version index after linking"
3157
+ )
3158
+ return None
3159
+
3160
+ try:
3161
+ artifact_name = f"{entity}/{project}/{portfolio}:v{response.version_index}"
3162
+ if is_artifact_registry_project(project):
3163
+ if organization:
3164
+ artifact_name = f"{organization}/{project}/{portfolio}:v{response.version_index}"
3165
+ else:
3166
+ artifact_name = f"{project}/{portfolio}:v{response.version_index}"
3167
+ linked_artifact = self._public_api()._artifact(artifact_name)
3168
+ except Exception as e:
3169
+ wandb.termerror(f"Error fetching link artifact after linking: {e}")
3170
+ return None
3171
+ return linked_artifact
3037
3172
 
3038
3173
  @_log_to_run
3039
- @_run_decorator._noop_on_finish()
3040
- @_run_decorator._attach
3174
+ @_raise_if_finished
3175
+ @_attach
3041
3176
  def use_artifact(
3042
3177
  self,
3043
3178
  artifact_or_name: str | Artifact,
@@ -3156,8 +3291,8 @@ class Run:
3156
3291
  return artifact
3157
3292
 
3158
3293
  @_log_to_run
3159
- @_run_decorator._noop_on_finish()
3160
- @_run_decorator._attach
3294
+ @_raise_if_finished
3295
+ @_attach
3161
3296
  def log_artifact(
3162
3297
  self,
3163
3298
  artifact_or_path: Artifact | StrPath,
@@ -3199,8 +3334,8 @@ class Run:
3199
3334
  )
3200
3335
 
3201
3336
  @_log_to_run
3202
- @_run_decorator._noop_on_finish()
3203
- @_run_decorator._attach
3337
+ @_raise_if_finished
3338
+ @_attach
3204
3339
  def upsert_artifact(
3205
3340
  self,
3206
3341
  artifact_or_path: Artifact | str,
@@ -3254,8 +3389,8 @@ class Run:
3254
3389
  )
3255
3390
 
3256
3391
  @_log_to_run
3257
- @_run_decorator._noop_on_finish()
3258
- @_run_decorator._attach
3392
+ @_raise_if_finished
3393
+ @_attach
3259
3394
  def finish_artifact(
3260
3395
  self,
3261
3396
  artifact_or_path: Artifact | str,
@@ -3456,8 +3591,8 @@ class Run:
3456
3591
  return artifact, _resolve_aliases(aliases)
3457
3592
 
3458
3593
  @_log_to_run
3459
- @_run_decorator._noop_on_finish()
3460
- @_run_decorator._attach
3594
+ @_raise_if_finished
3595
+ @_attach
3461
3596
  def log_model(
3462
3597
  self,
3463
3598
  path: StrPath,
@@ -3508,8 +3643,8 @@ class Run:
3508
3643
  )
3509
3644
 
3510
3645
  @_log_to_run
3511
- @_run_decorator._noop_on_finish()
3512
- @_run_decorator._attach
3646
+ @_raise_if_finished
3647
+ @_attach
3513
3648
  def use_model(self, name: str) -> FilePathStr:
3514
3649
  """Download the files logged in a model artifact 'name'.
3515
3650
 
@@ -3549,6 +3684,10 @@ class Run:
3549
3684
  Returns:
3550
3685
  path: (str) path to downloaded model artifact file(s).
3551
3686
  """
3687
+ if self._settings._offline:
3688
+ # Downloading artifacts is not supported when offline.
3689
+ raise RuntimeError("`use_model` not supported in offline mode.")
3690
+
3552
3691
  artifact = self.use_artifact(artifact_or_name=name)
3553
3692
  if "model" not in str(artifact.type.lower()):
3554
3693
  raise AssertionError(
@@ -3566,15 +3705,15 @@ class Run:
3566
3705
  return path
3567
3706
 
3568
3707
  @_log_to_run
3569
- @_run_decorator._noop_on_finish()
3570
- @_run_decorator._attach
3708
+ @_raise_if_finished
3709
+ @_attach
3571
3710
  def link_model(
3572
3711
  self,
3573
3712
  path: StrPath,
3574
3713
  registered_model_name: str,
3575
3714
  name: str | None = None,
3576
3715
  aliases: list[str] | None = None,
3577
- ) -> None:
3716
+ ) -> Artifact | None:
3578
3717
  """Log a model artifact version and link it to a registered model in the model registry.
3579
3718
 
3580
3719
  The linked model version will be visible in the UI for the specified registered model.
@@ -3636,7 +3775,7 @@ class Run:
3636
3775
  ValueError: if name has invalid special characters
3637
3776
 
3638
3777
  Returns:
3639
- None
3778
+ The linked artifact if linking was successful, otherwise None.
3640
3779
  """
3641
3780
  name_parts = registered_model_name.split("/")
3642
3781
  if len(name_parts) != 1:
@@ -3665,11 +3804,13 @@ class Run:
3665
3804
  artifact = self._log_artifact(
3666
3805
  artifact_or_path=path, name=name, type="model"
3667
3806
  )
3668
- self.link_artifact(artifact=artifact, target_path=target_path, aliases=aliases)
3807
+ return self.link_artifact(
3808
+ artifact=artifact, target_path=target_path, aliases=aliases
3809
+ )
3669
3810
 
3670
3811
  @_log_to_run
3671
- @_run_decorator._noop_on_finish()
3672
- @_run_decorator._attach
3812
+ @_raise_if_finished
3813
+ @_attach
3673
3814
  def alert(
3674
3815
  self,
3675
3816
  title: str,
@@ -3720,8 +3861,8 @@ class Run:
3720
3861
  return not exception_raised
3721
3862
 
3722
3863
  @_log_to_run
3723
- @_run_decorator._noop_on_finish()
3724
- @_run_decorator._attach
3864
+ @_raise_if_finished
3865
+ @_attach
3725
3866
  def mark_preempting(self) -> None:
3726
3867
  """Mark this run as preempting.
3727
3868
 
@@ -3732,8 +3873,8 @@ class Run:
3732
3873
 
3733
3874
  @property
3734
3875
  @_log_to_run
3735
- @_run_decorator._noop_on_finish()
3736
- @_run_decorator._attach
3876
+ @_raise_if_finished
3877
+ @_attach
3737
3878
  def _system_metrics(self) -> dict[str, list[tuple[datetime, float]]]:
3738
3879
  """Returns a dictionary of system metrics.
3739
3880
 
@@ -3781,8 +3922,8 @@ class Run:
3781
3922
 
3782
3923
  @property
3783
3924
  @_log_to_run
3784
- @_run_decorator._attach
3785
- @_run_decorator._noop_on_finish()
3925
+ @_attach
3926
+ @_raise_if_finished
3786
3927
  def _metadata(self) -> Metadata | None:
3787
3928
  """The metadata associated with this run.
3788
3929
 
@@ -3820,8 +3961,8 @@ class Run:
3820
3961
  return None
3821
3962
 
3822
3963
  @_log_to_run
3823
- @_run_decorator._noop_on_finish()
3824
- @_run_decorator._attach
3964
+ @_raise_if_finished
3965
+ @_attach
3825
3966
  def _metadata_callback(
3826
3967
  self,
3827
3968
  metadata: MetadataRequest,
@@ -3841,59 +3982,41 @@ class Run:
3841
3982
  # ------------------------------------------------------------------------------
3842
3983
  # HEADER
3843
3984
  # ------------------------------------------------------------------------------
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)
3985
+ def _header(self) -> None:
3986
+ self._header_wandb_version_info()
3987
+ self._header_sync_info()
3988
+ self._header_run_info()
3855
3989
 
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:
3990
+ def _header_wandb_version_info(self) -> None:
3991
+ if self._settings.quiet or self._settings.silent:
3863
3992
  return
3864
3993
 
3865
3994
  # TODO: add this to a higher verbosity level
3866
- printer.display(f"Tracking run with wandb version {wandb.__version__}")
3995
+ self._printer.display(f"Tracking run with wandb version {wandb.__version__}")
3867
3996
 
3868
- @staticmethod
3869
- def _header_sync_info(
3870
- *,
3871
- settings: Settings,
3872
- printer: printer.Printer,
3873
- ) -> None:
3874
- if settings._offline:
3875
- printer.display(
3997
+ def _header_sync_info(self) -> None:
3998
+ if self._settings._offline:
3999
+ self._printer.display(
3876
4000
  [
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.",
4001
+ f"W&B syncing is set to {self._printer.code('`offline`')}"
4002
+ f" in this directory. Run {self._printer.code('`wandb online`')}"
4003
+ f" or set {self._printer.code('WANDB_MODE=online')}"
4004
+ " to enable cloud syncing.",
3880
4005
  ]
3881
4006
  )
3882
4007
  else:
3883
- info = [f"Run data is saved locally in {printer.files(settings.sync_dir)}"]
3884
- if not printer.supports_html:
4008
+ sync_dir = self._settings.sync_dir
4009
+ info = [f"Run data is saved locally in {self._printer.files(sync_dir)}"]
4010
+ if not self._printer.supports_html:
3885
4011
  info.append(
3886
- f"Run {printer.code('`wandb offline`')} to turn off syncing."
4012
+ f"Run {self._printer.code('`wandb offline`')} to turn off syncing."
3887
4013
  )
3888
- if not settings.quiet and not settings.silent:
3889
- printer.display(info)
4014
+ if not self._settings.quiet and not self._settings.silent:
4015
+ self._printer.display(info)
4016
+
4017
+ def _header_run_info(self) -> None:
4018
+ settings, printer = self._settings, self._printer
3890
4019
 
3891
- @staticmethod
3892
- def _header_run_info(
3893
- *,
3894
- settings: Settings,
3895
- printer: printer.Printer,
3896
- ) -> None:
3897
4020
  if settings._offline or settings.silent:
3898
4021
  return
3899
4022
 
@@ -3911,12 +4034,13 @@ class Run:
3911
4034
  return
3912
4035
 
3913
4036
  if printer.supports_html:
3914
- if not wandb.jupyter.maybe_display(): # type: ignore
4037
+ import wandb.jupyter
4038
+
4039
+ if not wandb.jupyter.display_if_magic_is_used(self):
3915
4040
  run_line = f"<strong>{printer.link(run_url, run_name)}</strong>"
3916
4041
  project_line, sweep_line = "", ""
3917
4042
 
3918
- # TODO(settings): make settings the source of truth
3919
- if not wandb.jupyter.quiet(): # type: ignore
4043
+ if not settings.quiet:
3920
4044
  doc_html = printer.link(url_registry.url("developer-guide"), "docs")
3921
4045
 
3922
4046
  project_html = printer.link(project_url, "Weights & Biases")