wandb 0.21.4__py3-none-win32.whl → 0.22.1__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 (96) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +3 -3
  3. wandb/_pydantic/__init__.py +12 -11
  4. wandb/_pydantic/base.py +49 -19
  5. wandb/apis/__init__.py +2 -0
  6. wandb/apis/attrs.py +2 -0
  7. wandb/apis/importers/internals/internal.py +16 -23
  8. wandb/apis/internal.py +2 -0
  9. wandb/apis/normalize.py +2 -0
  10. wandb/apis/public/__init__.py +44 -1
  11. wandb/apis/public/api.py +215 -164
  12. wandb/apis/public/artifacts.py +23 -20
  13. wandb/apis/public/const.py +2 -0
  14. wandb/apis/public/files.py +33 -24
  15. wandb/apis/public/history.py +2 -0
  16. wandb/apis/public/jobs.py +20 -18
  17. wandb/apis/public/projects.py +4 -2
  18. wandb/apis/public/query_generator.py +3 -0
  19. wandb/apis/public/registries/__init__.py +7 -0
  20. wandb/apis/public/registries/_freezable_list.py +9 -12
  21. wandb/apis/public/registries/registries_search.py +8 -6
  22. wandb/apis/public/registries/registry.py +22 -17
  23. wandb/apis/public/reports.py +2 -0
  24. wandb/apis/public/runs.py +282 -60
  25. wandb/apis/public/sweeps.py +10 -9
  26. wandb/apis/public/teams.py +2 -0
  27. wandb/apis/public/users.py +2 -0
  28. wandb/apis/public/utils.py +16 -15
  29. wandb/automations/_generated/__init__.py +54 -127
  30. wandb/automations/_generated/create_generic_webhook_integration.py +1 -7
  31. wandb/automations/_generated/fragments.py +26 -91
  32. wandb/bin/gpu_stats.exe +0 -0
  33. wandb/bin/wandb-core +0 -0
  34. wandb/cli/beta_sync.py +9 -11
  35. wandb/errors/errors.py +3 -3
  36. wandb/proto/v3/wandb_internal_pb2.py +234 -224
  37. wandb/proto/v3/wandb_sync_pb2.py +19 -6
  38. wandb/proto/v4/wandb_internal_pb2.py +226 -224
  39. wandb/proto/v4/wandb_sync_pb2.py +10 -6
  40. wandb/proto/v5/wandb_internal_pb2.py +226 -224
  41. wandb/proto/v5/wandb_sync_pb2.py +10 -6
  42. wandb/proto/v6/wandb_base_pb2.py +3 -3
  43. wandb/proto/v6/wandb_internal_pb2.py +229 -227
  44. wandb/proto/v6/wandb_server_pb2.py +3 -3
  45. wandb/proto/v6/wandb_settings_pb2.py +3 -3
  46. wandb/proto/v6/wandb_sync_pb2.py +13 -9
  47. wandb/proto/v6/wandb_telemetry_pb2.py +3 -3
  48. wandb/sdk/artifacts/_factories.py +7 -2
  49. wandb/sdk/artifacts/_generated/__init__.py +112 -412
  50. wandb/sdk/artifacts/_generated/fragments.py +65 -0
  51. wandb/sdk/artifacts/_generated/operations.py +52 -22
  52. wandb/sdk/artifacts/_generated/run_input_artifacts.py +3 -23
  53. wandb/sdk/artifacts/_generated/run_output_artifacts.py +3 -23
  54. wandb/sdk/artifacts/_generated/type_info.py +19 -0
  55. wandb/sdk/artifacts/_gqlutils.py +47 -0
  56. wandb/sdk/artifacts/_models/__init__.py +4 -0
  57. wandb/sdk/artifacts/_models/base_model.py +20 -0
  58. wandb/sdk/artifacts/_validators.py +40 -12
  59. wandb/sdk/artifacts/artifact.py +69 -88
  60. wandb/sdk/artifacts/artifact_file_cache.py +6 -1
  61. wandb/sdk/artifacts/artifact_manifest_entry.py +61 -2
  62. wandb/sdk/artifacts/storage_handlers/gcs_handler.py +1 -1
  63. wandb/sdk/artifacts/storage_handlers/http_handler.py +1 -3
  64. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +1 -1
  65. wandb/sdk/artifacts/storage_handlers/s3_handler.py +1 -1
  66. wandb/sdk/artifacts/storage_policies/_factories.py +63 -0
  67. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +69 -124
  68. wandb/sdk/data_types/bokeh.py +5 -1
  69. wandb/sdk/data_types/image.py +17 -6
  70. wandb/sdk/interface/interface.py +41 -4
  71. wandb/sdk/interface/interface_queue.py +10 -0
  72. wandb/sdk/interface/interface_shared.py +9 -7
  73. wandb/sdk/interface/interface_sock.py +9 -3
  74. wandb/sdk/internal/_generated/__init__.py +2 -12
  75. wandb/sdk/internal/sender.py +1 -1
  76. wandb/sdk/internal/settings_static.py +2 -82
  77. wandb/sdk/launch/runner/kubernetes_runner.py +25 -20
  78. wandb/sdk/launch/utils.py +82 -1
  79. wandb/sdk/lib/progress.py +7 -4
  80. wandb/sdk/lib/service/service_client.py +5 -9
  81. wandb/sdk/lib/service/service_connection.py +39 -23
  82. wandb/sdk/mailbox/mailbox_handle.py +2 -0
  83. wandb/sdk/projects/_generated/__init__.py +12 -33
  84. wandb/sdk/wandb_init.py +31 -3
  85. wandb/sdk/wandb_login.py +53 -27
  86. wandb/sdk/wandb_run.py +5 -3
  87. wandb/sdk/wandb_settings.py +50 -13
  88. wandb/sync/sync.py +7 -2
  89. wandb/util.py +1 -1
  90. wandb/wandb_agent.py +35 -4
  91. {wandb-0.21.4.dist-info → wandb-0.22.1.dist-info}/METADATA +1 -1
  92. {wandb-0.21.4.dist-info → wandb-0.22.1.dist-info}/RECORD +95 -91
  93. wandb/sdk/artifacts/_graphql_fragments.py +0 -19
  94. {wandb-0.21.4.dist-info → wandb-0.22.1.dist-info}/WHEEL +0 -0
  95. {wandb-0.21.4.dist-info → wandb-0.22.1.dist-info}/entry_points.txt +0 -0
  96. {wandb-0.21.4.dist-info → wandb-0.22.1.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/wandb_login.py CHANGED
@@ -77,7 +77,7 @@ def login(
77
77
  UsageError: If `api_key` cannot be configured and no tty.
78
78
  """
79
79
  _handle_host_wandb_setting(host)
80
- return _login(
80
+ logged_in, _ = _login(
81
81
  anonymous=anonymous,
82
82
  key=key,
83
83
  relogin=relogin,
@@ -87,6 +87,7 @@ def login(
87
87
  verify=verify,
88
88
  referrer=referrer,
89
89
  )
90
+ return logged_in
90
91
 
91
92
 
92
93
  class ApiKeyStatus(enum.Enum):
@@ -245,24 +246,6 @@ class _WandbLogin:
245
246
 
246
247
  return key, status
247
248
 
248
- def _verify_login(self, key: str) -> None:
249
- api = InternalApi(api_key=key)
250
-
251
- try:
252
- is_api_key_valid = api.validate_api_key()
253
- except ConnectionError:
254
- raise AuthenticationError(
255
- "Unable to connect to server to verify API token."
256
- )
257
- except Exception:
258
- raise AuthenticationError("An error occurred while verifying the API key.")
259
-
260
- if not is_api_key_valid:
261
- raise AuthenticationError(
262
- f"API key verification failed for host {self._settings.base_url}."
263
- " Make sure your API key is valid."
264
- )
265
-
266
249
 
267
250
  def _login(
268
251
  *,
@@ -277,11 +260,30 @@ def _login(
277
260
  update_api_key: bool = True,
278
261
  _silent: Optional[bool] = None,
279
262
  _disable_warning: Optional[bool] = None,
280
- ) -> bool:
263
+ ) -> (bool, Optional[str]):
264
+ """Logs in to W&B.
265
+
266
+ This is the internal implementation of wandb.login(),
267
+ with many of the same arguments as wandb.login().
268
+ Additional arguments are documented below.
269
+
270
+ Args:
271
+ update_api_key: If true, the api key will be saved or updated
272
+ in the users .netrc file.
273
+ _silent: If true, will not print any messages to the console.
274
+ _disable_warning: If true, no warning will be displayed
275
+ when calling wandb.login() after wandb.init().
276
+
277
+ Returns:
278
+ bool: If the login was successful
279
+ or the user is assumed to be already be logged in.
280
+ str: The API key used to log in,
281
+ or None if the api key was not verified during the login process.
282
+ """
281
283
  if wandb.run is not None:
282
284
  if not _disable_warning:
283
285
  wandb.termwarn("Calling wandb.login() after wandb.init() has no effect.")
284
- return True
286
+ return True, None
285
287
 
286
288
  wlogin = _WandbLogin(
287
289
  anonymous=anonymous,
@@ -293,19 +295,19 @@ def _login(
293
295
  )
294
296
 
295
297
  if wlogin._settings._noop:
296
- return True
298
+ return True, None
297
299
 
298
300
  if wlogin._settings._offline and not wlogin._settings.x_cli_only_mode:
299
301
  wandb.termwarn("Unable to verify login in offline mode.")
300
- return False
302
+ return False, None
301
303
  elif wandb.util._is_kaggle() and not wandb.util._has_internet():
302
304
  wandb.termerror(
303
305
  "To use W&B in kaggle you must enable internet in the settings panel on the right."
304
306
  )
305
- return False
307
+ return False, None
306
308
 
307
309
  if wlogin._settings.identity_token_file:
308
- return True
310
+ return True, None
309
311
 
310
312
  key_is_pre_configured = False
311
313
  key_status = None
@@ -318,7 +320,7 @@ def _login(
318
320
  key, key_status = wlogin.prompt_api_key(referrer=referrer)
319
321
 
320
322
  if verify:
321
- wlogin._verify_login(key)
323
+ _verify_login(key, wlogin._settings.base_url)
322
324
 
323
325
  if not key_is_pre_configured:
324
326
  if update_api_key:
@@ -329,4 +331,28 @@ def _login(
329
331
  if key and not _silent:
330
332
  wlogin._print_logged_in_message()
331
333
 
332
- return key is not None
334
+ return key is not None, key
335
+
336
+
337
+ def _verify_login(key: str, base_url: str) -> None:
338
+ api = InternalApi(
339
+ api_key=key,
340
+ default_settings={"base_url": base_url},
341
+ )
342
+
343
+ try:
344
+ is_api_key_valid = api.validate_api_key()
345
+ except ConnectionError:
346
+ raise AuthenticationError(
347
+ "Unable to connect to server to verify API token."
348
+ ) from None
349
+ except Exception as e:
350
+ raise AuthenticationError(
351
+ "An error occurred while verifying the API key."
352
+ ) from e
353
+
354
+ if not is_api_key_valid:
355
+ raise AuthenticationError(
356
+ f"API key verification failed for host {base_url}."
357
+ " Make sure your API key is valid."
358
+ )
wandb/sdk/wandb_run.py CHANGED
@@ -33,6 +33,7 @@ from wandb.errors import CommError, UsageError
33
33
  from wandb.errors.links import url_registry
34
34
  from wandb.integration.torch import wandb_torch
35
35
  from wandb.plot import CustomChart, Visualize
36
+ from wandb.proto import wandb_internal_pb2 as pb
36
37
  from wandb.proto.wandb_deprecated import Deprecated
37
38
  from wandb.proto.wandb_internal_pb2 import (
38
39
  MetricRecord,
@@ -41,7 +42,6 @@ from wandb.proto.wandb_internal_pb2 import (
41
42
  RunRecord,
42
43
  )
43
44
  from wandb.sdk.artifacts._internal_artifact import InternalArtifact
44
- from wandb.sdk.artifacts._validators import is_artifact_registry_project
45
45
  from wandb.sdk.artifacts.artifact import Artifact
46
46
  from wandb.sdk.internal import job_builder
47
47
  from wandb.sdk.lib import asyncio_compat, wb_logging
@@ -2698,7 +2698,9 @@ class Run:
2698
2698
  assert self._backend and self._backend.interface
2699
2699
 
2700
2700
  while True:
2701
- handle = self._backend.interface.deliver_poll_exit()
2701
+ handle = await self._backend.interface.deliver_async(
2702
+ pb.Record(request=pb.Request(poll_exit=pb.PollExitRequest()))
2703
+ )
2702
2704
 
2703
2705
  time_start = time.monotonic()
2704
2706
  last_result = await handle.wait_async(timeout=None)
@@ -3004,7 +3006,7 @@ class Run:
3004
3006
  # the target entity to the run's entity. Instead, delegate to
3005
3007
  # Artifact.link() to resolve the required org entity.
3006
3008
  target = ArtifactPath.from_str(target_path)
3007
- if not (target.project and is_artifact_registry_project(target.project)):
3009
+ if not target.is_registry_path():
3008
3010
  target = target.with_defaults(prefix=self.entity, project=self.project)
3009
3011
 
3010
3012
  return artifact.link(target.to_str(), aliases)
@@ -160,6 +160,7 @@ CLIENT_ONLY_SETTINGS = (
160
160
  "reinit",
161
161
  "max_end_of_run_history_metrics",
162
162
  "max_end_of_run_summary_metrics",
163
+ "x_sync_dir_suffix",
163
164
  )
164
165
  """Python-only keys that are not fields on the settings proto."""
165
166
 
@@ -941,6 +942,13 @@ class Settings(BaseModel, validate_assignment=True):
941
942
  <!-- lazydoc-ignore-class-attributes -->
942
943
  """
943
944
 
945
+ x_sync_dir_suffix: str = ""
946
+ """Suffix to add to the run's directory name (sync_dir).
947
+
948
+ This is set in wandb.init() to avoid naming conflicts.
949
+ If set, it is joined to the default name with a dash.
950
+ """
951
+
944
952
  x_update_finish_state: bool = True
945
953
  """Flag to indicate whether this process can update the run's final state on the server.
946
954
 
@@ -954,6 +962,8 @@ class Settings(BaseModel, validate_assignment=True):
954
962
  """Check if a private field is provided and assign to the corresponding public one.
955
963
 
956
964
  This is a compatibility layer to handle previous versions of the settings.
965
+
966
+ <!-- lazydoc-ignore: internal -->
957
967
  """
958
968
  new_values = {}
959
969
  for key in values:
@@ -988,6 +998,10 @@ class Settings(BaseModel, validate_assignment=True):
988
998
 
989
999
  @model_validator(mode="after")
990
1000
  def validate_skip_transaction_log(self):
1001
+ """Validate x_skip_transaction_log.
1002
+
1003
+ <!-- lazydoc-ignore: internal -->
1004
+ """
991
1005
  if self._offline and self.x_skip_transaction_log:
992
1006
  raise ValueError("Cannot skip transaction log in offline mode")
993
1007
  return self
@@ -1445,6 +1459,8 @@ class Settings(BaseModel, validate_assignment=True):
1445
1459
  - Converts single string values to tuple format
1446
1460
  - Preserves None values
1447
1461
 
1462
+ <!-- lazydoc-ignore-classmethod: internal -->
1463
+
1448
1464
  Args:
1449
1465
  value: A string, list, tuple, or None representing tags
1450
1466
 
@@ -1453,8 +1469,6 @@ class Settings(BaseModel, validate_assignment=True):
1453
1469
 
1454
1470
  Raises:
1455
1471
  ValueError: If any tag is empty or exceeds 64 characters
1456
-
1457
- <!-- lazydoc-ignore-classmethod: internal -->
1458
1472
  """
1459
1473
  if value is None:
1460
1474
  return None
@@ -1733,10 +1747,12 @@ class Settings(BaseModel, validate_assignment=True):
1733
1747
  @property
1734
1748
  def sync_dir(self) -> str:
1735
1749
  """The directory for storing the run's files."""
1736
- return _path_convert(
1737
- self.wandb_dir,
1738
- f"{self.run_mode}-{self.timespec}-{self.run_id}",
1739
- )
1750
+ name = f"{self.run_mode}-{self.timespec}-{self.run_id}"
1751
+
1752
+ if self.x_sync_dir_suffix:
1753
+ name += f"-{self.x_sync_dir_suffix}"
1754
+
1755
+ return _path_convert(self.wandb_dir, name)
1740
1756
 
1741
1757
  @computed_field # type: ignore[prop-decorator]
1742
1758
  @property
@@ -1776,7 +1792,10 @@ class Settings(BaseModel, validate_assignment=True):
1776
1792
  # wandb/sdk/wandb_setup.py::_WandbSetup._settings_setup.
1777
1793
 
1778
1794
  def update_from_system_config_file(self):
1779
- """Update settings from the system config file."""
1795
+ """Update settings from the system config file.
1796
+
1797
+ <!-- lazydoc-ignore: internal -->
1798
+ """
1780
1799
  if not self.settings_system or not os.path.exists(self.settings_system):
1781
1800
  return
1782
1801
  for key, value in self._load_config_file(self.settings_system).items():
@@ -1784,7 +1803,10 @@ class Settings(BaseModel, validate_assignment=True):
1784
1803
  setattr(self, key, value)
1785
1804
 
1786
1805
  def update_from_workspace_config_file(self):
1787
- """Update settings from the workspace config file."""
1806
+ """Update settings from the workspace config file.
1807
+
1808
+ <!-- lazydoc-ignore: internal -->
1809
+ """
1788
1810
  if not self.settings_workspace or not os.path.exists(self.settings_workspace):
1789
1811
  return
1790
1812
  for key, value in self._load_config_file(self.settings_workspace).items():
@@ -1792,7 +1814,10 @@ class Settings(BaseModel, validate_assignment=True):
1792
1814
  setattr(self, key, value)
1793
1815
 
1794
1816
  def update_from_env_vars(self, environ: Dict[str, Any]):
1795
- """Update settings from environment variables."""
1817
+ """Update settings from environment variables.
1818
+
1819
+ <!-- lazydoc-ignore: internal -->
1820
+ """
1796
1821
  env_prefix: str = "WANDB_"
1797
1822
  private_env_prefix: str = env_prefix + "_"
1798
1823
  special_env_var_names = {
@@ -1829,7 +1854,10 @@ class Settings(BaseModel, validate_assignment=True):
1829
1854
  setattr(self, key, value)
1830
1855
 
1831
1856
  def update_from_system_environment(self):
1832
- """Update settings from the system environment."""
1857
+ """Update settings from the system environment.
1858
+
1859
+ <!-- lazydoc-ignore: internal -->
1860
+ """
1833
1861
  # For code saving, only allow env var override if value from server is true, or
1834
1862
  # if no preference was specified.
1835
1863
  if (self.save_code is True or self.save_code is None) and (
@@ -1909,13 +1937,19 @@ class Settings(BaseModel, validate_assignment=True):
1909
1937
  self.program = program
1910
1938
 
1911
1939
  def update_from_dict(self, settings: Dict[str, Any]) -> None:
1912
- """Update settings from a dictionary."""
1940
+ """Update settings from a dictionary.
1941
+
1942
+ <!-- lazydoc-ignore: internal -->
1943
+ """
1913
1944
  for key, value in dict(settings).items():
1914
1945
  if value is not None:
1915
1946
  setattr(self, key, value)
1916
1947
 
1917
1948
  def update_from_settings(self, settings: Settings) -> None:
1918
- """Update settings from another instance of `Settings`."""
1949
+ """Update settings from another instance of `Settings`.
1950
+
1951
+ <!-- lazydoc-ignore: internal -->
1952
+ """
1919
1953
  d = {field: getattr(settings, field) for field in settings.model_fields_set}
1920
1954
  if d:
1921
1955
  self.update_from_dict(d)
@@ -1923,7 +1957,10 @@ class Settings(BaseModel, validate_assignment=True):
1923
1957
  # Helper methods.
1924
1958
 
1925
1959
  def to_proto(self) -> wandb_settings_pb2.Settings:
1926
- """Generate a protobuf representation of the settings."""
1960
+ """Generate a protobuf representation of the settings.
1961
+
1962
+ <!-- lazydoc-ignore: internal -->
1963
+ """
1927
1964
  settings_proto = wandb_settings_pb2.Settings()
1928
1965
  for k, v in self.model_dump(exclude_none=True).items():
1929
1966
  if k in CLIENT_ONLY_SETTINGS:
wandb/sync/sync.py CHANGED
@@ -193,7 +193,7 @@ class SyncThread(threading.Thread):
193
193
  x_start_time=time.time(),
194
194
  )
195
195
 
196
- settings_static = SettingsStatic(settings.to_proto())
196
+ settings_static = SettingsStatic(dict(settings))
197
197
 
198
198
  handle_manager = handler.HandleManager(
199
199
  settings=settings_static,
@@ -207,7 +207,12 @@ class SyncThread(threading.Thread):
207
207
 
208
208
  filesystem.mkdir_exists_ok(settings.files_dir)
209
209
  send_manager.send_run(record, file_dir=settings.files_dir)
210
- watcher = tb_watcher.TBWatcher(settings, proto_run, new_interface, True)
210
+ watcher = tb_watcher.TBWatcher(
211
+ settings_static,
212
+ proto_run,
213
+ new_interface,
214
+ True,
215
+ )
211
216
 
212
217
  for tb in tb_logdirs:
213
218
  watcher.add(tb, True, tb_root)
wandb/util.py CHANGED
@@ -229,7 +229,7 @@ def import_module_lazy(name: str) -> types.ModuleType:
229
229
 
230
230
  def get_module(
231
231
  name: str,
232
- required: Optional[Union[str, bool]] = None,
232
+ required: Optional[str] = None,
233
233
  lazy: bool = True,
234
234
  ) -> Any:
235
235
  """Return module or None. Absolute import is required.
wandb/wandb_agent.py CHANGED
@@ -42,11 +42,42 @@ class AgentProcess:
42
42
  if command:
43
43
  if platform.system() == "Windows":
44
44
  kwargs = dict(creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
45
+ env.pop(wandb.env.SERVICE, None)
46
+ # TODO: Determine if we need the same stdin workaround as POSIX case below.
47
+ self._popen = subprocess.Popen(command, env=env, **kwargs)
45
48
  else:
46
- kwargs = dict(preexec_fn=os.setpgrp)
47
- if env.get(wandb.env.SERVICE):
48
- env.pop(wandb.env.SERVICE)
49
- self._popen = subprocess.Popen(command, env=env, **kwargs)
49
+ if sys.version_info >= (3, 11):
50
+ # preexec_fn=os.setpgrp is not thread-safe; process_group was introduced in
51
+ # python 3.11 to replace it, so use that when possible
52
+ kwargs = dict(process_group=0)
53
+ else:
54
+ kwargs = dict(preexec_fn=os.setpgrp)
55
+ env.pop(wandb.env.SERVICE, None)
56
+ # Upon spawning the subprocess in a new process group, the child's process group is
57
+ # not connected to the controlling terminal's stdin. If it tries to access stdin,
58
+ # it gets a SIGTTIN and blocks until we give it the terminal, which we don't want
59
+ # to do.
60
+ #
61
+ # By using subprocess.PIPE, we give it an independent stdin. However, it will still
62
+ # block if it tries to read from stdin, because we're not writing anything to it.
63
+ # We immediately close the subprocess's stdin here so it can fail fast and get an
64
+ # EOF.
65
+ #
66
+ # (One situation that makes this relevant is that importing `readline` even
67
+ # indirectly can cause the child to attempt to access stdin, which can trigger the
68
+ # deadlock. In Python 3.13, `import torch` indirectly imports `readline` via `pdb`,
69
+ # meaning `import torch` in a run script can deadlock unless we override stdin.
70
+ # See https://github.com/wandb/wandb/pull/10489 description for more details.)
71
+ #
72
+ # Also, we avoid spawning a new session because that breaks preempted child process
73
+ # handling.
74
+ self._popen = subprocess.Popen(
75
+ command,
76
+ env=env,
77
+ stdin=subprocess.PIPE,
78
+ **kwargs,
79
+ )
80
+ self._popen.stdin.close()
50
81
  elif function:
51
82
  self._proc = multiprocessing.Process(
52
83
  target=self._start,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wandb
3
- Version: 0.21.4
3
+ Version: 0.22.1
4
4
  Summary: A CLI and library for interacting with the Weights & Biases API.
5
5
  Project-URL: Source, https://github.com/wandb/wandb
6
6
  Project-URL: Bug Reports, https://github.com/wandb/wandb/issues