wandb 0.17.6__py3-none-any.whl → 0.17.8__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. package_readme.md +47 -53
  2. wandb/__init__.py +10 -19
  3. wandb/__init__.pyi +964 -0
  4. wandb/agents/pyagent.py +1 -2
  5. wandb/bin/nvidia_gpu_stats +0 -0
  6. wandb/cli/cli.py +21 -0
  7. wandb/data_types.py +4 -3
  8. wandb/env.py +13 -0
  9. wandb/integration/keras/__init__.py +2 -5
  10. wandb/integration/keras/callbacks/metrics_logger.py +10 -4
  11. wandb/integration/keras/callbacks/model_checkpoint.py +0 -5
  12. wandb/integration/keras/keras.py +11 -0
  13. wandb/integration/kfp/wandb_logging.py +1 -1
  14. wandb/integration/lightning/fabric/logger.py +1 -1
  15. wandb/integration/openai/fine_tuning.py +13 -5
  16. wandb/integration/ultralytics/pose_utils.py +0 -1
  17. wandb/proto/v3/wandb_internal_pb2.py +24 -24
  18. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  19. wandb/proto/v3/wandb_telemetry_pb2.py +12 -12
  20. wandb/proto/v4/wandb_internal_pb2.py +24 -24
  21. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  22. wandb/proto/v4/wandb_telemetry_pb2.py +12 -12
  23. wandb/proto/v5/wandb_internal_pb2.py +24 -24
  24. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  25. wandb/proto/v5/wandb_telemetry_pb2.py +12 -12
  26. wandb/proto/wandb_deprecated.py +2 -0
  27. wandb/sdk/artifacts/artifact.py +22 -26
  28. wandb/sdk/artifacts/artifact_manifest_entry.py +10 -2
  29. wandb/sdk/artifacts/storage_handlers/gcs_handler.py +31 -0
  30. wandb/sdk/data_types/_dtypes.py +5 -5
  31. wandb/sdk/data_types/base_types/media.py +3 -1
  32. wandb/sdk/data_types/helper_types/bounding_boxes_2d.py +3 -1
  33. wandb/sdk/data_types/helper_types/image_mask.py +3 -1
  34. wandb/sdk/data_types/image.py +3 -1
  35. wandb/sdk/data_types/saved_model.py +3 -1
  36. wandb/sdk/data_types/video.py +2 -2
  37. wandb/sdk/interface/interface.py +17 -16
  38. wandb/sdk/interface/interface_shared.py +6 -9
  39. wandb/sdk/internal/datastore.py +1 -1
  40. wandb/sdk/internal/handler.py +5 -3
  41. wandb/sdk/internal/internal.py +1 -1
  42. wandb/sdk/internal/job_builder.py +5 -2
  43. wandb/sdk/internal/tb_watcher.py +2 -2
  44. wandb/sdk/internal/update.py +2 -2
  45. wandb/sdk/launch/builder/kaniko_builder.py +13 -5
  46. wandb/sdk/launch/create_job.py +2 -0
  47. wandb/sdk/lib/_settings_toposort_generated.py +1 -0
  48. wandb/sdk/lib/apikey.py +1 -1
  49. wandb/sdk/service/service.py +7 -2
  50. wandb/sdk/service/streams.py +2 -4
  51. wandb/sdk/wandb_config.py +4 -1
  52. wandb/sdk/wandb_init.py +59 -8
  53. wandb/sdk/wandb_manager.py +0 -3
  54. wandb/sdk/wandb_require.py +22 -1
  55. wandb/sdk/wandb_run.py +137 -92
  56. wandb/sdk/wandb_settings.py +26 -2
  57. wandb/sdk/wandb_setup.py +69 -3
  58. wandb/sdk/wandb_sweep.py +5 -2
  59. wandb/testing/relay.py +7 -1
  60. {wandb-0.17.6.dist-info → wandb-0.17.8.dist-info}/METADATA +48 -54
  61. {wandb-0.17.6.dist-info → wandb-0.17.8.dist-info}/RECORD +64 -63
  62. {wandb-0.17.6.dist-info → wandb-0.17.8.dist-info}/WHEEL +0 -0
  63. {wandb-0.17.6.dist-info → wandb-0.17.8.dist-info}/entry_points.txt +0 -0
  64. {wandb-0.17.6.dist-info → wandb-0.17.8.dist-info}/licenses/LICENSE +0 -0
