wandb 0.21.0__py3-none-win_amd64.whl → 0.21.2__py3-none-win_amd64.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 (153) hide show
  1. wandb/__init__.py +16 -14
  2. wandb/__init__.pyi +427 -450
  3. wandb/agents/pyagent.py +41 -12
  4. wandb/analytics/sentry.py +7 -2
  5. wandb/apis/importers/mlflow.py +1 -1
  6. wandb/apis/public/__init__.py +1 -1
  7. wandb/apis/public/api.py +525 -360
  8. wandb/apis/public/artifacts.py +207 -13
  9. wandb/apis/public/automations.py +19 -3
  10. wandb/apis/public/files.py +172 -33
  11. wandb/apis/public/history.py +67 -15
  12. wandb/apis/public/integrations.py +25 -2
  13. wandb/apis/public/jobs.py +90 -2
  14. wandb/apis/public/projects.py +130 -79
  15. wandb/apis/public/query_generator.py +11 -1
  16. wandb/apis/public/registries/_utils.py +14 -16
  17. wandb/apis/public/registries/registries_search.py +183 -304
  18. wandb/apis/public/reports.py +96 -15
  19. wandb/apis/public/runs.py +299 -105
  20. wandb/apis/public/sweeps.py +222 -22
  21. wandb/apis/public/teams.py +41 -4
  22. wandb/apis/public/users.py +45 -4
  23. wandb/automations/_generated/delete_automation.py +1 -3
  24. wandb/automations/_generated/enums.py +13 -11
  25. wandb/beta/workflows.py +66 -30
  26. wandb/bin/gpu_stats.exe +0 -0
  27. wandb/bin/wandb-core +0 -0
  28. wandb/cli/cli.py +127 -3
  29. wandb/env.py +8 -0
  30. wandb/errors/errors.py +4 -1
  31. wandb/integration/lightning/fabric/logger.py +3 -4
  32. wandb/integration/metaflow/__init__.py +6 -0
  33. wandb/integration/metaflow/data_pandas.py +74 -0
  34. wandb/integration/metaflow/data_pytorch.py +75 -0
  35. wandb/integration/metaflow/data_sklearn.py +76 -0
  36. wandb/integration/metaflow/errors.py +13 -0
  37. wandb/integration/metaflow/metaflow.py +167 -223
  38. wandb/integration/openai/fine_tuning.py +1 -2
  39. wandb/integration/weave/__init__.py +6 -0
  40. wandb/integration/weave/interface.py +49 -0
  41. wandb/integration/weave/weave.py +63 -0
  42. wandb/jupyter.py +5 -5
  43. wandb/plot/custom_chart.py +30 -7
  44. wandb/proto/v3/wandb_internal_pb2.py +281 -280
  45. wandb/proto/v3/wandb_telemetry_pb2.py +4 -4
  46. wandb/proto/v4/wandb_internal_pb2.py +280 -280
  47. wandb/proto/v4/wandb_telemetry_pb2.py +4 -4
  48. wandb/proto/v5/wandb_internal_pb2.py +280 -280
  49. wandb/proto/v5/wandb_telemetry_pb2.py +4 -4
  50. wandb/proto/v6/wandb_internal_pb2.py +280 -280
  51. wandb/proto/v6/wandb_telemetry_pb2.py +4 -4
  52. wandb/proto/wandb_deprecated.py +6 -0
  53. wandb/sdk/artifacts/_factories.py +17 -0
  54. wandb/sdk/artifacts/_generated/__init__.py +221 -13
  55. wandb/sdk/artifacts/_generated/artifact_by_id.py +17 -0
  56. wandb/sdk/artifacts/_generated/artifact_by_name.py +22 -0
  57. wandb/sdk/artifacts/_generated/artifact_collection_membership_file_urls.py +43 -0
  58. wandb/sdk/artifacts/_generated/artifact_created_by.py +47 -0
  59. wandb/sdk/artifacts/_generated/artifact_file_urls.py +22 -0
  60. wandb/sdk/artifacts/_generated/artifact_type.py +31 -0
  61. wandb/sdk/artifacts/_generated/artifact_used_by.py +43 -0
  62. wandb/sdk/artifacts/_generated/artifact_via_membership_by_name.py +26 -0
  63. wandb/sdk/artifacts/_generated/delete_artifact.py +28 -0
  64. wandb/sdk/artifacts/_generated/enums.py +5 -0
  65. wandb/sdk/artifacts/_generated/fetch_artifact_manifest.py +38 -0
  66. wandb/sdk/artifacts/_generated/fetch_registries.py +32 -0
  67. wandb/sdk/artifacts/_generated/fragments.py +279 -41
  68. wandb/sdk/artifacts/_generated/link_artifact.py +6 -0
  69. wandb/sdk/artifacts/_generated/operations.py +654 -51
  70. wandb/sdk/artifacts/_generated/registry_collections.py +34 -0
  71. wandb/sdk/artifacts/_generated/registry_versions.py +34 -0
  72. wandb/sdk/artifacts/_generated/unlink_artifact.py +25 -0
  73. wandb/sdk/artifacts/_graphql_fragments.py +3 -86
  74. wandb/sdk/artifacts/_internal_artifact.py +19 -8
  75. wandb/sdk/artifacts/_validators.py +14 -4
  76. wandb/sdk/artifacts/artifact.py +512 -618
  77. wandb/sdk/artifacts/artifact_file_cache.py +10 -6
  78. wandb/sdk/artifacts/artifact_manifest.py +10 -9
  79. wandb/sdk/artifacts/artifact_manifest_entry.py +9 -10
  80. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +5 -3
  81. wandb/sdk/artifacts/storage_handlers/http_handler.py +1 -1
  82. wandb/sdk/artifacts/storage_handlers/s3_handler.py +1 -1
  83. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +1 -1
  84. wandb/sdk/data_types/audio.py +38 -10
  85. wandb/sdk/data_types/base_types/media.py +6 -56
  86. wandb/sdk/data_types/graph.py +48 -14
  87. wandb/sdk/data_types/helper_types/bounding_boxes_2d.py +1 -3
  88. wandb/sdk/data_types/helper_types/image_mask.py +1 -3
  89. wandb/sdk/data_types/histogram.py +34 -21
  90. wandb/sdk/data_types/html.py +35 -12
  91. wandb/sdk/data_types/image.py +104 -68
  92. wandb/sdk/data_types/molecule.py +32 -19
  93. wandb/sdk/data_types/object_3d.py +36 -17
  94. wandb/sdk/data_types/plotly.py +18 -5
  95. wandb/sdk/data_types/saved_model.py +4 -6
  96. wandb/sdk/data_types/table.py +59 -30
  97. wandb/sdk/data_types/video.py +53 -26
  98. wandb/sdk/integration_utils/auto_logging.py +2 -2
  99. wandb/sdk/interface/interface_queue.py +1 -4
  100. wandb/sdk/interface/interface_shared.py +26 -37
  101. wandb/sdk/interface/interface_sock.py +24 -14
  102. wandb/sdk/internal/internal_api.py +6 -0
  103. wandb/sdk/internal/job_builder.py +6 -0
  104. wandb/sdk/internal/settings_static.py +2 -3
  105. wandb/sdk/launch/agent/agent.py +8 -1
  106. wandb/sdk/launch/agent/run_queue_item_file_saver.py +2 -2
  107. wandb/sdk/launch/create_job.py +15 -2
  108. wandb/sdk/launch/inputs/internal.py +3 -4
  109. wandb/sdk/launch/inputs/schema.py +1 -0
  110. wandb/sdk/launch/runner/kubernetes_monitor.py +1 -0
  111. wandb/sdk/launch/runner/kubernetes_runner.py +323 -1
  112. wandb/sdk/launch/sweeps/scheduler.py +2 -3
  113. wandb/sdk/lib/asyncio_compat.py +19 -16
  114. wandb/sdk/lib/asyncio_manager.py +252 -0
  115. wandb/sdk/lib/deprecate.py +1 -7
  116. wandb/sdk/lib/disabled.py +1 -1
  117. wandb/sdk/lib/hashutil.py +27 -5
  118. wandb/sdk/lib/module.py +7 -13
  119. wandb/sdk/lib/printer.py +2 -2
  120. wandb/sdk/lib/printer_asyncio.py +3 -1
  121. wandb/sdk/lib/progress.py +0 -19
  122. wandb/sdk/lib/retry.py +185 -78
  123. wandb/sdk/lib/service/service_client.py +106 -0
  124. wandb/sdk/lib/service/service_connection.py +20 -26
  125. wandb/sdk/lib/service/service_token.py +30 -13
  126. wandb/sdk/mailbox/mailbox.py +13 -5
  127. wandb/sdk/mailbox/mailbox_handle.py +22 -13
  128. wandb/sdk/mailbox/response_handle.py +42 -106
  129. wandb/sdk/mailbox/wait_with_progress.py +7 -42
  130. wandb/sdk/wandb_init.py +77 -116
  131. wandb/sdk/wandb_login.py +19 -15
  132. wandb/sdk/wandb_metric.py +2 -0
  133. wandb/sdk/wandb_run.py +497 -469
  134. wandb/sdk/wandb_settings.py +145 -4
  135. wandb/sdk/wandb_setup.py +204 -124
  136. wandb/sdk/wandb_sweep.py +14 -13
  137. wandb/sdk/wandb_watch.py +4 -6
  138. wandb/sync/sync.py +10 -0
  139. wandb/util.py +58 -1
  140. wandb/wandb_run.py +1 -2
  141. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/METADATA +1 -1
  142. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/RECORD +145 -129
  143. wandb/sdk/interface/interface_relay.py +0 -38
  144. wandb/sdk/interface/router.py +0 -89
  145. wandb/sdk/interface/router_queue.py +0 -43
  146. wandb/sdk/interface/router_relay.py +0 -50
  147. wandb/sdk/interface/router_sock.py +0 -32
  148. wandb/sdk/lib/sock_client.py +0 -236
  149. wandb/vendor/pynvml/__init__.py +0 -0
  150. wandb/vendor/pynvml/pynvml.py +0 -4779
  151. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/WHEEL +0 -0
  152. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/entry_points.txt +0 -0
  153. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/wandb_setup.py CHANGED
