wandb 0.16.4__py3-none-any.whl → 0.16.5__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 (47) hide show
  1. wandb/__init__.py +2 -2
  2. wandb/agents/pyagent.py +1 -1
  3. wandb/apis/public/api.py +6 -6
  4. wandb/apis/reports/v2/interface.py +4 -8
  5. wandb/apis/reports/v2/internal.py +12 -45
  6. wandb/cli/cli.py +24 -3
  7. wandb/integration/ultralytics/callback.py +0 -1
  8. wandb/proto/v3/wandb_internal_pb2.py +332 -312
  9. wandb/proto/v3/wandb_settings_pb2.py +13 -3
  10. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  11. wandb/proto/v4/wandb_internal_pb2.py +316 -312
  12. wandb/proto/v4/wandb_settings_pb2.py +5 -3
  13. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  14. wandb/sdk/artifacts/artifact.py +67 -17
  15. wandb/sdk/artifacts/artifact_manifest_entry.py +6 -1
  16. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +1 -0
  17. wandb/sdk/artifacts/artifact_saver.py +1 -18
  18. wandb/sdk/artifacts/storage_handler.py +2 -1
  19. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +13 -5
  20. wandb/sdk/interface/interface.py +42 -9
  21. wandb/sdk/interface/interface_shared.py +13 -7
  22. wandb/sdk/internal/file_stream.py +19 -0
  23. wandb/sdk/internal/handler.py +1 -4
  24. wandb/sdk/internal/internal_api.py +2 -0
  25. wandb/sdk/internal/job_builder.py +45 -17
  26. wandb/sdk/internal/sender.py +53 -28
  27. wandb/sdk/internal/settings_static.py +9 -0
  28. wandb/sdk/internal/system/system_info.py +4 -1
  29. wandb/sdk/launch/create_job.py +1 -0
  30. wandb/sdk/launch/runner/kubernetes_runner.py +20 -2
  31. wandb/sdk/launch/utils.py +5 -5
  32. wandb/sdk/lib/__init__.py +2 -5
  33. wandb/sdk/lib/_settings_toposort_generated.py +1 -0
  34. wandb/sdk/lib/filesystem.py +11 -1
  35. wandb/sdk/lib/run_moment.py +72 -0
  36. wandb/sdk/service/streams.py +1 -6
  37. wandb/sdk/wandb_init.py +12 -1
  38. wandb/sdk/wandb_login.py +43 -26
  39. wandb/sdk/wandb_run.py +158 -89
  40. wandb/sdk/wandb_settings.py +53 -16
  41. wandb/testing/relay.py +5 -6
  42. {wandb-0.16.4.dist-info → wandb-0.16.5.dist-info}/METADATA +1 -1
  43. {wandb-0.16.4.dist-info → wandb-0.16.5.dist-info}/RECORD +47 -46
  44. {wandb-0.16.4.dist-info → wandb-0.16.5.dist-info}/WHEEL +1 -1
  45. {wandb-0.16.4.dist-info → wandb-0.16.5.dist-info}/LICENSE +0 -0
  46. {wandb-0.16.4.dist-info → wandb-0.16.5.dist-info}/entry_points.txt +0 -0
  47. {wandb-0.16.4.dist-info → wandb-0.16.5.dist-info}/top_level.txt +0 -0
wandb/sdk/wandb_init.py CHANGED
@@ -7,6 +7,7 @@ your evaluation script, and each step would be tracked as a run in W&B.
7
7
  For more on using `wandb.init()`, including code snippets, check out our