@@ -56,7 +56,7 @@ class TypeRegistry:
56
56
  # but will be ultimately treated as a None. Ignoring type since
57
57
  # mypy does not trust that py_obj is a float by the time it is
58
58
  # passed to isnan.
59
- if py_obj.__class__ == float and math.isnan(py_obj): # type: ignore
59
+ if py_obj.__class__ is float and math.isnan(py_obj): # type: ignore
60
60
  return NoneType()
61
61
 
62
62
  # TODO: generalize this to handle other config input types
@@ -134,7 +134,7 @@ def _params_obj_to_json_obj(
134
134
  artifact: t.Optional["Artifact"] = None,
135
135
  ) -> t.Any:
136
136
  """Helper method."""
137
- if params_obj.__class__ == dict:
137
+ if params_obj.__class__ is dict:
138
138
  return {
139
139
  key: _params_obj_to_json_obj(params_obj[key], artifact)
140
140
  for key in params_obj
@@ -151,7 +151,7 @@ def _json_obj_to_params_obj(
151
151
  json_obj: t.Any, artifact: t.Optional["Artifact"] = None
152
152
  ) -> t.Any:
153
153
  """Helper method."""
154
- if json_obj.__class__ == dict:
154
+ if json_obj.__class__ is dict:
155
155
  if "wb_type" in json_obj:
156
156
  return TypeRegistry.type_from_dict(json_obj, artifact)
157
157
  else:
@@ -159,7 +159,7 @@ def _json_obj_to_params_obj(
159
159
  key: _json_obj_to_params_obj(json_obj[key], artifact)
160
160
  for key in json_obj
161
161
  }
162
- elif json_obj.__class__ == list:
162
+ elif json_obj.__class__ is list:
163
163
  return [_json_obj_to_params_obj(item, artifact) for item in json_obj]
164
164
  else:
165
165
  return json_obj
@@ -533,7 +533,7 @@ class UnionType(Type):
533
533
  self,
534
534
  allowed_types: t.Optional[t.Sequence[ConvertibleToType]] = None,
535
535
  ):
536
- assert allowed_types is None or (allowed_types.__class__ == list)
536
+ assert allowed_types is None or (allowed_types.__class__ is list)
537
537
  if allowed_types is None:
538
538
  wb_types = []
539
539
  else:
@@ -159,9 +159,11 @@ class Media(WBValue):
159
159
  # into Media itself we should get rid of them
160
160
  from wandb import Image
161
161
  from wandb.data_types import Audio
162
+ from wandb.sdk.wandb_run import Run
162
163
 
163
164
  json_obj = {}
164
- if isinstance(run, wandb.wandb_sdk.wandb_run.Run):
165
+
166
+ if isinstance(run, Run):
165
167
  json_obj.update(
166
168
  {
167
169
  "_type": "file", # TODO(adrian): This isn't (yet) a real media type we support on the frontend.
@@ -276,7 +276,9 @@ class BoundingBoxes2D(JSONMetadata):
276
276
  return True
277
277
 
278
278
  def to_json(self, run_or_artifact: Union["LocalRun", "Artifact"]) -> dict:
279
- if isinstance(run_or_artifact, wandb.wandb_sdk.wandb_run.Run):
279
+ from wandb.sdk.wandb_run import Run
280
+
281
+ if isinstance(run_or_artifact, Run):
280
282
  return super().to_json(run_or_artifact)
281
283
  elif isinstance(run_or_artifact, wandb.Artifact):
282
284
  # TODO (tim): I would like to log out a proper dictionary representing this object, but don't
@@ -191,9 +191,11 @@ class ImageMask(Media):
191
191
  )
192
192
 
193
193
  def to_json(self, run_or_artifact: Union["LocalRun", "Artifact"]) -> dict:
194
+ from wandb.sdk.wandb_run import Run
195
+
194
196
  json_dict = super().to_json(run_or_artifact)
195
197
 
196
- if isinstance(run_or_artifact, wandb.wandb_sdk.wandb_run.Run):
198
+ if isinstance(run_or_artifact, Run):
197
199
  json_dict["_type"] = self.type_name()
198
200
  return json_dict
199
201
  elif isinstance(run_or_artifact, wandb.Artifact):
@@ -418,6 +418,8 @@ class Image(BatchableMedia):
418
418
  )