@@ -28,12 +28,13 @@ import logging
28
28
  import os
29
29
  import pathlib
30
30
  import sys
31
+ import threading
31
32
  from typing import TYPE_CHECKING, Any, Union
32
33
 
33
34
  import wandb
34
35
  import wandb.integration.sagemaker as sagemaker
35
36
  from wandb.env import CONFIG_DIR
36
- from wandb.sdk.lib import import_hooks, wb_logging
37
+ from wandb.sdk.lib import asyncio_manager, import_hooks, wb_logging
37
38
 
38
39
  from . import wandb_settings
39
40
  from .lib import config_util, server
@@ -88,19 +89,16 @@ Logger = Union[logging.Logger, _EarlyLogger]
88
89
  class _WandbSetup:
89
90
  """W&B library singleton."""
90
91
 
91
- def __init__(
92
- self,
93
- pid: int,
94
- settings: Settings | None = None,
95
- environ: dict | None = None,
96
- ) -> None:
92
+ def __init__(self, pid: int) -> None:
93
+ self._asyncer = asyncio_manager.AsyncioManager()
94
+ self._asyncer.start()
95
+
97
96
  self._connection: ServiceConnection | None = None
98
97
 
99
98
  self._active_runs: list[wandb_run.Run] = []
99
+ self._active_runs_lock = threading.Lock()
100
100
 