8
8
  [guide and FAQs](https://docs.wandb.ai/guides/track/launch).
9
9
  """
10
+
10
11
  import copy
11
12
  import json
12
13
  import logging
@@ -828,7 +829,7 @@ class _WandbInit:
828
829
  and self.settings.launch_config_path
829
830
  and os.path.exists(self.settings.launch_config_path)
830
831
  ):
831
- run._save(self.settings.launch_config_path)
832
+ run.save(self.settings.launch_config_path)
832
833
  # put artifacts in run config here
833
834
  # since doing so earlier will cause an error
834
835
  # as the run is not upserted
@@ -961,6 +962,7 @@ def init(
961
962
  monitor_gym: Optional[bool] = None,
962
963
  save_code: Optional[bool] = None,
963
964
  id: Optional[str] = None,
965
+ fork_from: Optional[str] = None,
964
966
  settings: Union[Settings, Dict[str, Any], None] = None,
965
967
  ) -> Union[Run, RunDisabled, None]:
966
968
  r"""Start a new run to track and log to W&B.
@@ -1122,6 +1124,10 @@ def init(
1122
1124
  for saving hyperparameters to compare across runs. The ID cannot
1123
1125
  contain the following special characters: `/\#?%:`.
1124
1126
  See [our guide to resuming runs](https://docs.wandb.com/guides/runs/resuming).
1127
+ fork_from: (str, optional) A string with the format <run_id>?_step=<step> describing
1128
+ a moment in a previous run to fork a new run from. Creates a new run that picks up
1129
+ logging history from the specified run at the specified moment. The target run must
1130
+ be in the current project.
1125
1131
 
1126
1132
  Examples:
1127
1133
  ### Set where the run is logged
@@ -1167,6 +1173,11 @@ def init(
1167
1173
  error_seen = None
1168
1174
  except_exit = None
1169
1175
  run: Optional[Union[Run, RunDisabled]] = None
1176
+
1177
+ # convert fork_from into a version that can be passed to settings
1178
+ if fork_from is not None and resume is not None:
1179
+ raise ValueError("Cannot specify both `fork_from` and `resume`")
1180
+
1170
1181
  try:
1171
1182
  wi = _WandbInit()
1172
1183
  wi.setup(kwargs)
wandb/sdk/wandb_login.py CHANGED
@@ -22,7 +22,7 @@ from wandb.old.settings import Settings as OldSettings
22
22
  from ..apis import InternalApi
23
23
  from .internal.internal_api import Api
24
24
  from .lib import apikey
25
- from .wandb_settings import Settings, Source
25
+ from .wandb_settings import Settings
26
26
 
27
27
 
28
28
  def _handle_host_wandb_setting(host: Optional[str], cloud: bool = False) -> None:
@@ -80,11 +80,17 @@ def login(
80
80
  _handle_host_wandb_setting(host)
81
81
  if wandb.setup()._settings._noop:
82
82
  return True
83
- kwargs = dict(locals())
84
- _verify = kwargs.pop("verify", False)
85
- configured = _login(**kwargs)
86
83
 
87
- if _verify:
84
+ configured = _login(
85
+ anonymous=anonymous,
86
+ key=key,
87
+ relogin=relogin,
88
+ host=host,
89
+ force=force,
90
+ timeout=timeout,
91
+ )
92
+
93
+ if verify:
88
94
  from . import wandb_setup
89
95
 
90
96
  singleton = wandb_setup._WandbSetup._instance
@@ -115,22 +121,32 @@ class _WandbLogin:
115
121
  self._key = None
116
122
  self._relogin = None
117
123
 
118
- def setup(self, kwargs):
119
- self.kwargs = kwargs
124
+ def setup(
125
+ self,
126
+ *,
127
+ anonymous: Optional[Literal["must", "allow", "never"]] = None,
128
+ key: Optional[str] = None,
129
+ relogin: Optional[bool] = None,
130
+ host: Optional[str] = None,
131
+ force: Optional[bool] = None,
132
+ timeout: Optional[int] = None,
133
+ ):
134
+ self._relogin = relogin
120
135
 
121
136
  # built up login settings
122
137
  login_settings: Settings = wandb.Settings()
123
- settings_param = kwargs.pop("_settings", None)
124
- # note that this case does not come up anywhere except for the tests
125
- if settings_param is not None:
126
- if isinstance(settings_param, Settings):
127
- login_settings._apply_settings(settings_param)
128
- elif isinstance(settings_param, dict):
129
- login_settings.update(settings_param, source=Source.LOGIN)
130
- _logger = wandb.setup()._get_logger()
131
- # Do not save relogin into settings as we just want to relogin once
132
- self._relogin = kwargs.pop("relogin", None)
133
- login_settings._apply_login(kwargs, _logger=_logger)
138
+ logger = wandb.setup()._get_logger()
139
+
140
+ login_settings._apply_login(
141
+ {
142
+ "anonymous": anonymous,
143
+ "key": key,
144
+ "host": host,
145
+ "force": force,
146
+ "timeout": timeout,
147
+ },
148
+ _logger=logger,
149
+ )
134
150
 
135
151
  # make sure they are applied globally
136
152
  self._wl = wandb.setup(settings=login_settings)
@@ -259,6 +275,7 @@ class _WandbLogin:
259
275
 
260
276
 
261
277
  def _login(
278
+ *,
262
279
  anonymous: Optional[Literal["must", "allow", "never"]] = None,
263
280
  key: Optional[str] = None,
264
281
  relogin: Optional[bool] = None,
@@ -270,9 +287,6 @@ def _login(
270
287
  _disable_warning: Optional[bool] = None,
271
288
  _entity: Optional[str] = None,
272
289
  ):
273
- kwargs = dict(locals())
274
- _disable_warning = kwargs.pop("_disable_warning", None)
275
-
276
290
  if wandb.run is not None:
277
291
  if not _disable_warning:
278
292
  wandb.termwarn("Calling wandb.login() after wandb.init() has no effect.")
@@ -280,20 +294,24 @@ def _login(
280
294
 
281
295
  wlogin = _WandbLogin()
282
296
 
283
- _backend = kwargs.pop("_backend", None)
284
297
  if _backend:
285
298
  wlogin.set_backend(_backend)
286
299
 
287
- _silent = kwargs.pop("_silent", None)
288
300
  if _silent:
289
301
  wlogin.set_silent(_silent)
290
302
 
291
- _entity = kwargs.pop("_entity", None)
292
303
  if _entity:
293
304
  wlogin.set_entity(_entity)
294
305
 
295
306
  # configure login object
296
- wlogin.setup(kwargs)
307
+ wlogin.setup(
308
+ anonymous=anonymous,
309
+ key=key,
310
+ relogin=relogin,
311
+ host=host,
312
+ force=force,
313
+ timeout=timeout,
314
+ )
297
315
 
298
316
  if wlogin._settings._offline:
299
317
  return False
@@ -306,7 +324,6 @@ def _login(
306
324
  # perform a login
307
325
  logged_in = wlogin.login()
308
326
 
309
- key = kwargs.get("key")
310
327
  if key:
311
328
  wlogin.configure_api_key(key)
312
329
 
wandb/sdk/wandb_run.py CHANGED
@@ -6,6 +6,7 @@ import json
6
6
  import logging
7
7
  import numbers
8
8
  import os
9
+ import pathlib
9
10
  import re
10
11
  import sys
11
12
  import threading
@@ -42,7 +43,6 @@ from wandb.apis import internal, public
42
43
  from wandb.apis.internal import Api
43
44
  from wandb.apis.public import Api as PublicApi
44
45
  from wandb.proto.wandb_internal_pb2 import (
45
- JobInfoResponse,
46
46
  MetricRecord,
47
47
  PollExitResponse,
48
48
  Result,
@@ -531,7 +531,6 @@ class Run:
531
531
  _check_version: Optional["CheckVersionResponse"]
532
532
  _sampled_history: Optional["SampledHistoryResponse"]
533
533
  _final_summary: Optional["GetSummaryResponse"]
534
- _job_info: Optional["JobInfoResponse"]
535
534
  _poll_exit_handle: Optional[MailboxHandle]
536
535
  _poll_exit_response: Optional[PollExitResponse]
537
536
  _server_info_response: Optional[ServerInfoResponse]
@@ -642,7 +641,6 @@ class Run:
642
641
  self._server_info_response = None
643
642
  self._internal_messages_response = None
644
643
  self._poll_exit_handle = None
645
- self._job_info = None
646
644
 
647
645
  # Initialize telemetry object
648
646
  self._telemetry_obj = telemetry.TelemetryRecord()
@@ -673,6 +671,12 @@ class Run:
673
671
  os.path.join("code", self._settings.program_relpath)
674
672
  )
675
673
 
674
+ if self._settings.fork_from is not None:
675
+ config[wandb_key]["branch_point"] = {
676
+ "run_id": self._settings.fork_from.run,
677
+ "step": self._settings.fork_from.value,
678
+ }
679
+
676
680
  self._config._update(config, ignore_locked=True)
677
681
 
678
682
  if sweep_config:
@@ -734,7 +738,7 @@ class Run:
734
738
  and self._settings.launch_config_path
735
739
  and os.path.exists(self._settings.launch_config_path)
736
740
  ):
737
- self._save(self._settings.launch_config_path)
741
+ self.save(self._settings.launch_config_path)
738
742
  with open(self._settings.launch_config_path) as fp:
739
743
  launch_config = json.loads(fp.read())
740
744
  if launch_config.get("overrides", {}).get("artifacts") is not None:
@@ -1385,6 +1389,8 @@ class Run:
1385
1389
 
1386
1390
  @_run_decorator._noop_on_finish()
1387
1391
  def _summary_update_callback(self, summary_record: SummaryRecord) -> None:
1392
+ with telemetry.context(run=self) as tel:
1393
+ tel.feature.set_summary = True
1388
1394
  if self._backend and self._backend.interface:
1389
1395
  self._backend.interface.publish_summary(summary_record)
1390
1396
 
@@ -1811,6 +1817,10 @@ class Run:
1811
1817
  ValueError: if invalid data is passed
1812
1818
 
1813
1819
  """
1820
+ if step is not None:
1821
+ with telemetry.context(run=self) as tel:
1822
+ tel.feature.set_step_log = True
1823
+
1814
1824
  if sync is not None:
1815
1825
  deprecate.deprecate(
1816
1826
  field_name=deprecate.Deprecated.run__log_sync,
@@ -1831,20 +1841,53 @@ class Run:
1831
1841
  @_run_decorator._attach
1832
1842
  def save(
1833
1843
  self,
1834
- glob_str: Optional[str] = None,
1835
- base_path: Optional[str] = None,
1844
+ glob_str: Optional[Union[str, os.PathLike]] = None,
1845
+ base_path: Optional[Union[str, os.PathLike]] = None,
1836
1846
  policy: "PolicyName" = "live",
1837
1847
  ) -> Union[bool, List[str]]:
1838
- """Ensure all files matching `glob_str` are synced to wandb with the policy specified.
1848
+ """Sync one or more files to W&B.
1849
+
1850
+ Relative paths are relative to the current working directory.
1851
+
1852
+ A Unix glob, such as "myfiles/*", is expanded at the time `save` is
1853
+ called regardless of the `policy`. In particular, new files are not
1854
+ picked up automatically.
1855
+
1856
+ A `base_path` may be provided to control the directory structure of
1857
+ uploaded files. It should be a prefix of `glob_str`, and the direcotry
1858
+ structure beneath it is preserved. It's best understood through
1859
+ examples:
1860
+
1861
+ ```
1862
+ wandb.save("these/are/myfiles/*")
1863
+ # => Saves files in a "these/are/myfiles/" folder in the run.
1864
+
1865
+ wandb.save("these/are/myfiles/*", base_path="these")
1866
+ # => Saves files in an "are/myfiles/" folder in the run.
1867
+
1868
+ wandb.save("/User/username/Documents/run123/*.txt")
1869
+ # => Saves files in a "run123/" folder in the run.
1870
+
1871
+ wandb.save("/User/username/Documents/run123/*.txt", base_path="/User")
1872
+ # => Saves files in a "username/Documents/run123/" folder in the run.
1873
+
1874
+ wandb.save("files/*/saveme.txt")
1875
+ # => Saves each "saveme.txt" file in an appropriate subdirectory
1876
+ # of "files/".
1877
+ ```
1839
1878
 
1840
1879
  Arguments:
1841
- glob_str: (string) a relative or absolute path to a unix glob or regular
1842
- path. If this isn't specified the method is a noop.
1843
- base_path: (string) the base path to run the glob relative to
1844
- policy: (string) one of `live`, `now`, or `end`
1845
- - live: upload the file as it changes, overwriting the previous version
1846
- - now: upload the file once now
1847
- - end: only upload file when the run ends
1880
+ glob_str: A relative or absolute path or Unix glob.
1881
+ base_path: A path to use to infer a directory structure; see examples.
1882
+ policy: One of `live`, `now`, or `end`.
1883
+ * live: upload the file as it changes, overwriting the previous version
1884
+ * now: upload the file once now
1885
+ * end: upload file when the run ends
1886
+
1887
+ Returns:
1888
+ Paths to the symlinks created for the matched files.
1889
+
1890
+ For historical reasons, this may return a boolean in legacy code.
1848
1891
  """
1849
1892
  if glob_str is None:
1850
1893
  # noop for historical reasons, run.save() may be called in legacy code
@@ -1857,77 +1900,116 @@ class Run:
1857
1900
  )
1858
1901
  return True
1859
1902
 
1860
- return self._save(glob_str, base_path, policy)
1903
+ if isinstance(glob_str, bytes):
1904
+ # Preserved for backward compatibility: allow bytes inputs.
1905
+ glob_str = glob_str.decode("utf-8")
1906
+ if isinstance(glob_str, str) and (
1907
+ glob_str.startswith("gs://") or glob_str.startswith("s3://")
1908
+ ):
1909
+ # Provide a better error message for a common misuse.
1910
+ wandb.termlog(f"{glob_str} is a cloud storage url, can't save file to W&B.")
1911
+ return []
1912
+ glob_path = pathlib.Path(glob_str)
1913
+
1914
+ if base_path is not None:
1915
+ base_path = pathlib.Path(base_path)
1916
+ elif not glob_path.is_absolute():
1917
+ base_path = pathlib.Path(".")
1918
+ else:
1919
+ # Absolute glob paths with no base path get special handling.
1920
+ wandb.termwarn(
1921
+ "Saving files without folders. If you want to preserve "
1922
+ "subdirectories pass base_path to wandb.save, i.e. "
1923
+ 'wandb.save("/mnt/folder/file.h5", base_path="/mnt")',
1924
+ repeat=False,
1925
+ )
1926
+ base_path = glob_path.resolve().parent.parent
1861
1927
 
1862
- def _save(
1863
- self,
1864
- glob_str: Optional[str] = None,
1865
- base_path: Optional[str] = None,
1866
- policy: "PolicyName" = "live",
1867
- ) -> Union[bool, List[str]]:
1868
1928
  if policy not in ("live", "end", "now"):
1869
1929
  raise ValueError(
1870
- 'Only "live" "end" and "now" policies are currently supported.'
1930
+ 'Only "live", "end" and "now" policies are currently supported.'
1871
1931
  )
1872
- if isinstance(glob_str, bytes):
1873
- glob_str = glob_str.decode("utf-8")
1874
- if not isinstance(glob_str, str):
1875
- raise ValueError("Must call wandb.save(glob_str) with glob_str a str")
1876
1932
 
1877
- if base_path is None:
1878
- if os.path.isabs(glob_str):
1879
- base_path = os.path.dirname(glob_str)
1880
- wandb.termwarn(
1881
- "Saving files without folders. If you want to preserve "
1882
- "sub directories pass base_path to wandb.save, i.e. "
1883
- 'wandb.save("/mnt/folder/file.h5", base_path="/mnt")',
1884
- repeat=False,
1885
- )
1886
- else:
1887
- base_path = "."
1888
- wandb_glob_str = GlobStr(os.path.relpath(glob_str, base_path))
1889
- if ".." + os.sep in wandb_glob_str:
1890
- raise ValueError("globs can't walk above base_path")
1933
+ resolved_glob_path = glob_path.resolve()
1934
+ resolved_base_path = base_path.resolve()
1935
+
1936
+ return self._save(
1937
+ resolved_glob_path,
1938
+ resolved_base_path,
1939
+ policy,
1940
+ )
1941
+
1942
+ def _save(
1943
+ self,
1944
+ glob_path: pathlib.Path,
1945
+ base_path: pathlib.Path,
1946
+ policy: "PolicyName",
1947
+ ) -> List[str]:
1948
+ # Can't use is_relative_to() because that's added in Python 3.9,
1949
+ # but we support down to Python 3.7.
1950
+ if not str(glob_path).startswith(str(base_path)):
1951
+ raise ValueError("Glob may not walk above the base path")
1952
+
1953
+ if glob_path == base_path:
1954
+ raise ValueError("Glob cannot be the same as the base path")
1955
+
1956
+ relative_glob = glob_path.relative_to(base_path)
1957
+ if relative_glob.parts[0] == "*":
1958
+ raise ValueError("Glob may not start with '*' relative to the base path")
1959
+ relative_glob_str = GlobStr(str(relative_glob))
1891
1960
 
1892
1961
  with telemetry.context(run=self) as tel:
1893
1962
  tel.feature.save = True
1894
1963
 
1895
- if glob_str.startswith("gs://") or glob_str.startswith("s3://"):
1896
- wandb.termlog(
1897
- "%s is a cloud storage url, can't save file to wandb." % glob_str
1898
- )
1899
- return []
1900
- files = glob.glob(os.path.join(self._settings.files_dir, wandb_glob_str))
1901
- warn = False
1902
- if len(files) == 0 and "*" in wandb_glob_str:
1903
- warn = True
1904
- for path in glob.glob(glob_str):
1905
- file_name = os.path.relpath(path, base_path)
1906
- abs_path = os.path.abspath(path)
1907
- wandb_path = os.path.join(self._settings.files_dir, file_name)
1908
- filesystem.mkdir_exists_ok(os.path.dirname(wandb_path))
1909
- # We overwrite symlinks because namespaces can change in Tensorboard
1910
- if os.path.islink(wandb_path) and abs_path != os.readlink(wandb_path):
1911
- os.remove(wandb_path)
1912
- os.symlink(abs_path, wandb_path)
1913
- elif not os.path.exists(wandb_path):
1914
- os.symlink(abs_path, wandb_path)
1915
- files.append(wandb_path)
1916
- if warn:
1917
- file_str = "%i file" % len(files)
1918
- if len(files) > 1:
1964
+ # Paths to the symlinks created for the globbed files.
1965
+ wandb_files = [
1966
+ str(path)
1967
+ for path in pathlib.Path(
1968
+ self._settings.files_dir,
1969
+ ).glob(relative_glob_str)
1970
+ ]
1971
+
1972
+ had_symlinked_files = len(wandb_files) > 0
1973
+ is_star_glob = "*" in relative_glob_str
1974
+
1975
+ # The base_path may itself be a glob, so we can't do
1976
+ # base_path.glob(relative_glob_str)
1977
+ for path_str in glob.glob(str(base_path / relative_glob_str)):
1978
+ path = pathlib.Path(path_str).absolute()
1979
+
1980
+ # We can't use relative_to() because base_path may be a glob.
1981
+ saved_path = pathlib.Path(*path.parts[len(base_path.parts) :])
1982
+
1983
+ wandb_path = pathlib.Path(self._settings.files_dir, saved_path)
1984
+
1985
+ wandb_files.append(str(wandb_path))
1986
+ wandb_path.parent.mkdir(parents=True, exist_ok=True)
1987
+
1988
+ # Delete the symlink if it exists.
1989
+ try:
1990
+ wandb_path.unlink()
1991
+ except FileNotFoundError:
1992
+ # In Python 3.8, we would pass missing_ok=True, but as of now
1993
+ # we support down to Python 3.7.
1994
+ pass
1995
+
1996
+ wandb_path.symlink_to(path)
1997
+
1998
+ # Inform users that new files aren't detected automatically.
1999
+ if not had_symlinked_files and is_star_glob:
2000
+ file_str = f"{len(wandb_files)} file"
2001
+ if len(wandb_files) > 1:
1919
2002
  file_str += "s"
1920
2003
  wandb.termwarn(
1921
- (
1922
- "Symlinked %s into the W&B run directory, "
1923
- "call wandb.save again to sync new files."
1924
- )
1925
- % file_str
2004
+ f"Symlinked {file_str} into the W&B run directory, "
2005
+ "call wandb.save again to sync new files."
1926
2006
  )
1927
- files_dict: FilesDict = dict(files=[(wandb_glob_str, policy)])
2007
+
2008
+ files_dict: FilesDict = {"files": [(relative_glob_str, policy)]}
1928
2009
  if self._backend and self._backend.interface:
1929
2010
  self._backend.interface.publish_files(files_dict)
1930
- return files
2011
+
2012
+ return wandb_files
1931
2013
 
1932
2014
  @_run_decorator._attach
1933
2015
  def restore(
@@ -2485,7 +2567,6 @@ class Run:
2485
2567
  sampled_history_handle = (
2486
2568
  self._backend.interface.deliver_request_sampled_history()
2487
2569
  )
2488
- job_info_handle = self._backend.interface.deliver_request_job_info()
2489
2570
 
2490
2571
  result = server_info_handle.wait(timeout=-1)
2491
2572
  assert result
@@ -2499,10 +2580,6 @@ class Run:
2499
2580
  assert result
2500
2581
  self._final_summary = result.response.get_summary_response
2501
2582
 
2502
- result = job_info_handle.wait(timeout=-1)
2503
- assert result
2504
- self._job_info = result.response.job_info_response
2505
-
2506
2583
  if self._backend:
2507
2584
  self._backend.cleanup()
2508
2585
 
@@ -2525,7 +2602,6 @@ class Run:
2525
2602
  server_info_response=self._server_info_response,
2526
2603
  check_version_response=self._check_version,
2527
2604
  internal_messages_response=self._internal_messages_response,
2528
- job_info=self._job_info,
2529
2605
  reporter=self._reporter,
2530
2606
  quiet=self._quiet,
2531
2607
  settings=self._settings,
@@ -3509,7 +3585,7 @@ class Run:
3509
3585
  if settings._offline or settings.silent:
3510
3586
  return
3511
3587
 
3512
- run_url = settings.run_url
3588
+ workspace_url = f"{settings.run_url}/workspace"
3513
3589
  project_url = settings.project_url
3514
3590
  sweep_url = settings.sweep_url
3515
3591
 
@@ -3520,7 +3596,7 @@ class Run:
3520
3596
 
3521
3597
  if printer._html:
3522
3598
  if not wandb.jupyter.maybe_display():
3523
- run_line = f"<strong>{printer.link(run_url, run_name)}</strong>"
3599
+ run_line = f"<strong>{printer.link(workspace_url, run_name)}</strong>"
3524
3600
  project_line, sweep_line = "", ""
3525
3601
 
3526
3602
  # TODO(settings): make settings the source of truth
@@ -3552,7 +3628,7 @@ class Run:
3552
3628
  f'{printer.emoji("broom")} View sweep at {printer.link(sweep_url)}'
3553
3629
  )
3554
3630
  printer.display(
3555
- f'{printer.emoji("rocket")} View run at {printer.link(run_url)}',
3631
+ f'{printer.emoji("rocket")} View run at {printer.link(workspace_url)}',
3556
3632
  )
3557
3633
 
3558
3634
  # TODO(settings) use `wandb_settings` (if self.settings.anonymous == "true":)
@@ -3576,7 +3652,6 @@ class Run:
3576
3652
  server_info_response: Optional[ServerInfoResponse] = None,
3577
3653
  check_version_response: Optional["CheckVersionResponse"] = None,
3578
3654
  internal_messages_response: Optional["InternalMessagesResponse"] = None,
3579
- job_info: Optional["JobInfoResponse"] = None,
3580
3655
  reporter: Optional[Reporter] = None,
3581
3656
  quiet: Optional[bool] = None,
3582
3657
  *,
@@ -3593,7 +3668,6 @@ class Run:
3593
3668
 
3594
3669
  Run._footer_sync_info(
3595
3670
  poll_exit_response=poll_exit_response,
3596
- job_info=job_info,
3597
3671
  quiet=quiet,
3598
3672
  settings=settings,
3599
3673
  printer=printer,
@@ -3778,7 +3852,6 @@ class Run:
3778
3852
  @staticmethod
3779
3853
  def _footer_sync_info(
3780
3854
  poll_exit_response: Optional[PollExitResponse] = None,
3781
- job_info: Optional["JobInfoResponse"] = None,
3782
3855
  quiet: Optional[bool] = None,
3783
3856
  *,
3784
3857
  settings: "Settings",
@@ -3798,14 +3871,10 @@ class Run:
3798
3871
  else:
3799
3872
  info = []
3800
3873
  if settings.run_name and settings.run_url:
3874
+ run_workspace = f"{settings.run_url}/workspace"
3801
3875
  info = [
3802
- f"{printer.emoji('rocket')} View run {printer.name(settings.run_name)} at: {printer.link(settings.run_url)}"
3876
+ f"{printer.emoji('rocket')} View run {printer.name(settings.run_name)} at: {printer.link(run_workspace)}"
3803
3877
  ]
3804
- if job_info and job_info.version and job_info.sequenceId:
3805
- link = f"{settings.project_url}/jobs/{job_info.sequenceId}/version_details/{job_info.version}"
3806
- info.append(
3807
- f"{printer.emoji('lightning')} View job at {printer.link(link)}",
3808
- )
3809
3878
  if poll_exit_response and poll_exit_response.file_counts:
3810
3879
  logger.info("logging synced files")
3811
3880
  file_counts = poll_exit_response.file_counts