419
419
 
420
420
  def to_json(self, run_or_artifact: Union["LocalRun", "Artifact"]) -> dict:
421
+ from wandb.sdk.wandb_run import Run
422
+
421
423
  json_dict = super().to_json(run_or_artifact)
422
424
  json_dict["_type"] = Image._log_type
423
425
  json_dict["format"] = self.format
@@ -456,7 +458,7 @@ class Image(BatchableMedia):
456
458
  "digest": classes_entry.digest,
457
459
  }
458
460
 
459
- elif not isinstance(run_or_artifact, wandb.wandb_sdk.wandb_run.Run):
461
+ elif not isinstance(run_or_artifact, Run):
460
462
  raise ValueError("to_json accepts wandb_run.Run or wandb_artifact.Artifact")
461
463
 
462
464
  if self._boxes:
@@ -148,7 +148,9 @@ class _SavedModel(WBValue, Generic[SavedModelObjType]):
148
148
  # bit of tech debt in the other data types which requires the input to `to_json`
149
149
  # to accept a Run or Artifact. However, Run additions should be deprecated in the future.
150
150
  # This check helps ensure we do not add to the debt.
151
- if isinstance(run_or_artifact, wandb.wandb_sdk.wandb_run.Run):
151
+ from wandb.sdk.wandb_run import Run
152
+
153
+ if isinstance(run_or_artifact, Run):
152
154
  raise ValueError("SavedModel cannot be added to run - must use artifact")
153
155
  artifact = run_or_artifact