101
- self._environ = environ or dict(os.environ)
102
101
  self._sweep_config: dict | None = None
103
- self._config: dict | None = None
104
102
  self._server: server.Server | None = None
105
103
  self._pid = pid
106
104
 
@@ -108,11 +106,13 @@ class _WandbSetup:
108
106
  # and logging is ready
109
107
  self._logger: Logger = _EarlyLogger()
110
108
 
111
- self._settings = self._settings_setup(settings)
109
+ self._settings: Settings | None = None
110
+ self._settings_environ: dict[str, str] | None = None
112
111
 
113
- wandb.termsetup(self._settings, None)
114
-
115
- self._setup()
112
+ @property
113
+ def asyncer(self) -> asyncio_manager.AsyncioManager:
114
+ """The internal asyncio thread used by wandb."""
115
+ return self._asyncer
116
116
 
117
117
  def add_active_run(self, run: wandb_run.Run) -> None:
118
118
  """Append a run to the active runs list.
@@ -122,8 +122,9 @@ class _WandbSetup:
122
122
  Args:
123
123
  run: A newly initialized run.
124
124
  """
125
- if run not in self._active_runs:
126
- self._active_runs.append(run)
125
+ with self._active_runs_lock:
126
+ if run not in self._active_runs:
127
+ self._active_runs.append(run)
127
128
 
128
129
  def remove_active_run(self, run: wandb_run.Run) -> None:
129
130
  """Remove the run from the active runs list.
@@ -134,17 +135,19 @@ class _WandbSetup:
134
135
  run: A run that is finished or crashed.
135
136
  """
136
137
  try:
137
- self._active_runs.remove(run)
138
+ with self._active_runs_lock:
139
+ self._active_runs.remove(run)
138
140
  except ValueError:
139
141
  pass # Removing a run multiple times is not an error.
140
142
 
141
143
  @property
142
144
  def most_recent_active_run(self) -> wandb_run.Run | None:
143
145
  """The most recently initialized run that is not yet finished."""
