wandb 0.17.6__py3-none-win32.whl → 0.17.8__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 (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/wandb-core +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 = ""