154
156
  json_obj = {
@@ -214,9 +214,9 @@ class Video(BatchableMedia):
214
214
  n_rows = 2 ** ((b.bit_length() - 1) // 2)
215
215
  n_cols = video.shape[0] // n_rows
216
216
 
217
- video = np.reshape(video, newshape=(n_rows, n_cols, t, c, h, w))
217
+ video = video.reshape(n_rows, n_cols, t, c, h, w)
218
218
  video = np.transpose(video, axes=(2, 0, 4, 1, 5, 3))
219
- video = np.reshape(video, newshape=(t, n_rows * h, n_cols * w, c))
219
+ video = video.reshape(t, n_rows * h, n_cols * w, c)
220
220
  return video
221
221
 
222
222
  @classmethod
@@ -54,18 +54,20 @@ MANIFEST_FILE_SIZE_THRESHOLD = 100_000
54
54
 
55
55
  GlobStr = NewType("GlobStr", str)
56
56
 
57
- if TYPE_CHECKING:
58
- from ..wandb_run import Run
57
+ if sys.version_info >= (3, 8):
58
+ from typing import Literal, TypedDict
59
+ else:
60
+ from typing_extensions import Literal, TypedDict
61
+
62
+ PolicyName = Literal["now", "live", "end"]
59
63
 
60
- if sys.version_info >= (3, 8):
61
- from typing import Literal, TypedDict
62
- else:
63
- from typing_extensions import Literal, TypedDict
64
64
 
65
- PolicyName = Literal["now", "live", "end"]
65
+ class FilesDict(TypedDict):
66
+ files: Iterable[Tuple[GlobStr, PolicyName]]
66
67
 
67
- class FilesDict(TypedDict):
68
- files: Iterable[Tuple[GlobStr, PolicyName]]
68
+
69
+ if TYPE_CHECKING:
70
+ from ..wandb_run import Run
69
71
 
70
72
 
71
73
  logger = logging.getLogger("wandb")
@@ -112,15 +114,14 @@ class InterfaceBase:
112
114
  def _publish_header(self, header: pb.HeaderRecord) -> None:
113
115
  raise NotImplementedError
114
116
 
115
- def communicate_status(self) -> Optional[pb.StatusResponse]:
116
- status = pb.StatusRequest()
117
- resp = self._communicate_status(status)
118
- return resp
117
+ def deliver_status(self) -> MailboxHandle:
118
+ return self._deliver_status(pb.StatusRequest())
119
119
 
120
120
  @abstractmethod
121
- def _communicate_status(
122
- self, status: pb.StatusRequest
123
- ) -> Optional[pb.StatusResponse]:
121
+ def _deliver_status(
122
+ self,
123
+ status: pb.StatusRequest,
124
+ ) -> MailboxHandle:
124
125
  raise NotImplementedError
125
126
 
126
127
  def _make_config(
@@ -299,7 +299,7 @@ class InterfaceShared(InterfaceBase):
299
299
  raise NotImplementedError
300
300
 
301
301
  def _communicate(
302
- self, rec: pb.Record, timeout: Optional[int] = 5, local: Optional[bool] = None
302
+ self, rec: pb.Record, timeout: Optional[int] = 30, local: Optional[bool] = None
303
303
  ) -> Optional[pb.Result]:
304
304
  return self._communicate_async(rec, local=local).get(timeout=timeout)
305
305
 
@@ -421,15 +421,12 @@ class InterfaceShared(InterfaceBase):
421
421
  rec = self._make_record(alert=proto_alert)
422
422
  self._publish(rec)
423
423
 
424
- def _communicate_status(
425
- self, status: pb.StatusRequest
426
- ) -> Optional[pb.StatusResponse]:
424
+ def _deliver_status(
425
+ self,
426
+ status: pb.StatusRequest,
427
+ ) -> MailboxHandle:
427
428
  req = self._make_request(status=status)
428
- resp = self._communicate(req, local=True)
429
- if resp is None:
430
- return None
431
- assert resp.response.status_response
432
- return resp.response.status_response
429
+ return self._deliver_record(req)
433
430
 
434
431
  def _publish_exit(self, exit_data: pb.RunExitRecord) -> None:
435
432
  rec = self._make_record(exit=exit_data)
@@ -69,7 +69,7 @@ class DataStore:
69
69
 
70
70
  def __init__(self) -> None:
71
71
  self._opened_for_scan = False
72
- self._fp: Optional["IO[Any]"] = None
72
+ self._fp: Optional[IO[Any]] = None
73
73
  self._index = 0
74
74
  self._flush_offset = 0
75
75
  self._size_bytes = 0
@@ -660,7 +660,11 @@ class HandleManager:
660
660
  self._dispatch_record(record)
661
661
 
662
662
  def handle_request_check_version(self, record: Record) -> None:
663
- self._dispatch_record(record)
663
+ if self._settings._offline:
664
+ result = proto_util._result_from_record(record)
665
+ self._respond_result(result)
666
+ else:
667
+ self._dispatch_record(record)
664
668
 
665
669
  def handle_request_attach(self, record: Record) -> None:
666
670
  result = proto_util._result_from_record(record)
@@ -745,8 +749,6 @@ class HandleManager:
745
749
  self._respond_result(result)
746
750
 
747
751
  def handle_request_status(self, record: Record) -> None:
748
- # TODO(mempressure): do something better?
749
- assert record.control.req_resp
750
752
  result = proto_util._result_from_record(record)
751
753
  self._respond_result(result)
752
754
 
@@ -62,7 +62,7 @@ def wandb_internal(
62
62
 
63
63
  """
64
64
  # mark this process as internal
65
- wandb._set_internal_process()
65
+ wandb._set_internal_process() # type: ignore
66
66
  _setup_tracelog()
67
67
  started = time.time()
68
68
 
@@ -423,15 +423,18 @@ class JobBuilder:
423
423
  api: Api,
424
424
  build_context: Optional[str] = None,
425
425
  dockerfile: Optional[str] = None,
426
+ base_image: Optional[str] = None,
426
427
  ) -> Optional[Artifact]:
427
428
  """Build a job artifact from the current run.
428
429
 
429
430
  Arguments:
431
+ api (Api): The API object to use to create the job artifact.
430
432
  build_context (Optional[str]): Path within the job source code to
431
433
  the image build context. Saved as part of the job for future
432
434
  builds.
433
435
  dockerfile (Optional[str]): Path within the build context the
434
436
  Dockerfile. Saved as part of the job for future builds.
437
+ base_image (Optional[str]): The base image used to run the job code.
435
438
 
436
439
  Returns:
437
440
  Optional[Artifact]: The job artifact if it was successfully built,
@@ -467,8 +470,6 @@ class JobBuilder:
467
470
  "warn",
468
471
  )
469
472
  return None
470
- metadata["dockerfile"] = dockerfile
471
- metadata["build_context"] = build_context
472
473
 
473
474
  runtime: Optional[str] = metadata.get("python")
474
475
  # can't build a job without a python version
@@ -520,6 +521,8 @@ class JobBuilder:
520
521
  source["build_context"] = build_context # type: ignore[typeddict-item]
521
522
  if dockerfile:
522
523
  source["dockerfile"] = dockerfile # type: ignore[typeddict-item]
524
+ if base_image:
525
+ source["base_image"] = base_image # type: ignore[typeddict-item]
523
526
 
524
527
  # Pop any keys that are initialized to None. The current TypedDict
525
528
  # system for source dicts requires all keys to be present, but we
@@ -123,7 +123,7 @@ class TBWatcher:
123
123
  self._force = force
124
124
  # TODO(jhr): do we need locking in this queue?
125
125
  self._watcher_queue = queue.PriorityQueue()
126
- wandb.tensorboard.reset_state()
126
+ wandb.tensorboard.reset_state() # type: ignore
127
127
 
128
128
  def _calculate_namespace(self, logdir: str, rootdir: str) -> Optional[str]:
129
129
  namespace: Optional[str]
@@ -430,7 +430,7 @@ class TBEventConsumer:
430
430
  def _handle_event(
431
431
  self, event: "ProtoEvent", history: Optional["TBHistory"] = None
432
432
  ) -> None:
433
- wandb.tensorboard._log(
433
+ wandb.tensorboard._log( # type: ignore
434
434
  event.event,
435
435
  step=event.event.step,
436
436
  namespace=event.namespace,
@@ -10,7 +10,7 @@ def _find_available(
10
10
  ) -> Optional[Tuple[str, bool, bool, bool, Optional[str]]]:
11
11
  from wandb.util import parse_version
12
12
 
13
- pypi_url = f"https://pypi.org/pypi/{wandb._wandb_module}/json"
13
+ pypi_url = "https://pypi.org/pypi/wandb/json"
14
14
 
15
15
  yanked_dict = {}
16
16
  try:
@@ -78,7 +78,7 @@ def check_available(current_version: str) -> Optional[Dict[str, Optional[str]]]:
78
78
  if not package_info:
79
79
  return None
80
80
 
81
- wandb_module_name = wandb._wandb_module
81
+ wandb_module_name = "wandb"
82
82
 
83
83
  latest_version, pip_prerelease, deleted, yanked, yanked_reason = package_info
84
84
  upgrade_message = (
@@ -63,6 +63,13 @@ else:
63
63
  NAMESPACE = "wandb"
64
64
 
65
65
 
66
+ def get_pod_name_safe(job: client.V1Job):
67
+ try:
68
+ return job.spec.template.metadata.name
69
+ except AttributeError:
70
+ return None
71
+
72
+
66
73
  async def _wait_for_completion(
67
74
  batch_client: client.BatchV1Api, job_name: str, deadline_secs: Optional[int] = None
68
75
  ) -> bool:
@@ -319,17 +326,18 @@ class KanikoBuilder(AbstractBuilder):
319
326
  await self._create_docker_ecr_config_map(
320
327
  build_job_name, core_v1, repo_uri
321
328
  )
322
- await batch_v1.create_namespaced_job(NAMESPACE, build_job)
323
-
329
+ k8s_job = await batch_v1.create_namespaced_job(NAMESPACE, build_job)
324
330
  # wait for double the job deadline since it might take time to schedule
325
331
  if not await _wait_for_completion(
326
332
  batch_v1, build_job_name, 3 * _DEFAULT_BUILD_TIMEOUT_SECS
327
333
  ):
328
334
  if job_tracker:
329
335
  job_tracker.set_err_stage("build")
330
- raise Exception(
331
- f"Failed to build image in kaniko for job {run_id}. View logs with `kubectl logs -n {NAMESPACE} {build_job_name}`."
332
- )
336
+ msg = f"Failed to build image in kaniko for job {run_id}."
337
+ pod_name = get_pod_name_safe(k8s_job)
338
+ if pod_name:
339
+ msg += f" View logs with `kubectl logs -n {NAMESPACE} {pod_name}`."
340
+ raise Exception(msg)
333
341
  try:
334
342
  pods_from_job = await core_v1.list_namespaced_pod(
335
343
  namespace=NAMESPACE, label_selector=f"job-name={build_job_name}"
@@ -114,6 +114,7 @@ def _create_job(
114
114
  git_hash: Optional[str] = None,
115
115
  build_context: Optional[str] = None,
116
116
  dockerfile: Optional[str] = None,
117
+ base_image: Optional[str] = None,
117
118
  ) -> Tuple[Optional[Artifact], str, List[str]]:
118
119
  wandb.termlog(f"Creating launch job of type: {job_type}...")
119
120
 
@@ -188,6 +189,7 @@ def _create_job(
188
189
  api.api,
189
190
  dockerfile=dockerfile,
190
191
  build_context=build_context,
192
+ base_image=base_image,
191
193
  )
192
194
  if not artifact:
193
195
  wandb.termerror("JobBuilder failed to build a job")
@@ -63,6 +63,7 @@ _Setting = Literal[
63
63
  "_python",
64
64
  "_runqueue_item_id",
65
65
  "_require_core",
66
+ "_require_legacy_service",
66
67
  "_save_requirements",
67
68
  "_service_transport",
68
69
  "_service_wait",
wandb/sdk/lib/apikey.py CHANGED
@@ -107,7 +107,7 @@ def prompt_api_key( # noqa: C901
107
107
 
108
108
  if jupyter and "google.colab" in sys.modules:
109
109
  log_string = term.LOG_STRING_NOCOLOR
110
- key = wandb.jupyter.attempt_colab_login(app_url)
110
+ key = wandb.jupyter.attempt_colab_login(app_url) # type: ignore
111
111
  if key is not None:
112
112
  write_key(settings, key, api=api)
113
113
  return key # type: ignore
@@ -15,7 +15,12 @@ import time
15
15
  from typing import TYPE_CHECKING, Any, Dict, Optional
16
16
 
17
17
  from wandb import _sentry, termlog
18
- from wandb.env import core_debug, core_error_reporting_enabled, is_require_core
18
+ from wandb.env import (
19
+ core_debug,
20
+ core_error_reporting_enabled,
21
+ is_require_core,
22
+ is_require_legacy_service,
23
+ )
19
24
  from wandb.errors import Error, WandbCoreNotAvailableError
20
25
  from wandb.sdk.lib.wburls import wburls
21
26
  from wandb.util import get_core_path, get_module
@@ -164,7 +169,7 @@ class _Service:
164
169
 
165
170
  service_args = []
166
171
 
167
- if is_require_core():
172
+ if is_require_core() and not is_require_legacy_service():
168
173
  try:
169
174
  core_path = get_core_path()
170
175
  except WandbCoreNotAvailableError as e:
@@ -81,9 +81,7 @@ class StreamRecord:
81
81
  self._wait_thread_active()
82
82
 
83
83
  def _wait_thread_active(self) -> None:
84
- result = self._iface.communicate_status()
85
- # TODO: using the default communicate timeout, is that enough? retries?
86
- assert result
84
+ self._iface.deliver_status().wait(timeout=-1)
87
85
 
88
86
  def join(self) -> None:
89
87
  self._iface.join()
@@ -212,7 +210,7 @@ class StreamMux:
212
210
  # run_id = action.stream_id # will want to fix if a streamid != runid
213
211
  settings = action._data
214
212
  thread = StreamThread(
215
- target=wandb.wandb_sdk.internal.internal.wandb_internal,
213
+ target=wandb.wandb_sdk.internal.internal.wandb_internal, # type: ignore
216
214
  kwargs=dict(
217
215
  settings=settings,
218
216
  record_q=stream._record_q,
wandb/sdk/wandb_config.py CHANGED
@@ -61,7 +61,7 @@ class Config:
61
61
 
62
62
  Using absl flags
63
63
  ```
64
- flags.DEFINE_string(model’, None, model to run) # name, default, help
64
+ flags.DEFINE_string("model", None, "model to run") # name, default, help
65
65
  wandb.config.update(flags.FLAGS) # adds all absl flags to config
66
66
  ```
67
67
 
@@ -129,6 +129,9 @@ class Config:
129
129
  def __getitem__(self, key):
130
130
  return self._items[key]
131
131
 
132
+ def __iter__(self):
133
+ return iter(self._items)
134
+
132
135
  def _check_locked(self, key, ignore_locked=False) -> bool:
133
136
  locked = self._locked.get(key)
134
137
  if locked is not None:
wandb/sdk/wandb_init.py CHANGED
@@ -15,6 +15,7 @@ import os
15
15
  import platform
16
16
  import sys
17
17
  import tempfile
18
+ import time
18
19
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union
19
20
 
20
21
  import wandb
@@ -174,7 +175,9 @@ class _WandbInit:
174
175
  # we add this logic to be backward compatible with the old behavior of disable
175
176
  # where it would disable the service if the mode was set to disabled
176
177
  mode = kwargs.get("mode")
177
- settings_mode = (kwargs.get("settings") or {}).get("mode")
178
+ settings_mode = (kwargs.get("settings") or {}).get("mode") or os.environ.get(
179
+ "WANDB_MODE"
180
+ )
178
181
  _disable_service = mode == "disabled" or settings_mode == "disabled"
179
182
  setup_settings = {"_disable_service": _disable_service}
180
183
 
@@ -262,7 +265,7 @@ class _WandbInit:
262
265
 
263
266
  monitor_gym = kwargs.pop("monitor_gym", None)
264
267
  if monitor_gym and len(wandb.patched["gym"]) == 0:
265
- wandb.gym.monitor()
268
+ wandb.gym.monitor() # type: ignore
266
269
 
267
270
  if wandb.patched["tensorboard"]:
268
271
  with telemetry.context(obj=self._init_telemetry_obj) as tel:
@@ -271,7 +274,7 @@ class _WandbInit:
271
274
  tensorboard = kwargs.pop("tensorboard", None)
272
275
  sync_tensorboard = kwargs.pop("sync_tensorboard", None)
273
276
  if tensorboard or sync_tensorboard and len(wandb.patched["tensorboard"]) == 0:
274
- wandb.tensorboard.patch()
277
+ wandb.tensorboard.patch() # type: ignore
275
278
  with telemetry.context(obj=self._init_telemetry_obj) as tel:
276
279
  tel.feature.tensorboard_sync = True
277
280
 
@@ -459,7 +462,7 @@ class _WandbInit:
459
462
 
460
463
  def _jupyter_setup(self, settings: Settings) -> None:
461
464
  """Add hooks, and session history saving."""
462
- self.notebook = wandb.jupyter.Notebook(settings)
465
+ self.notebook = wandb.jupyter.Notebook(settings) # type: ignore
463
466
  ipython = self.notebook.shell
464
467
 
465
468
  # Monkey patch ipython publish to capture displayed outputs
@@ -522,17 +525,62 @@ class _WandbInit:
522
525
  logger.info(f"Logging internal logs to {settings.log_internal}")
523
526
 
524
527
  def _make_run_disabled(self) -> Run:
528
+ """Returns a Run-like object where all methods are no-ops.
529
+
530
+ This method is used when wandb.init(mode="disabled") is called or WANDB_MODE=disabled
531
+ is set. It creates a Run object that mimics the behavior of a normal Run but doesn't
532
+ communicate with the W&B servers.
533
+
534
+ The returned Run object has all expected attributes and methods, but they are
535
+ no-op versions that don't perform any actual logging or communication.
536
+ """
525
537
  drun = Run(settings=Settings(mode="disabled", files_dir=tempfile.gettempdir()))
526
- drun._config = wandb.wandb_sdk.wandb_config.Config()
538
+ # config and summary objects
539
+ drun._config = wandb.sdk.wandb_config.Config()
527
540
  drun._config.update(self.sweep_config)
528
541
  drun._config.update(self.config)
529
542
  drun.summary = SummaryDisabled() # type: ignore
543
+ # methods
530
544
  drun.log = lambda data, *_, **__: drun.summary.update(data) # type: ignore
531
545
  drun.finish = lambda *_, **__: module.unset_globals() # type: ignore
546
+ drun.join = drun.finish # type: ignore
547
+ drun.define_metric = lambda *_, **__: wandb.sdk.wandb_metric.Metric("dummy") # type: ignore
548
+ drun.save = lambda *_, **__: False # type: ignore
549
+ for symbol in (
550
+ "alert",
551
+ "finish_artifact",
552
+ "get_project_url",
553
+ "get_sweep_url",
554
+ "get_url",
555
+ "link_artifact",
556
+ "link_model",
557
+ "use_artifact",
558
+ "log_artifact",
559
+ "log_code",
560
+ "log_model",
561
+ "use_model",
562
+ "mark_preempting",
563
+ "plot_table",
564
+ "restore",
565
+ "status",
566
+ "watch",
567
+ "unwatch",
568
+ "upsert_artifact",
569
+ ):
570
+ setattr(drun, symbol, lambda *_, **__: None) # type: ignore
571
+ # attributes
532
572
  drun._step = 0
573
+ drun._attach_id = None
533
574
  drun._run_obj = None
534
575
  drun._run_id = runid.generate_id()
535
576
  drun._name = "dummy-" + drun.id
577
+ drun._project = "dummy"
578
+ drun._entity = "dummy"
579
+ drun._tags = tuple()
580
+ drun._notes = None
581
+ drun._group = None
582
+ drun._start_time = time.time()
583
+ drun._starting_step = 0
536
584
  module.set_global(
537
585
  run=drun,
538
586
  config=drun.config,
@@ -688,7 +736,10 @@ class _WandbInit:
688
736
  tel.feature.flow_control_disabled = True
689
737
  if self.settings._flow_control_custom:
690
738
  tel.feature.flow_control_custom = True
691
- if self.settings._require_core:
739
+ if (
740
+ self.settings._require_core
741
+ and not self.settings._require_legacy_service
742
+ ):
692
743
  tel.feature.core = True
693
744
  if self.settings._shared:
694
745
  wandb.termwarn(
@@ -868,7 +919,7 @@ def _attach(
868
919
  raise UsageError(
869
920
  "Either `attach_id` or `run_id` must be specified or `run` must have `_attach_id`"
870
921
  )
871
- wandb._assert_is_user_process()
922
+ wandb._assert_is_user_process() # type: ignore
872
923
 
873
924
  _wl = wandb_setup._setup()
874
925
  assert _wl
@@ -1157,7 +1208,7 @@ def init(
1157
1208
  Returns:
1158
1209
  A `Run` object.
1159
1210
  """
1160
- wandb._assert_is_user_process()
1211
+ wandb._assert_is_user_process() # type: ignore
1161
1212
 
1162
1213
  kwargs = dict(locals())
1163
1214
 
@@ -13,7 +13,6 @@ import wandb
13
13
  from wandb import env, trigger
14
14
  from wandb.errors import Error
15
15
  from wandb.sdk.lib.exit_hooks import ExitHooks
16
- from wandb.sdk.lib.import_hooks import unregister_all_post_import_hooks
17
16
 
18
17
  if TYPE_CHECKING:
19
18
  from wandb.proto import wandb_settings_pb2
@@ -163,8 +162,6 @@ class _Manager:
163
162
  This sends a teardown record to the process. An exception is raised if
164
163
  the process has already been shut down.
165
164
  """
166
- unregister_all_post_import_hooks()
167
-
168
165
  if self._atexit_lambda:
169
166
  atexit.unregister(self._atexit_lambda)
170
167
  self._atexit_lambda = None
@@ -13,7 +13,12 @@ import os
13
13
  from typing import Optional, Sequence, Union
14
14
 
15
15
  import wandb
16
- from wandb.env import _REQUIRE_CORE
16
+ from wandb.env import (
17
+ _REQUIRE_CORE,
18
+ _REQUIRE_LEGACY_SERVICE,
19
+ is_require_core,
20
+ is_require_legacy_service,
21
+ )
17
22
  from wandb.errors import UnsupportedError
18
23
  from wandb.sdk import wandb_run
19
24
  from wandb.sdk.lib.wburls import wburls
@@ -41,8 +46,24 @@ class _Requires:
41
46
  self._require_service()
42
47
 
43
48
  def require_core(self) -> None:
49
+ if is_require_legacy_service():
50
+ raise UnsupportedError(
51
+ 'Cannot use wandb.require("core") because legacy-service'
52
+ " is already required--did you use"
53
+ ' wandb.require("legacy-service") or set the'
54
+ " WANDB__REQUIRE_LEGACY_SERVICE environment variable?"
55
+ )
44
56
  os.environ[_REQUIRE_CORE] = "true"
45
57
 
58
+ def require_legacy_service(self) -> None:
59
+ if is_require_core():
60
+ raise UnsupportedError(
61
+ 'Cannot use wandb.require("legacy-service") because core'
62
+ ' is already required--did you use wandb.require("core")'
63
+ " or set the WANDB__REQUIRE_CORE environment variable?"
64
+ )
65
+ os.environ[_REQUIRE_LEGACY_SERVICE] = "true"
66
+
46
67
  def apply(self) -> None:
47
68
  """Call require_* method for supported features."""
48
69
  last_message: str = ""