144
- if not self._active_runs:
145
- return None
146
+ with self._active_runs_lock:
147
+ if not self._active_runs:
148
+ return None
146
149
 
147
- return self._active_runs[-1]
150
+ return self._active_runs[-1]
148
151
 
149
152
  def finish_all_active_runs(self) -> None:
150
153
  """Finish all unfinished runs.
@@ -156,74 +159,131 @@ class _WandbSetup:
156
159
  default and only behavior, it does not seem worth optimizing.
157
160
  """
158
161
  # Take a snapshot as each call to `finish()` modifies `_active_runs`.
159
- runs_copy = list(self._active_runs)
162
+ with self._active_runs_lock:
163
+ runs_copy = list(self._active_runs)
164
+
160
165
  for run in runs_copy:
161
166
  run.finish()
162
167
 
163
- def _settings_setup(
168
+ def did_environment_change(self) -> bool:
169
+ """Check if os.environ has changed since settings were initialized."""
170
+ if not self._settings_environ:
171
+ return False
172
+
173
+ exclude_env_vars = {"WANDB_SERVICE", "WANDB_KUBEFLOW_URL"}
174
+ singleton_env = {
175
+ k: v
176
+ for k, v in self._settings_environ.items()
177
+ if k.startswith("WANDB_") and k not in exclude_env_vars
178
+ }
179
+ os_env = {
180
+ k: v
181
+ for k, v in os.environ.items()
182
+ if k.startswith("WANDB_") and k not in exclude_env_vars
183
+ }
184
+
185
+ return (
186
+ set(singleton_env.keys()) == set(os_env.keys()) #
187
+ and set(singleton_env.values()) == set(os_env.values())
188
+ )
189
+
190
+ def _load_settings(
164
191
  self,
165
- settings: Settings | None,
166
- ) -> wandb_settings.Settings:
167
- s = wandb_settings.Settings()
192
+ *,
193
+ system_settings_path: str | None,
194
+ disable_sagemaker: bool,
195
+ overrides: Settings | None = None,
196
+ ) -> None:
197
+ """Load settings from environment variables, config files, etc.
198
+
199
+ Args:
200
+ system_settings_path: Location of system settings file to use.
201
+ If not provided, reads the WANDB_CONFIG_DIR environment
202
+ variable or uses the default location.
203
+ disable_sagemaker: If true, skips modifying settings based on
204
+ SageMaker.
205
+ overrides: Additional settings to apply to the global settings.
206
+ """
207
+ self._settings = wandb_settings.Settings()
168
208
 
169
209
  # the pid of the process to monitor for system stats
170
210
  pid = os.getpid()
171
211
  self._logger.info(f"Current SDK version is {wandb.__version__}")
172
212
  self._logger.info(f"Configure stats pid to {pid}")
173
- s.x_stats_pid = pid
213
+ self._settings.x_stats_pid = pid
174
214
 
175
- if settings and settings.settings_system:
176
- s.settings_system = settings.settings_system
215
+ if system_settings_path:
216
+ self._settings.settings_system = system_settings_path
177
217
  elif config_dir_str := os.getenv(CONFIG_DIR, None):
178
218
  config_dir = pathlib.Path(config_dir_str).expanduser()
179
- s.settings_system = str(config_dir / "settings")
219
+ self._settings.settings_system = str(config_dir / "settings")
180
220
  else:
