wandb 0.19.1__py3-none-win32.whl → 0.19.2__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 (79) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +3 -5
  3. wandb/agents/pyagent.py +1 -1
  4. wandb/apis/importers/wandb.py +1 -1
  5. wandb/apis/public/files.py +1 -1
  6. wandb/apis/public/jobs.py +1 -1
  7. wandb/apis/public/runs.py +2 -7
  8. wandb/apis/reports/v1/__init__.py +1 -1
  9. wandb/apis/reports/v2/__init__.py +1 -1
  10. wandb/apis/workspaces/__init__.py +1 -1
  11. wandb/bin/gpu_stats.exe +0 -0
  12. wandb/bin/wandb-core +0 -0
  13. wandb/cli/beta.py +7 -4
  14. wandb/cli/cli.py +5 -7
  15. wandb/docker/__init__.py +4 -4
  16. wandb/integration/fastai/__init__.py +4 -6
  17. wandb/integration/keras/keras.py +5 -3
  18. wandb/integration/metaflow/metaflow.py +7 -7
  19. wandb/integration/prodigy/prodigy.py +3 -11
  20. wandb/integration/sagemaker/__init__.py +5 -3
  21. wandb/integration/sagemaker/config.py +17 -8
  22. wandb/integration/sagemaker/files.py +0 -1
  23. wandb/integration/sagemaker/resources.py +47 -18
  24. wandb/integration/torch/wandb_torch.py +1 -1
  25. wandb/proto/v3/wandb_internal_pb2.py +273 -235
  26. wandb/proto/v4/wandb_internal_pb2.py +222 -214
  27. wandb/proto/v5/wandb_internal_pb2.py +222 -214
  28. wandb/sdk/artifacts/artifact.py +3 -9
  29. wandb/sdk/backend/backend.py +1 -1
  30. wandb/sdk/data_types/base_types/wb_value.py +1 -1
  31. wandb/sdk/data_types/graph.py +2 -2
  32. wandb/sdk/data_types/saved_model.py +1 -1
  33. wandb/sdk/data_types/video.py +1 -1
  34. wandb/sdk/interface/interface.py +25 -25
  35. wandb/sdk/interface/interface_shared.py +21 -5
  36. wandb/sdk/internal/handler.py +19 -1
  37. wandb/sdk/internal/internal.py +1 -1
  38. wandb/sdk/internal/internal_api.py +4 -5
  39. wandb/sdk/internal/sample.py +2 -2
  40. wandb/sdk/internal/sender.py +1 -2
  41. wandb/sdk/internal/settings_static.py +3 -1
  42. wandb/sdk/internal/system/assets/disk.py +4 -4
  43. wandb/sdk/internal/system/assets/gpu.py +1 -1
  44. wandb/sdk/internal/system/assets/memory.py +1 -1
  45. wandb/sdk/internal/system/system_info.py +1 -1
  46. wandb/sdk/internal/system/system_monitor.py +3 -1
  47. wandb/sdk/internal/tb_watcher.py +1 -1
  48. wandb/sdk/launch/_project_spec.py +3 -3
  49. wandb/sdk/launch/builder/abstract.py +1 -1
  50. wandb/sdk/lib/apikey.py +2 -3
  51. wandb/sdk/lib/fsm.py +1 -1
  52. wandb/sdk/lib/gitlib.py +1 -1
  53. wandb/sdk/lib/gql_request.py +1 -1
  54. wandb/sdk/lib/interrupt.py +37 -0
  55. wandb/sdk/lib/lazyloader.py +1 -1
  56. wandb/sdk/lib/service_connection.py +1 -1
  57. wandb/sdk/lib/telemetry.py +1 -1
  58. wandb/sdk/service/_startup_debug.py +1 -1
  59. wandb/sdk/service/server_sock.py +3 -2
  60. wandb/sdk/service/service.py +1 -1
  61. wandb/sdk/service/streams.py +19 -17
  62. wandb/sdk/verify/verify.py +13 -13
  63. wandb/sdk/wandb_init.py +95 -104
  64. wandb/sdk/wandb_login.py +1 -1
  65. wandb/sdk/wandb_metadata.py +547 -0
  66. wandb/sdk/wandb_run.py +127 -35
  67. wandb/sdk/wandb_settings.py +5 -36
  68. wandb/sdk/wandb_setup.py +83 -82
  69. wandb/sdk/wandb_sweep.py +2 -2
  70. wandb/sdk/wandb_sync.py +15 -18
  71. wandb/sync/sync.py +10 -10
  72. wandb/util.py +11 -3
  73. wandb/wandb_agent.py +11 -16
  74. wandb/wandb_controller.py +7 -7
  75. {wandb-0.19.1.dist-info → wandb-0.19.2.dist-info}/METADATA +3 -2
  76. {wandb-0.19.1.dist-info → wandb-0.19.2.dist-info}/RECORD +79 -77
  77. {wandb-0.19.1.dist-info → wandb-0.19.2.dist-info}/WHEEL +0 -0
  78. {wandb-0.19.1.dist-info → wandb-0.19.2.dist-info}/entry_points.txt +0 -0
  79. {wandb-0.19.1.dist-info → wandb-0.19.2.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/wandb_run.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import _thread as thread
4
3
  import atexit
5
4
  import functools
6
5
  import glob
@@ -37,6 +36,7 @@ from wandb.errors.links import url_registry
37
36
  from wandb.integration.torch import wandb_torch
38
37
  from wandb.plot import CustomChart, Visualize
39
38
  from wandb.proto.wandb_internal_pb2 import (
39
+ MetadataRequest,
40
40
  MetricRecord,
41
41
  PollExitResponse,
42
42
  Result,
@@ -73,6 +73,7 @@ from .lib import (
73
73
  deprecate,
74
74
  filenames,
75
75
  filesystem,
76
+ interrupt,
76
77
  ipython,
77
78
  module,
78
79
  printer,
@@ -84,6 +85,7 @@ from .lib import (
84
85
  from .lib.exit_hooks import ExitHooks
85
86
  from .lib.mailbox import MailboxError, MailboxHandle, MailboxProbe, MailboxProgress
86
87
  from .wandb_alerts import AlertLevel
88
+ from .wandb_metadata import Metadata
87
89
  from .wandb_settings import Settings
88
90
  from .wandb_setup import _WandbSetup
89
91
 
@@ -287,7 +289,7 @@ class RunStatusChecker:
287
289
  # TODO(frz): This check is required
288
290
  # until WB-3606 is resolved on server side.
289
291
  if not wandb.agents.pyagent.is_running(): # type: ignore
290
- thread.interrupt_main()
292
+ interrupt.interrupt_main()
291
293
  return
292
294
 
293
295
  try:
@@ -595,15 +597,17 @@ class Run:
595
597
  self._config._set_artifact_callback(self._config_artifact_callback)
596
598
  self._config._set_settings(self._settings)
597
599
 
598
- # todo: perhaps this should be a property that is a noop on a finished run
600
+ # TODO: perhaps this should be a property that is a noop on a finished run
599
601
  self.summary = wandb_summary.Summary(
600
602
  self._summary_get_current_summary_callback,
601
603
  )
602
604
  self.summary._set_update_callback(self._summary_update_callback)
603
605
 
606
+ self.__metadata: Metadata | None = None
607
+
604
608
  self._step = 0
605
609
  self._starting_step = 0
606
- # todo: eventually would be nice to make this configurable using self._settings._start_time
610
+ # TODO: eventually would be nice to make this configurable using self._settings._start_time
607
611
  # need to test (jhr): if you set start time to 2 days ago and run a test for 15 minutes,
608
612
  # does the total time get calculated right (not as 2 days and 15 minutes)?
609
613
  self._start_time = time.time()
@@ -1359,32 +1363,68 @@ class Run:
1359
1363
  files: FilesDict = dict(files=[(GlobStr(glob.escape(fname)), "now")])
1360
1364
  self._backend.interface.publish_files(files)
1361
1365
 
1362
- def _serialize_custom_charts(self, data: dict[str, Any]) -> dict[str, Any]:
1363
- if not data:
1364
- return data
1366
+ def _pop_all_charts(
1367
+ self,
1368
+ data: dict[str, Any],
1369
+ key_prefix: str | None = None,
1370
+ ) -> dict[str, Any]:
1371
+ """Pops all charts from a dictionary including nested charts.
1365
1372
 
1366
- chart_keys = set()
1373
+ This function will return a mapping of the charts and a dot-separated
1374
+ key for each chart. Indicating the path to the chart in the data dictionary.
1375
+ """
1376
+ keys_to_remove = set()
1377
+ charts: dict[str, Any] = {}
1367
1378
  for k, v in data.items():
1379
+ key = f"{key_prefix}.{k}" if key_prefix else k
1368
1380
  if isinstance(v, Visualize):
1369
- data[k] = v.table
1370
- v.set_key(k)
1371
- self._config_callback(
1372
- val=v.spec.config_value,
1373
- key=v.spec.config_key,
1374
- )
1381
+ keys_to_remove.add(k)
1382
+ charts[key] = v
1375
1383
  elif isinstance(v, CustomChart):
1376
- chart_keys.add(k)
1377
- v.set_key(k)
1378
- self._config_callback(
1379
- key=v.spec.config_key,
1380
- val=v.spec.config_value,
1381
- )
1384
+ keys_to_remove.add(k)
1385
+ charts[key] = v
1386
+ elif isinstance(v, dict):
1387
+ nested_charts = self._pop_all_charts(v, key)
1388
+ charts.update(nested_charts)
1389
+
1390
+ for k in keys_to_remove:
1391
+ data.pop(k)
1392
+
1393
+ return charts
1394
+
1395
+ def _serialize_custom_charts(
1396
+ self,
1397
+ data: dict[str, Any],
1398
+ ) -> dict[str, Any]:
1399
+ """Process and replace chart objects with their underlying table values.
1400
+
1401
+ This processes the chart objects passed to `run.log()`, replacing their entries
1402
+ in the given dictionary (which is saved to the run's history) and adding them
1403
+ to the run's config.
1404
+
1405
+ Args:
1406
+ data: Dictionary containing data that may include plot objects
1407
+ Plot objects can be nested in dictionaries, which will be processed recursively.
1408
+
1409
+ Returns:
1410
+ The processed dictionary with custom charts transformed into tables.
1411
+ """
1412
+ if not data:
1413
+ return data
1414
+
1415
+ charts = self._pop_all_charts(data)
1416
+ for k, v in charts.items():
1417
+ v.set_key(k)
1418
+ self._config_callback(
1419
+ val=v.spec.config_value,
1420
+ key=v.spec.config_key,
1421
+ )
1382
1422
 
1383
- for k in chart_keys:
1384
- # remove the chart key from the row
1385
- v = data.pop(k)
1386
1423
  if isinstance(v, CustomChart):
1387
1424
  data[v.spec.table_key] = v.table
1425
+ elif isinstance(v, Visualize):
1426
+ data[k] = v.table
1427
+
1388
1428
  return data
1389
1429
 
1390
1430
  def _partial_history_callback(
@@ -2133,8 +2173,9 @@ class Run:
2133
2173
  # Inform the service that we're done sending messages for this run.
2134
2174
  #
2135
2175
  # TODO: Why not do this in _atexit_cleanup()?
2136
- service = self._wl and self._wl.service
2137
- if service and self._settings.run_id:
2176
+ if self._settings.run_id:
2177
+ assert self._wl
2178
+ service = self._wl.assert_service()
2138
2179
  service.inform_finish(run_id=self._settings.run_id)
2139
2180
 
2140
2181
  finally:
@@ -2313,7 +2354,7 @@ class Run:
2313
2354
  self._err_redir = err_redir
2314
2355
  logger.info("Redirects installed.")
2315
2356
  except Exception as e:
2316
- print(e)
2357
+ wandb.termwarn(f"Failed to redirect: {e}")
2317
2358
  logger.error("Failed to redirect.", exc_info=e)
2318
2359
  return
2319
2360
 
@@ -2374,8 +2415,7 @@ class Run:
2374
2415
  logger.info("atexit reg")
2375
2416
  self._hooks = ExitHooks()
2376
2417
 
2377
- service = self._wl and self._wl.service
2378
- if not service:
2418
+ if self.settings.x_disable_service:
2379
2419
  self._hooks.hook()
2380
2420
  # NB: manager will perform atexit hook like behavior for outstanding runs
2381
2421
  atexit.register(lambda: self._atexit_cleanup())
@@ -2388,10 +2428,6 @@ class Run:
2388
2428
  self._output_writer.close()
2389
2429
  self._output_writer = None
2390
2430
 
2391
- def _on_init(self) -> None:
2392
- if self._settings._offline:
2393
- return
2394
-
2395
2431
  def _on_start(self) -> None:
2396
2432
  # would like to move _set_global to _on_ready to unify _on_start and _on_attach
2397
2433
  # (we want to do the set globals after attach)
@@ -3231,7 +3267,7 @@ class Run:
3231
3267
  use_after_commit: bool = False,
3232
3268
  ) -> Artifact:
3233
3269
  api = internal.Api()
3234
- if api.settings().get("anonymous") == "true":
3270
+ if api.settings().get("anonymous") in ["allow", "must"]:
3235
3271
  wandb.termwarn(
3236
3272
  "Artifacts logged anonymously cannot be claimed and expire after 7 days."
3237
3273
  )
@@ -3665,6 +3701,62 @@ class Run:
3665
3701
  logger.error("Error getting system metrics: %s", e)
3666
3702
  return {}
3667
3703
 
3704
+ @property
3705
+ @_run_decorator._attach
3706
+ @_run_decorator._noop_on_finish()
3707
+ def _metadata(self) -> Metadata | None:
3708
+ """The metadata associated with this run.
3709
+
3710
+ NOTE: Automatically collected metadata can be overridden by the user.
3711
+ """
3712
+ if not self._backend or not self._backend.interface:
3713
+ return self.__metadata
3714
+
3715
+ # Initialize the metadata object if it doesn't exist.
3716
+ if self.__metadata is None:
3717
+ self.__metadata = Metadata()
3718
+ self.__metadata._set_callback(self._metadata_callback)
3719
+
3720
+ handle = self._backend.interface.deliver_get_system_metadata()
3721
+ result = handle.wait(timeout=1)
3722
+
3723
+ if not result:
3724
+ logger.error("Error getting run metadata: no result")
3725
+ return None
3726
+
3727
+ try:
3728
+ response = result.response.get_system_metadata_response
3729
+
3730
+ # Temporarily disable the callback to prevent triggering
3731
+ # an update call to wandb-core with the callback.
3732
+ with self.__metadata.disable_callback():
3733
+ # Values stored in the metadata object take precedence.
3734
+ self.__metadata.update_from_proto(response.metadata, skip_existing=True)
3735
+
3736
+ return self.__metadata
3737
+ except Exception as e:
3738
+ logger.error("Error getting run metadata: %s", e)
3739
+
3740
+ return None
3741
+
3742
+ @_run_decorator._noop_on_finish()
3743
+ @_run_decorator._attach
3744
+ def _metadata_callback(
3745
+ self,
3746
+ metadata: MetadataRequest,
3747
+ ) -> None:
3748
+ """Callback to publish Metadata to wandb-core upon user updates."""
3749
+ # ignore updates if the attached to another run
3750
+ if self._is_attached:
3751
+ wandb.termwarn(
3752
+ "Metadata updates are ignored when attached to another run.",
3753
+ repeat=False,
3754
+ )
3755
+ return
3756
+
3757
+ if self._backend and self._backend.interface:
3758
+ self._backend.interface.publish_metadata(metadata)
3759
+
3668
3760
  # ------------------------------------------------------------------------------
3669
3761
  # HEADER
3670
3762
  # ------------------------------------------------------------------------------
@@ -3772,8 +3864,8 @@ class Run:
3772
3864
  f'{printer.emoji("rocket")} View run at {printer.link(run_url)}',
3773
3865
  )
3774
3866
 
3775
- # TODO(settings) use `wandb_settings` (if self.settings.anonymous == "true":)
3776
- if run_name and Api().api.settings().get("anonymous") == "true":
3867
+ # TODO(settings) use `wandb_settings` (if self.settings.anonymous in ["allow", "must"]:)
3868
+ if run_name and Api().api.settings().get("anonymous") in ["allow", "must"]:
3777
3869
  printer.display(
3778
3870
  (
3779
3871
  "Do NOT share these links with anyone."
@@ -349,7 +349,7 @@ class Settings(BaseModel, validate_assignment=True):
349
349
  @model_validator(mode="before")
350
350
  @classmethod
351
351
  def catch_private_settings(cls, values):
352
- """Check if a private field is provided and assign to the corrsponding public one.
352
+ """Check if a private field is provided and assign to the corresponding public one.
353
353
 
354
354
  This is a compatibility layer to handle previous versions of the settings.
355
355
  """
@@ -961,7 +961,7 @@ class Settings(BaseModel, validate_assignment=True):
961
961
  # The Settings class does not track the source of the settings,
962
962
  # so it is up to the developer to ensure that the settings are applied
963
963
  # in the correct order. Most of the updates are done in
964
- # wandb/sdk/wandb_setup.py::_WandbSetup__WandbSetup._settings_setup.
964
+ # wandb/sdk/wandb_setup.py::_WandbSetup._settings_setup.
965
965
 
966
966
  def update_from_system_config_file(self):
967
967
  """Update settings from the system config file."""
@@ -1063,7 +1063,8 @@ class Settings(BaseModel, validate_assignment=True):
1063
1063
  )
1064
1064
  self.x_executable = _executable
1065
1065
 
1066
- self.docker = env.get_docker(util.image_id_from_k8s())
1066
+ if self.docker is None:
1067
+ self.docker = env.get_docker(util.image_id_from_k8s())
1067
1068
 
1068
1069
  # proceed if not in CLI mode
1069
1070
  if self.x_cli_only_mode:
@@ -1176,38 +1177,6 @@ class Settings(BaseModel, validate_assignment=True):
1176
1177
  with open(self.resume_fname, "w") as f:
1177
1178
  f.write(json.dumps({"run_id": self.run_id}))
1178
1179
 
1179
- def handle_sweep_logic(self):
1180
- """Update settings based on sweep context.
1181
-
1182
- When running a sweep, the project, entity, and run_id are handled externally,
1183
- and should be ignored if they are set.
1184
- """
1185
- if self.sweep_id is None:
1186
- return
1187
-
1188
- for key in ("project", "entity", "run_id"):
1189
- value = getattr(self, key)
1190
- if value is not None:
1191
- wandb.termwarn(f"Ignoring {key} {value!r} when running a sweep.")
1192
- setattr(self, key, None)
1193
-
1194
- def handle_launch_logic(self):
1195
- """Update settings based on launch context.
1196
-
1197
- When running in a launch context, the project, entity, and run_id are handled
1198
- externally, and should be ignored if they are set.
1199
- """
1200
- if not self.launch:
1201
- return
1202
-
1203
- for key in ("project", "entity", "run_id"):
1204
- value = getattr(self, key)
1205
- if value is not None:
1206
- wandb.termwarn(
1207
- f"Ignoring {key} {value!r} when running from wandb launch context."
1208
- )
1209
- setattr(self, key, None)
1210
-
1211
1180
  @staticmethod
1212
1181
  def validate_url(url: str) -> None:
1213
1182
  """Validate a URL string."""
@@ -1293,7 +1262,7 @@ class Settings(BaseModel, validate_assignment=True):
1293
1262
  def _get_url_query_string(self) -> str:
1294
1263
  """Construct the query string for project, run, and sweep URLs."""
1295
1264
  # TODO: remove dependency on Api()
1296
- if Api().settings().get("anonymous") != "true":
1265
+ if Api().settings().get("anonymous") not in ["allow", "must"]:
1297
1266
  return ""
1298
1267
 
1299
1268
  api_key = apikey.api_key(settings=self)
wandb/sdk/wandb_setup.py CHANGED
@@ -20,6 +20,7 @@ import threading
20
20
  from typing import TYPE_CHECKING, Any, Union
21
21
 
22
22
  import wandb
23
+ import wandb.integration.sagemaker as sagemaker
23
24
  from wandb.sdk.lib import import_hooks
24
25
 
25
26
  from . import wandb_settings
@@ -61,29 +62,19 @@ class _EarlyLogger:
61
62
  def log(self, level: str, msg: str, *args: Any, **kwargs: Any) -> None:
62
63
  self._log.append((level, msg, args, kwargs))
63
64
 
64
- def _flush(self) -> None:
65
- assert self is not logger
66
- assert logger is not None
65
+ def _flush(self, new_logger: Logger) -> None:
66
+ assert self is not new_logger
67
67
  for level, msg, args, kwargs in self._log:
68
- logger.log(level, msg, *args, **kwargs)
68
+ new_logger.log(level, msg, *args, **kwargs)
69
69
  for msg, args, kwargs in self._exception:
70
- logger.exception(msg, *args, **kwargs)
70
+ new_logger.exception(msg, *args, **kwargs)
71
71
 
72
72
 
73
73
  Logger = Union[logging.Logger, _EarlyLogger]
74
74
 
75
- # logger will be configured to be either a standard logger instance or _EarlyLogger
76
- logger: Logger | None = None
77
75
 
78
-
79
- def _set_logger(log_object: Logger) -> None:
80
- """Configure module logger."""
81
- global logger
82
- logger = log_object
83
-
84
-
85
- class _WandbSetup__WandbSetup: # noqa: N801
86
- """Inner class of _WandbSetup."""
76
+ class _WandbSetup:
77
+ """W&B library singleton."""
87
78
 
88
79
  def __init__(
89
80
  self,
@@ -104,48 +95,52 @@ class _WandbSetup__WandbSetup: # noqa: N801
104
95
 
105
96
  # TODO(jhr): defer strict checks until settings are fully initialized
106
97
  # and logging is ready
107
- self._early_logger = _EarlyLogger()
108
- _set_logger(self._early_logger)
98
+ self._logger: Logger = _EarlyLogger()
109
99
 
110
- self._settings = self._settings_setup(settings, self._early_logger)
100
+ self._settings = self._settings_setup(settings)
111
101
 
112
- wandb.termsetup(self._settings, logger)
102
+ wandb.termsetup(self._settings, None)
113
103
 
114
104
  self._check()
115
105
  self._setup()
116
106
 
117
107
  def _settings_setup(
118
108
  self,
119
- settings: Settings | None = None,
120
- early_logger: _EarlyLogger | None = None,
109
+ settings: Settings | None,
121
110
  ) -> wandb_settings.Settings:
122
111
  s = wandb_settings.Settings()
123
112
 
124
113
  # the pid of the process to monitor for system stats
125
114
  pid = os.getpid()
126
- if early_logger:
127
- early_logger.info(f"Current SDK version is {wandb.__version__}")
128
- early_logger.info(f"Configure stats pid to {pid}")
115
+ self._logger.info(f"Current SDK version is {wandb.__version__}")
116
+ self._logger.info(f"Configure stats pid to {pid}")
129
117
  s.x_stats_pid = pid
130
118
 
131
119
  # load settings from the system config
132
- if s.settings_system and early_logger:
133
- early_logger.info(f"Loading settings from {s.settings_system}")
120
+ if s.settings_system:
121
+ self._logger.info(f"Loading settings from {s.settings_system}")
134
122
  s.update_from_system_config_file()
135
123
 
136
124
  # load settings from the workspace config
137
- if s.settings_workspace and early_logger:
138
- early_logger.info(f"Loading settings from {s.settings_workspace}")
125
+ if s.settings_workspace:
126
+ self._logger.info(f"Loading settings from {s.settings_workspace}")
139
127
  s.update_from_workspace_config_file()
140
128
 
141
129
  # load settings from the environment variables
142
- if early_logger:
143
- early_logger.info("Loading settings from environment variables")
130
+ self._logger.info("Loading settings from environment variables")
144
131
  s.update_from_env_vars(self._environ)
145
132
 
146
133
  # infer settings from the system environment
147
134
  s.update_from_system_environment()
148
135
 
136
+ # load SageMaker settings
137
+ check_sagemaker_env = not s.sagemaker_disable
138
+ if settings and settings.sagemaker_disable:
139
+ check_sagemaker_env = False
140
+ if check_sagemaker_env and sagemaker.is_using_sagemaker():
141
+ self._logger.info("Loading SageMaker settings")
142
+ sagemaker.set_global_settings(s)
143
+
149
144
  # load settings from the passed init/setup settings
150
145
  if settings:
151
146
  s.update_from_settings(settings)
@@ -165,13 +160,15 @@ class _WandbSetup__WandbSetup: # noqa: N801
165
160
  self._settings.update_from_dict(user_settings)
166
161
 
167
162
  def _early_logger_flush(self, new_logger: Logger) -> None:
168
- if not self._early_logger:
163
+ if self._logger is new_logger:
169
164
  return
170
- _set_logger(new_logger)
171
- self._early_logger._flush()
172
165
 
173
- def _get_logger(self) -> Logger | None:
174
- return logger
166
+ if isinstance(self._logger, _EarlyLogger):
167
+ self._logger._flush(new_logger)
168
+ self._logger = new_logger
169
+
170
+ def _get_logger(self) -> Logger:
171
+ return self._logger
175
172
 
176
173
  @property
177
174
  def settings(self) -> wandb_settings.Settings:
@@ -236,16 +233,11 @@ class _WandbSetup__WandbSetup: # noqa: N801
236
233
  if threading.current_thread() is not threading.main_thread():
237
234
  pass
238
235
  elif threading.current_thread().name != "MainThread":
239
- print("bad thread2", threading.current_thread().name)
236
+ wandb.termwarn(f"bad thread2: {threading.current_thread().name}")
240
237
  if getattr(sys, "frozen", False):
241
- print("frozen, could be trouble")
238
+ wandb.termwarn("frozen, could be trouble")
242
239
 
243
240
  def _setup(self) -> None:
244
- if not self._settings._noop and not self._settings.x_disable_service:
245
- from wandb.sdk.lib import service_connection
246
-
247
- self._connection = service_connection.connect_to_service(self._settings)
248
-
249
241
  sweep_path = self._settings.sweep_param_path
250
242
  if sweep_path:
251
243
  self._sweep_config = config_util.dict_from_config_file(
@@ -278,53 +270,61 @@ class _WandbSetup__WandbSetup: # noqa: N801
278
270
  if internal_exit_code != 0:
279
271
  sys.exit(internal_exit_code)
280
272
 
281
- @property
282
- def service(self) -> ServiceConnection | None:
283
- """Returns a connection to the service process, if it exists."""
273
+ def ensure_service(self) -> ServiceConnection:
274
+ """Returns a connection to the service process creating it if needed."""
275
+ if self._connection:
276
+ return self._connection
277
+
278
+ from wandb.sdk.lib import service_connection
279
+
280
+ self._connection = service_connection.connect_to_service(self._settings)
284
281
  return self._connection
285
282
 
283
+ def assert_service(self) -> ServiceConnection:
284
+ """Returns a connection to the service process, asserting it exists.
286
285
 
287
- class _WandbSetup:
288
- """Wandb singleton class.
286
+ Unlike ensure_service(), this will not start up a service process
287
+ if it didn't already exist.
288
+ """
289
+ if not self._connection:
290
+ raise AssertionError("Expected service process to exist.")
289
291
 
290
- Note: This is a process local singleton.
291
- (Forked processes will get a new copy of the object)
292
- """
292
+ return self._connection
293
293
 
294
- _instance: _WandbSetup__WandbSetup | None = None
295
294
 
296
- def __init__(self, settings: Settings | None = None) -> None:
297
- pid = os.getpid()
298
- if _WandbSetup._instance and _WandbSetup._instance._pid == pid:
299
- _WandbSetup._instance._update(settings=settings)
300
- return
301
- _WandbSetup._instance = _WandbSetup__WandbSetup(settings=settings, pid=pid)
295
+ _singleton: _WandbSetup | None = None
296
+ """The W&B library singleton, or None if not yet set up.
302
297
 
303
- @property
304
- def service(self) -> ServiceConnection | None:
305
- """Returns a connection to the service process, if it exists."""
306
- if not self._instance:
307
- return None
308
- return self._instance.service
298
+ The value is invalid and must not be used if `os.getpid() != _singleton._pid`.
299
+ """
309
300
 
310
- def __getattr__(self, name: str) -> Any:
311
- return getattr(self._instance, name)
312
301
 
302
+ def singleton() -> _WandbSetup | None:
303
+ """Returns the W&B singleton if it exists for the current process.
313
304
 
314
- def _setup(
315
- settings: Settings | None = None,
316
- _reset: bool = False,
317
- ) -> _WandbSetup | None:
318
- """Set up library context."""
319
- if _reset:
320
- teardown()
305
+ Unlike setup(), this does not create the singleton if it doesn't exist.
306
+ """
307
+ if _singleton and _singleton._pid == os.getpid():
308
+ return _singleton
309
+ else:
321
310
  return None
322
311
 
323
- wl = _WandbSetup(settings=settings)
324
- return wl
325
312
 
313
+ def _setup(settings: Settings | None = None) -> _WandbSetup:
314
+ """Set up library context."""
315
+ global _singleton
316
+
317
+ pid = os.getpid()
326
318
 
327
- def setup(settings: Settings | None = None) -> _WandbSetup | None:
319
+ if _singleton and _singleton._pid == pid:
320
+ _singleton._update(settings=settings)
321
+ return _singleton
322
+ else:
323
+ _singleton = _WandbSetup(settings=settings, pid=pid)
324
+ return _singleton
325
+
326
+
327
+ def setup(settings: Settings | None = None) -> _WandbSetup:
328
328
  """Prepares W&B for use in the current process and its children.
329
329
 
330
330
  You can usually ignore this as it is implicitly called by `wandb.init()`.
@@ -380,8 +380,7 @@ def setup(settings: Settings | None = None) -> _WandbSetup | None:
380
380
  wandb.teardown()
381
381
  ```
382
382
  """
383
- ret = _setup(settings=settings)
384
- return ret
383
+ return _setup(settings=settings)
385
384
 
386
385
 
387
386
  def teardown(exit_code: int | None = None) -> None:
@@ -395,8 +394,10 @@ def teardown(exit_code: int | None = None) -> None:
395
394
  in an `atexit` hook, but this is not reliable in certain setups
396
395
  such as when using Python's `multiprocessing` module.
397
396
  """
398
- setup_instance = _WandbSetup._instance
399
- _WandbSetup._instance = None
397
+ global _singleton
398
+
399
+ orig_singleton = _singleton
400
+ _singleton = None
400
401
 
401
- if setup_instance:
402
- setup_instance._teardown(exit_code=exit_code)
402
+ if orig_singleton:
403
+ orig_singleton._teardown(exit_code=exit_code)
wandb/sdk/wandb_sweep.py CHANGED
@@ -85,10 +85,10 @@ def sweep(
85
85
  api = InternalApi()
86
86
  sweep_id, warnings = api.upsert_sweep(sweep, prior_runs=prior_runs)
87
87
  handle_sweep_config_violations(warnings)
88
- print("Create sweep with ID:", sweep_id)
88
+ print("Create sweep with ID:", sweep_id) # noqa: T201
89
89
  sweep_url = _get_sweep_url(api, sweep_id)
90
90
  if sweep_url:
91
- print("Sweep URL:", sweep_url)
91
+ print("Sweep URL:", sweep_url) # noqa: T201
92
92
  return sweep_id
93
93
 
94
94