181
- s.settings_system = str(
221
+ self._settings.settings_system = str(
182
222
  pathlib.Path("~", ".config", "wandb", "settings").expanduser()
183
223
  )
184
224
 
185
225
  # load settings from the system config
186
- if s.settings_system:
187
- self._logger.info(f"Loading settings from {s.settings_system}")
188
- s.update_from_system_config_file()
226
+ if self._settings.settings_system:
227
+ self._logger.info(
228
+ f"Loading settings from {self._settings.settings_system}",
229
+ )
230
+ self._settings.update_from_system_config_file()
189
231
 
190
232
  # load settings from the workspace config
191
- if s.settings_workspace:
192
- self._logger.info(f"Loading settings from {s.settings_workspace}")
193
- s.update_from_workspace_config_file()
233
+ if self._settings.settings_workspace:
234
+ self._logger.info(
235
+ f"Loading settings from {self._settings.settings_workspace}",
236
+ )
237
+ self._settings.update_from_workspace_config_file()
194
238
 
195
239
  # load settings from the environment variables
196
240
  self._logger.info("Loading settings from environment variables")
197
- s.update_from_env_vars(self._environ)
241
+ self._settings_environ = os.environ.copy()
242
+ self._settings.update_from_env_vars(self._settings_environ)
198
243
 
199
244
  # infer settings from the system environment
200
- s.update_from_system_environment()
245
+ self._settings.update_from_system_environment()
201
246
 
202
247
  # load SageMaker settings
203
- check_sagemaker_env = not s.sagemaker_disable
204
- if settings and settings.sagemaker_disable:
205
- check_sagemaker_env = False
206
- if check_sagemaker_env and sagemaker.is_using_sagemaker():
248
+ if (
249
+ not self._settings.sagemaker_disable
250
+ and not disable_sagemaker
251
+ and sagemaker.is_using_sagemaker()
252
+ ):
207
253
  self._logger.info("Loading SageMaker settings")
208
- sagemaker.set_global_settings(s)
254
+ sagemaker.set_global_settings(self._settings)
209
255
 
210
256
  # load settings from the passed init/setup settings
211
- if settings:
212
- s.update_from_settings(settings)
257
+ if overrides:
258
+ self._settings.update_from_settings(overrides)
213
259
 
214
- return s
260
+ wandb.termsetup(self._settings, None)
215
261
 
216
- def _update(self, settings: Settings | None = None) -> None:
217
- if not settings:
218
- return
219
- self._settings.update_from_settings(settings)
262
+ def _update(self, settings: Settings | None) -> None:
263
+ """Update settings, initializing them if necessary.
220
264
 
221
- def _update_user_settings(self) -> None:
265
+ Args:
266
+ settings: Overrides to apply, if any.
267
+ """
268
+ if not self._settings:
269
+ system_settings_path = settings.settings_system if settings else None
270
+ disable_sagemaker = settings.sagemaker_disable if settings else False
271
+ self._load_settings(
272
+ system_settings_path=system_settings_path,
273
+ disable_sagemaker=disable_sagemaker,
274
+ overrides=settings,
275
+ )
276
+
277
+ # This is 'elif' because load_settings already applies overrides.
278
+ elif settings:
279
+ self._settings.update_from_settings(settings)
280
+
281
+ def update_user_settings(self) -> None:
222
282
  # Get rid of cached results to force a refresh.
223
283
  self._server = None
224
284
  user_settings = self._load_user_settings()
225
285
  if user_settings is not None:
226
- self._settings.update_from_dict(user_settings)
286
+ self.settings.update_from_dict(user_settings)
227
287
 
228
288
  def _early_logger_flush(self, new_logger: Logger) -> None:
229
289
  if self._logger is new_logger:
@@ -238,6 +298,22 @@ class _WandbSetup:
238
298
 
239
299
  @property
240
300
  def settings(self) -> wandb_settings.Settings:
301
+ """The global wandb settings.
302
+
303
+ Initializes settings if they have not yet been loaded.
304
+ """
305
+ if not self._settings:
306
+ self._load_settings(
307
+ system_settings_path=None,
308
+ disable_sagemaker=False,
309
+ )
310
+ assert self._settings
311
+
312
+ return self._settings
313
+
314
+ @property
315
+ def settings_if_loaded(self) -> wandb_settings.Settings | None:
316
+ """The global wandb settings, or None if not yet loaded."""
241
317
  return self._settings
242
318
 
243
319
  def _get_entity(self) -> str | None:
@@ -262,7 +338,7 @@ class _WandbSetup:
262
338
  @property
263
339
  def viewer(self) -> dict[str, Any]:
264
340
  if self._server is None:
265
- self._server = server.Server(settings=self._settings)
341
+ self._server = server.Server(settings=self.settings)
266
342
 
267
343
  return self._server.viewer
268
344
 
@@ -282,36 +358,36 @@ class _WandbSetup:
282
358
 
283
359
  return user_settings
284
360
 
285
- def _setup(self) -> None:
286
- sweep_path = self._settings.sweep_param_path
361
+ @property
362
+ def config(self) -> dict:
363
+ sweep_path = self.settings.sweep_param_path
287
364
  if sweep_path:
288
365
  self._sweep_config = config_util.dict_from_config_file(
289
366
  sweep_path, must_exist=True
290
367
  )
291
368
 
369
+ config = {}
370
+
292
371
  # if config_paths was set, read in config dict
293
- if self._settings.config_paths:
372
+ if self.settings.config_paths:
294
373
  # TODO(jhr): handle load errors, handle list of files
295
- for config_path in self._settings.config_paths:
374
+ for config_path in self.settings.config_paths:
296
375
  config_dict = config_util.dict_from_config_file(config_path)
297
- if config_dict is None:
298
- continue
299
- if self._config is not None:
300
- self._config.update(config_dict)
301
- else:
302
- self._config = config_dict
376
+ if config_dict:
377
+ config.update(config_dict)
378
+
379
+ return config
303
380
 
304
381
  def _teardown(self, exit_code: int | None = None) -> None:
305
382
  import_hooks.unregister_all_post_import_hooks()
306
383
 
307
- if not self._connection:
308
- return
384
+ if self._connection:
385
+ internal_exit_code = self._connection.teardown(exit_code or 0)
386
+ else:
387
+ internal_exit_code = None
309
388
 
310
- # Reset to None so that setup() creates a new connection.
311
- connection = self._connection
312
- self._connection = None
389
+ self._asyncer.join()
313
390
 
314
- internal_exit_code = connection.teardown(exit_code or 0)
315
391
  if internal_exit_code not in (None, 0):
316
392
  sys.exit(internal_exit_code)
317
393
 
@@ -322,7 +398,10 @@ class _WandbSetup:
322
398
 
323
399
  from wandb.sdk.lib.service import service_connection
324
400
 
325
- self._connection = service_connection.connect_to_service(self._settings)
401
+ self._connection = service_connection.connect_to_service(
402
+ self._asyncer,
403
+ self.settings,
404
+ )
326
405
  return self._connection
327
406
 
328
407
  def assert_service(self) -> ServiceConnection:
@@ -343,6 +422,8 @@ _singleton: _WandbSetup | None = None
343
422
  The value is invalid and must not be used if `os.getpid() != _singleton._pid`.
344
423
  """
345
424
 
425
+ _singleton_lock = threading.Lock()
426
+
346
427
 
347
428
  def singleton() -> _WandbSetup:
348
429
  """The W&B singleton for the current process.
@@ -351,29 +432,14 @@ def singleton() -> _WandbSetup:
351
432
  process) creates the singleton, and all subsequent calls return it
352
433
  until teardown(). This does not start the service process.
353
434
  """
354
- return _setup(start_service=False)
355
-
356
-
357
- def singleton_if_setup() -> _WandbSetup | None:
358
- """The W&B singleton for the current process or None if it isn't set up.
359
-
360
- Always prefer singleton() over this function.
361
-
362
- Unlike singleton(), this never creates the singleton and therefore never
363
- initializes global settings from the environment. This is useful only
364
- during tests, which may modify the environment after having imported wandb
365
- and called certain functions.
366
- """
367
- if _singleton and _singleton._pid == os.getpid():
368
- return _singleton
369
- else:
370
- return None
435
+ return _setup(start_service=False, load_settings=False)
371
436
 
372
437
 
373
438
  @wb_logging.log_to_all_runs()
374
439
  def _setup(
375
440
  settings: Settings | None = None,
376
441
  start_service: bool = True,
442
+ load_settings: bool = True,
377
443
  ) -> _WandbSetup:
378
444
  """Set up library context.
379
445
 
@@ -384,18 +450,31 @@ def _setup(
384
450
  NOTE: A service process will only be started if allowed by the
385
451
  global settings (after the given updates). The service will not
386
452
  start up if the mode resolves to "disabled".
453
+ load_settings: Whether to load settings from the environment
454
+ if creating a new singleton. If False, then settings and
455
+ start_service must be None.
387
456
  """
388
457
  global _singleton
389
458
 
459
+ if not load_settings and settings:
460
+ raise ValueError("Cannot pass settings if load_settings is False.")
461
+ if not load_settings and start_service:
462
+ raise ValueError("Cannot use start_service if load_settings is False.")
463
+
390
464
  pid = os.getpid()
465
+ with _singleton_lock:
466
+ if _singleton and _singleton._pid == pid:
467
+ current_singleton = _singleton
468
+ else:
469
+ current_singleton = _WandbSetup(pid=pid)
470
+
471
+ if load_settings:
472
+ current_singleton._update(settings)
391
473
 
392
- if _singleton and _singleton._pid == pid:
393
- _singleton._update(settings=settings)
394
- else:
395
- _singleton = _WandbSetup(settings=settings, pid=pid)
474
+ if start_service and not current_singleton.settings._noop:
475
+ current_singleton.ensure_service()
396
476
 
397
- if start_service and not _singleton.settings._noop:
398
- _singleton.ensure_service()
477
+ _singleton = current_singleton
399
478
 
400
479
  return _singleton
401
480
 
@@ -419,49 +498,49 @@ def setup(settings: Settings | None = None) -> _WandbSetup:
419
498
  overridden by subsequent `wandb.init()` calls.
420
499
 
421
500
  Example:
422
- ```python
423
- import multiprocessing
501
+ ```python
502
+ import multiprocessing
424
503
 
425
- import wandb
504
+ import wandb
426
505
 
427
506
 
428
- def run_experiment(params):
429
- with wandb.init(config=params):
430
- # Run experiment
431
- pass
507
+ def run_experiment(params):
508
+ with wandb.init(config=params):
509
+ # Run experiment
510
+ pass
432
511
 
433
512
 
434
- if __name__ == "__main__":
435
- # Start backend and set global config
436
- wandb.setup(settings={"project": "my_project"})
513
+ if __name__ == "__main__":
514
+ # Start backend and set global config
515
+ wandb.setup(settings={"project": "my_project"})
437
516
 
438
- # Define experiment parameters
439
- experiment_params = [
440
- {"learning_rate": 0.01, "epochs": 10},
441
- {"learning_rate": 0.001, "epochs": 20},
442
- ]
517
+ # Define experiment parameters
518
+ experiment_params = [
519
+ {"learning_rate": 0.01, "epochs": 10},
520
+ {"learning_rate": 0.001, "epochs": 20},
521
+ ]
443
522
 
444
- # Start multiple processes, each running a separate experiment
445
- processes = []
446
- for params in experiment_params:
447
- p = multiprocessing.Process(target=run_experiment, args=(params,))
448
- p.start()
449
- processes.append(p)
523
+ # Start multiple processes, each running a separate experiment
524
+ processes = []
525
+ for params in experiment_params:
526
+ p = multiprocessing.Process(target=run_experiment, args=(params,))
527
+ p.start()
528
+ processes.append(p)
450
529
 
451
- # Wait for all processes to complete
452
- for p in processes:
453
- p.join()
530
+ # Wait for all processes to complete
531
+ for p in processes:
532
+ p.join()
454
533
 
455
- # Optional: Explicitly shut down the backend
456
- wandb.teardown()
457
- ```
534
+ # Optional: Explicitly shut down the backend
535
+ wandb.teardown()
536
+ ```
458
537
  """
459
538
  return _setup(settings=settings)
460
539
 
461
540
 
462
541
  @wb_logging.log_to_all_runs()
463
542
  def teardown(exit_code: int | None = None) -> None:
464
- """Waits for wandb to finish and frees resources.
543
+ """Waits for W&B to finish and frees resources.
465
544
 
466
545
  Completes any runs that were not explicitly finished
467
546
  using `run.finish()` and waits for all data to be uploaded.
@@ -473,8 +552,9 @@ def teardown(exit_code: int | None = None) -> None:
473
552
  """
474
553
  global _singleton
475
554
 
476
- orig_singleton = _singleton
477
- _singleton = None
555
+ with _singleton_lock:
556
+ orig_singleton = _singleton
557
+ _singleton = None
478
558
 
479
- if orig_singleton:
480
- orig_singleton._teardown(exit_code=exit_code)
559
+ if orig_singleton:
560
+ orig_singleton._teardown(exit_code=exit_code)
wandb/sdk/wandb_sweep.py CHANGED
@@ -45,11 +45,12 @@ def sweep(
45
45
  Make note the unique identifier, `sweep_id`, that is returned.
46
46
  At a later step provide the `sweep_id` to a sweep agent.
47
47
 
48
+ See [Sweep configuration structure](https://docs.wandb.ai/guides/sweeps/define-sweep-configuration)
49
+ for information on how to define your sweep.
50
+
48
51
  Args:
49
52
  sweep: The configuration of a hyperparameter search.
50
- (or configuration generator). See
51
- [Sweep configuration structure](https://docs.wandb.ai/guides/sweeps/define-sweep-configuration)
52
- for information on how to define your sweep.
53
+ (or configuration generator).
53
54
  If you provide a callable, ensure that the callable does
54
55
  not take arguments and that it returns a dictionary that
55
56
  conforms to the W&B sweep config spec.
@@ -64,7 +65,7 @@ def sweep(
64
65
  prior_runs: The run IDs of existing runs to add to this sweep.
65
66
 
66
67
  Returns:
67
- sweep_id: str. A unique identifier for the sweep.
68
+ str: A unique identifier for the sweep.
68
69
  """
69
70
  if callable(sweep):
70
71
  sweep = sweep()
@@ -99,16 +100,16 @@ def controller(
99
100
  ) -> "_WandbController":
100
101
  """Public sweep controller constructor.
101
102
 
102
- Usage:
103
- ```python
104
- import wandb
103
+ Examples:
104
+ ```python
105
+ import wandb
105
106
 
106
- tuner = wandb.controller(...)
107
- print(tuner.sweep_config)
108
- print(tuner.sweep_id)
109
- tuner.configure_search(...)
110
- tuner.configure_stopping(...)
111
- ```
107
+ tuner = wandb.controller(...)
108
+ print(tuner.sweep_config)
109
+ print(tuner.sweep_id)
110
+ tuner.configure_search(...)
111
+ tuner.configure_stopping(...)
112
+ ```
112
113
 
113
114
  """
114
115
  from ..wandb_controller import _WandbController
wandb/sdk/wandb_watch.py CHANGED
@@ -17,15 +17,13 @@ from .lib import telemetry
17
17
  if TYPE_CHECKING:
18
18
  import torch # type: ignore [import-not-found]
19
19
 
20
- from wandb.sdk.wandb_run import Run
21
-
22
20
  logger = logging.getLogger("wandb")
23
21
 
24
22
  _global_watch_idx = 0
25
23
 
26
24
 
27
25
  def _watch(
28
- run: Run,
26
+ run: wandb.Run,
29
27
  models: torch.nn.Module | Sequence[torch.nn.Module],
30
28
  criterion: torch.F | None = None,
31
29
  log: Literal["gradients", "parameters", "all"] | None = "gradients",
@@ -39,7 +37,7 @@ def _watch(
39
37
  extended to support arbitrary machine learning models in the future.
40
38
 
41
39
  Args:
42
- run (wandb.sdk.wandb_run.Run): The run object to log to.
40
+ run (wandb.Run): The run object to log to.
43
41
  models (Union[torch.nn.Module, Sequence[torch.nn.Module]]):
44
42
  A single model or a sequence of models to be monitored.
45
43
  criterion (Optional[torch.F]):
@@ -122,12 +120,12 @@ def _watch(
122
120
 
123
121
 
124
122
  def _unwatch(
125
- run: Run, models: torch.nn.Module | Sequence[torch.nn.Module] | None = None
123
+ run: wandb.Run, models: torch.nn.Module | Sequence[torch.nn.Module] | None = None
126
124
  ) -> None:
127
125
  """Remove pytorch model topology, gradient and parameter hooks.
128
126
 
129
127
  Args:
130
- run (wandb.sdk.wandb_run.Run):
128
+ run (wandb.Run):
131
129
  The run object to log to.
132
130
  models (torch.nn.Module | Sequence[torch.nn.Module]):
133
131
  Optional list of pytorch models that have had watch called on them
wandb/sync/sync.py CHANGED
@@ -54,6 +54,7 @@ class SyncThread(threading.Thread):
54
54
  log_path=None,
55
55
  append=None,
56
56
  skip_console=None,
57
+ replace_tags=None,
57
58
  ):
58
59
  threading.Thread.__init__(self)
59
60
  # mark this process as internal
@@ -71,6 +72,7 @@ class SyncThread(threading.Thread):
71
72
  self._log_path = log_path
72
73
  self._append = append
73
74
  self._skip_console = skip_console
75
+ self._replace_tags = replace_tags or {}
74
76
 
75
77
  self._tmp_dir = tempfile.TemporaryDirectory()
76
78
  atexit.register(self._tmp_dir.cleanup)
@@ -94,6 +96,11 @@ class SyncThread(threading.Thread):
94
96
  pb.run.entity = self._entity
95
97
  if self._job_type:
96
98
  pb.run.job_type = self._job_type
99
+ # Replace tags if specified
100
+ if self._replace_tags:
101
+ new_tags = [self._replace_tags.get(tag, tag) for tag in pb.run.tags]
102
+ pb.run.ClearField("tags")
103
+ pb.run.tags.extend(new_tags)
97
104
  pb.control.req_resp = True
98
105
  elif record_type in ("output", "output_raw") and self._skip_console:
99
106
  return pb, exit_pb, True
@@ -338,6 +345,7 @@ class SyncManager:
338
345
  log_path=None,
339
346
  append=None,
340
347
  skip_console=None,
348
+ replace_tags=None,
341
349
  ):
342
350
  self._sync_list = []
343
351
  self._thread = None
@@ -353,6 +361,7 @@ class SyncManager:
353
361
  self._log_path = log_path
354
362
  self._append = append
355
363
  self._skip_console = skip_console
364
+ self._replace_tags = replace_tags or {}
356
365
 
357
366
  def status(self):
358
367
  pass
@@ -376,6 +385,7 @@ class SyncManager:
376
385
  log_path=self._log_path,
377
386
  append=self._append,
378
387
  skip_console=self._skip_console,
388
+ replace_tags=self._replace_tags,
379
389
  )
380
390
  self._thread.start()
381
391