wandb 0.19.9__py3-none-win_amd64.whl → 0.19.11__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 (156) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +6 -3
  3. wandb/_pydantic/__init__.py +14 -8
  4. wandb/_pydantic/base.py +51 -36
  5. wandb/_pydantic/utils.py +73 -0
  6. wandb/_pydantic/v1_compat.py +79 -57
  7. wandb/apis/public/__init__.py +2 -2
  8. wandb/apis/public/api.py +684 -4
  9. wandb/apis/public/artifacts.py +377 -677
  10. wandb/apis/public/automations.py +69 -0
  11. wandb/apis/public/integrations.py +180 -0
  12. wandb/apis/public/projects.py +29 -0
  13. wandb/apis/public/registries/__init__.py +0 -0
  14. wandb/apis/public/registries/_freezable_list.py +179 -0
  15. wandb/apis/public/{registries.py → registries/registries_search.py} +22 -129
  16. wandb/apis/public/registries/registry.py +357 -0
  17. wandb/apis/public/registries/utils.py +140 -0
  18. wandb/apis/public/runs.py +58 -56
  19. wandb/apis/public/utils.py +107 -1
  20. wandb/automations/__init__.py +73 -0
  21. wandb/automations/_filters/__init__.py +40 -0
  22. wandb/automations/_filters/expressions.py +181 -0
  23. wandb/automations/_filters/operators.py +258 -0
  24. wandb/automations/_filters/run_metrics.py +332 -0
  25. wandb/automations/_generated/__init__.py +177 -0
  26. wandb/automations/_generated/create_automation.py +17 -0
  27. wandb/automations/_generated/create_generic_webhook_integration.py +43 -0
  28. wandb/automations/_generated/delete_automation.py +17 -0
  29. wandb/automations/_generated/enums.py +33 -0
  30. wandb/automations/_generated/fragments.py +358 -0
  31. wandb/automations/_generated/generic_webhook_integrations_by_entity.py +22 -0
  32. wandb/automations/_generated/get_automations.py +24 -0
  33. wandb/automations/_generated/get_automations_by_entity.py +26 -0
  34. wandb/automations/_generated/input_types.py +104 -0
  35. wandb/automations/_generated/integrations_by_entity.py +22 -0
  36. wandb/automations/_generated/operations.py +647 -0
  37. wandb/automations/_generated/slack_integrations_by_entity.py +22 -0
  38. wandb/automations/_generated/update_automation.py +17 -0
  39. wandb/automations/_utils.py +237 -0
  40. wandb/automations/_validators.py +165 -0
  41. wandb/automations/actions.py +220 -0
  42. wandb/automations/automations.py +87 -0
  43. wandb/automations/events.py +287 -0
  44. wandb/automations/integrations.py +45 -0
  45. wandb/automations/scopes.py +78 -0
  46. wandb/beta/workflows.py +9 -10
  47. wandb/bin/gpu_stats.exe +0 -0
  48. wandb/bin/wandb-core +0 -0
  49. wandb/cli/cli.py +3 -3
  50. wandb/env.py +11 -0
  51. wandb/integration/keras/keras.py +2 -1
  52. wandb/integration/langchain/wandb_tracer.py +2 -1
  53. wandb/jupyter.py +137 -118
  54. wandb/old/settings.py +4 -1
  55. wandb/old/summary.py +0 -2
  56. wandb/proto/v3/wandb_internal_pb2.py +297 -292
  57. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  58. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  59. wandb/proto/v4/wandb_internal_pb2.py +292 -292
  60. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  61. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  62. wandb/proto/v5/wandb_internal_pb2.py +292 -292
  63. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  64. wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
  65. wandb/proto/v6/wandb_base_pb2.py +41 -0
  66. wandb/proto/v6/wandb_internal_pb2.py +393 -0
  67. wandb/proto/v6/wandb_server_pb2.py +78 -0
  68. wandb/proto/v6/wandb_settings_pb2.py +58 -0
  69. wandb/proto/v6/wandb_telemetry_pb2.py +52 -0
  70. wandb/proto/wandb_base_pb2.py +2 -0
  71. wandb/proto/wandb_deprecated.py +8 -0
  72. wandb/proto/wandb_internal_pb2.py +3 -1
  73. wandb/proto/wandb_server_pb2.py +2 -0
  74. wandb/proto/wandb_settings_pb2.py +2 -0
  75. wandb/proto/wandb_telemetry_pb2.py +2 -0
  76. wandb/sdk/artifacts/_generated/__init__.py +289 -0
  77. wandb/sdk/artifacts/_generated/add_aliases.py +21 -0
  78. wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +43 -0
  79. wandb/sdk/artifacts/_generated/artifact_version_files.py +36 -0
  80. wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +36 -0
  81. wandb/sdk/artifacts/_generated/delete_aliases.py +21 -0
  82. wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +25 -0
  83. wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +35 -0
  84. wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +35 -0
  85. wandb/sdk/artifacts/_generated/enums.py +17 -0
  86. wandb/sdk/artifacts/_generated/fetch_linked_artifacts.py +67 -0
  87. wandb/sdk/artifacts/_generated/fragments.py +221 -0
  88. wandb/sdk/artifacts/_generated/input_types.py +28 -0
  89. wandb/sdk/artifacts/_generated/move_artifact_collection.py +35 -0
  90. wandb/sdk/artifacts/_generated/operations.py +611 -0
  91. wandb/sdk/artifacts/_generated/project_artifact_collection.py +101 -0
  92. wandb/sdk/artifacts/_generated/project_artifact_collections.py +33 -0
  93. wandb/sdk/artifacts/_generated/project_artifact_type.py +24 -0
  94. wandb/sdk/artifacts/_generated/project_artifact_types.py +24 -0
  95. wandb/sdk/artifacts/_generated/project_artifacts.py +42 -0
  96. wandb/sdk/artifacts/_generated/run_input_artifacts.py +51 -0
  97. wandb/sdk/artifacts/_generated/run_output_artifacts.py +51 -0
  98. wandb/sdk/artifacts/_generated/update_artifact.py +26 -0
  99. wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +35 -0
  100. wandb/sdk/artifacts/_generated/update_artifact_sequence.py +35 -0
  101. wandb/sdk/artifacts/_graphql_fragments.py +57 -79
  102. wandb/sdk/artifacts/_validators.py +120 -1
  103. wandb/sdk/artifacts/artifact.py +419 -215
  104. wandb/sdk/artifacts/artifact_file_cache.py +4 -6
  105. wandb/sdk/artifacts/artifact_manifest_entry.py +13 -3
  106. wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
  107. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +182 -1
  108. wandb/sdk/artifacts/storage_policy.py +3 -0
  109. wandb/sdk/data_types/base_types/media.py +2 -3
  110. wandb/sdk/data_types/base_types/wb_value.py +34 -11
  111. wandb/sdk/data_types/html.py +36 -9
  112. wandb/sdk/data_types/image.py +12 -12
  113. wandb/sdk/data_types/table.py +5 -0
  114. wandb/sdk/data_types/trace_tree.py +2 -0
  115. wandb/sdk/data_types/utils.py +1 -1
  116. wandb/sdk/data_types/video.py +59 -57
  117. wandb/sdk/interface/interface.py +4 -3
  118. wandb/sdk/internal/internal_api.py +21 -31
  119. wandb/sdk/internal/profiler.py +6 -5
  120. wandb/sdk/internal/run.py +13 -6
  121. wandb/sdk/internal/sender.py +5 -2
  122. wandb/sdk/launch/sweeps/utils.py +8 -0
  123. wandb/sdk/lib/apikey.py +25 -4
  124. wandb/sdk/lib/asyncio_compat.py +1 -1
  125. wandb/sdk/lib/deprecate.py +13 -22
  126. wandb/sdk/lib/disabled.py +2 -1
  127. wandb/sdk/lib/printer.py +37 -8
  128. wandb/sdk/lib/printer_asyncio.py +46 -0
  129. wandb/sdk/lib/redirect.py +10 -5
  130. wandb/sdk/projects/_generated/__init__.py +47 -0
  131. wandb/sdk/projects/_generated/delete_project.py +22 -0
  132. wandb/sdk/projects/_generated/enums.py +4 -0
  133. wandb/sdk/projects/_generated/fetch_registry.py +22 -0
  134. wandb/sdk/projects/_generated/fragments.py +41 -0
  135. wandb/sdk/projects/_generated/input_types.py +13 -0
  136. wandb/sdk/projects/_generated/operations.py +88 -0
  137. wandb/sdk/projects/_generated/rename_project.py +27 -0
  138. wandb/sdk/projects/_generated/upsert_registry_project.py +27 -0
  139. wandb/sdk/service/server_sock.py +19 -14
  140. wandb/sdk/service/service.py +18 -8
  141. wandb/sdk/service/streams.py +5 -0
  142. wandb/sdk/verify/verify.py +6 -3
  143. wandb/sdk/wandb_init.py +217 -70
  144. wandb/sdk/wandb_login.py +13 -4
  145. wandb/sdk/wandb_run.py +419 -295
  146. wandb/sdk/wandb_settings.py +27 -10
  147. wandb/sdk/wandb_setup.py +61 -0
  148. wandb/util.py +33 -29
  149. {wandb-0.19.9.dist-info → wandb-0.19.11.dist-info}/METADATA +5 -5
  150. {wandb-0.19.9.dist-info → wandb-0.19.11.dist-info}/RECORD +153 -83
  151. wandb/_globals.py +0 -19
  152. wandb/sdk/internal/_generated/base.py +0 -226
  153. wandb/sdk/internal/_generated/typing_compat.py +0 -14
  154. {wandb-0.19.9.dist-info → wandb-0.19.11.dist-info}/WHEEL +0 -0
  155. {wandb-0.19.9.dist-info → wandb-0.19.11.dist-info}/entry_points.txt +0 -0
  156. {wandb-0.19.9.dist-info → wandb-0.19.11.dist-info}/licenses/LICENSE +0 -0
@@ -11,7 +11,7 @@ import subprocess
11
11
  import sys
12
12
  from pathlib import Path
13
13
  from tempfile import NamedTemporaryFile
14
- from typing import IO, TYPE_CHECKING, ContextManager, Iterator
14
+ from typing import IO, ContextManager, Iterator, Protocol
15
15
 
16
16
  import wandb
17
17
  from wandb import env, util
@@ -19,12 +19,10 @@ from wandb.sdk.lib.filesystem import files_in
19
19
  from wandb.sdk.lib.hashutil import B64MD5, ETag, b64_to_hex_id
20
20
  from wandb.sdk.lib.paths import FilePathStr, StrPath, URIStr
21
21
 
22
- if TYPE_CHECKING:
23
- from typing import Protocol
24
22
 
25
- class Opener(Protocol):
26
- def __call__(self, mode: str = ...) -> ContextManager[IO]:
27
- pass
23
+ class Opener(Protocol):
24
+ def __call__(self, mode: str = ...) -> ContextManager[IO]:
25
+ pass
28
26
 
29
27
 
30
28
  def _get_sys_umask_threadsafe() -> int:
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import concurrent.futures
5
6
  import json
6
7
  import logging
7
8
  import os
@@ -9,8 +10,9 @@ from pathlib import Path
9
10
  from typing import TYPE_CHECKING
10
11
  from urllib.parse import urlparse
11
12
 
13
+ from wandb.proto.wandb_deprecated import Deprecated
12
14
  from wandb.sdk.lib import filesystem
13
- from wandb.sdk.lib.deprecate import Deprecated, deprecate
15
+ from wandb.sdk.lib.deprecate import deprecate
14
16
  from wandb.sdk.lib.hashutil import (
15
17
  B64MD5,
16
18
  ETag,
@@ -129,7 +131,11 @@ class ArtifactManifestEntry:
129
131
  return self._parent_artifact
130
132
 
131
133
  def download(
132
- self, root: str | None = None, skip_cache: bool | None = None
134
+ self,
135
+ root: str | None = None,
136
+ skip_cache: bool | None = None,
137
+ executor: concurrent.futures.Executor | None = None,
138
+ multipart: bool | None = None,
133
139
  ) -> FilePathStr:
134
140
  """Download this artifact entry to the specified root path.
135
141
 
@@ -169,7 +175,11 @@ class ArtifactManifestEntry:
169
175
  )
170
176
  else:
171
177
  cache_path = self._parent_artifact.manifest.storage_policy.load_file(
172
- self._parent_artifact, self, dest_path=override_cache_path
178
+ self._parent_artifact,
179
+ self,
180
+ dest_path=override_cache_path,
181
+ executor=executor,
182
+ multipart=multipart,
173
183
  )
174
184
 
175
185
  if skip_cache:
@@ -171,6 +171,7 @@ class AzureHandler(StorageHandler):
171
171
  def _get_credential(
172
172
  self, account_url: str
173
173
  ) -> azure.identity.DefaultAzureCredential | str:
174
+ # NOTE: Always returns default credential for reinit="create_new" runs.
174
175
  if (
175
176
  wandb.run
176
177
  and wandb.run.settings.azure_account_url_to_access_key is not None
@@ -2,20 +2,28 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import concurrent.futures
6
+ import functools
5
7
  import hashlib
8
+ import logging
6
9
  import math
7
10
  import os
11
+ import queue
8
12
  import shutil
9
- from typing import TYPE_CHECKING, Any, Sequence
13
+ import threading
14
+ from collections import deque
15
+ from typing import IO, TYPE_CHECKING, Any, NamedTuple, Sequence
10
16
  from urllib.parse import quote
11
17
 
12
18
  import requests
13
19
  import urllib3
14
20
 
21
+ from wandb import env
15
22
  from wandb.errors.term import termwarn
16
23
  from wandb.proto.wandb_internal_pb2 import ServerFeature
17
24
  from wandb.sdk.artifacts.artifact_file_cache import (
18
25
  ArtifactFileCache,
26
+ Opener,
19
27
  get_artifact_file_cache,
20
28
  )
21
29
  from wandb.sdk.artifacts.staging import get_staging_dir
@@ -60,6 +68,27 @@ S3_MIN_MULTI_UPLOAD_SIZE = 2 * 1024**3
60
68
  S3_MAX_MULTI_UPLOAD_SIZE = 5 * 1024**4
61
69
 
62
70
 
71
+ # Minimum size to switch to multipart download, same as upload, 2GB.
72
+ _MULTIPART_DOWNLOAD_SIZE = S3_MIN_MULTI_UPLOAD_SIZE
73
+ # Multipart download part size is same as multpart upload size, which is hard coded to 100MB.
74
+ # https://github.com/wandb/wandb/blob/7b2a13cb8efcd553317167b823c8e52d8c3f7c4e/core/pkg/artifacts/saver.go#L496
75
+ # https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance-guidelines.html#optimizing-performance-guidelines-get-range
76
+ _DOWNLOAD_PART_SIZE_BYTES = 100 * 1024 * 1024
77
+ # Chunk size for reading http response and writing to disk. 1MB.
78
+ _HTTP_RES_CHUNK_SIZE_BYTES = 1 * 1024 * 1024
79
+ # Signal end of _ChunkQueue, consumer (file writer) should stop after getting this item.
80
+ # NOTE: it should only be used for multithread executor, it does notwork for multiprocess executor.
81
+ # multipart download is using the executor from artifact.download() which is a multithread executor.
82
+ _CHUNK_QUEUE_SENTINEL = object()
83
+
84
+ logger = logging.getLogger(__name__)
85
+
86
+
87
+ class _ChunkContent(NamedTuple):
88
+ offset: int
89
+ data: bytes
90
+
91
+
63
92
  class WandbStoragePolicy(StoragePolicy):
64
93
  @classmethod
65
94
  def name(cls) -> str:
@@ -120,7 +149,20 @@ class WandbStoragePolicy(StoragePolicy):
120
149
  artifact: Artifact,
121
150
  manifest_entry: ArtifactManifestEntry,
122
151
  dest_path: str | None = None,
152
+ executor: concurrent.futures.Executor | None = None,
153
+ multipart: bool | None = None,
123
154
  ) -> FilePathStr:
155
+ """Use cache or download the file using signed url.
156
+
157
+ Args:
158
+ executor: Passed from caller, artifact has a thread pool for multi file download.
159
+ Reuse the thread pool for multi part download. The thread pool is closed when
160
+ artifact download is done.
161
+ multipart: If set to `None` (default), the artifact will be downloaded
162
+ in parallel using multipart download if individual file size is greater than
163
+ 2GB. If set to `True` or `False`, the artifact will be downloaded in
164
+ parallel or serially regardless of the file size.
165
+ """
124
166
  if dest_path is not None:
125
167
  self._cache._override_cache_path = dest_path
126
168
 
@@ -132,6 +174,20 @@ class WandbStoragePolicy(StoragePolicy):
132
174
  return path
133
175
 
134
176
  if manifest_entry._download_url is not None:
177
+ # Use multipart parallel download for large file
178
+ if (
179
+ executor is not None
180
+ and manifest_entry.size is not None
181
+ and self._should_multipart_download(manifest_entry.size, multipart)
182
+ ):
183
+ self._multipart_file_download(
184
+ executor,
185
+ manifest_entry._download_url,
186
+ manifest_entry.size,
187
+ cache_open,
188
+ )
189
+ return path
190
+ # Serial download
135
191
  response = self._session.get(manifest_entry._download_url, stream=True)
136
192
  try:
137
193
  response.raise_for_status()
@@ -165,6 +221,131 @@ class WandbStoragePolicy(StoragePolicy):
165
221
  file.write(data)
166
222
  return path
167
223
 
224
+ def _should_multipart_download(
225
+ self,
226
+ file_size: int,
227
+ multipart: bool | None,
228
+ ) -> bool:
229
+ if multipart is not None:
230
+ return multipart
231
+ return file_size >= _MULTIPART_DOWNLOAD_SIZE
232
+
233
+ def _write_chunks_to_file(
234
+ self,
235
+ f: IO,
236
+ q: queue.Queue,
237
+ download_has_error: threading.Event,
238
+ ):
239
+ while not download_has_error.is_set():
240
+ item = q.get()
241
+ if item is _CHUNK_QUEUE_SENTINEL:
242
+ # Normal shutdown, all the chunks are written
243
+ return
244
+ elif isinstance(item, _ChunkContent):
245
+ try:
246
+ # NOTE: Seek works without pre allocating the file on disk.
247
+ # It automatically creates a sparse file, e.g. ls -hl would show
248
+ # a bigger size compared to du -sh * because downloading different
249
+ # chunks is not a sequential write.
250
+ # See https://man7.org/linux/man-pages/man2/lseek.2.html
251
+ f.seek(item.offset)
252
+ f.write(item.data)
253
+ except Exception as e:
254
+ if env.is_debug():
255
+ logger.debug(f"Error writing chunk to file: {e}")
256
+ download_has_error.set()
257
+ raise e
258
+ else:
259
+ raise ValueError(f"Unknown queue item type: {type(item)}")
260
+
261
+ def _download_part(
262
+ self,
263
+ download_url: str,
264
+ headers: dict,
265
+ start: int,
266
+ q: queue.Queue,
267
+ download_has_error: threading.Event,
268
+ ):
269
+ # Other threads has error, no need to start
270
+ if download_has_error.is_set():
271
+ return
272
+ response = self._session.get(
273
+ url=download_url,
274
+ headers=headers,
275
+ stream=True,
276
+ )
277
+ response.raise_for_status()
278
+
279
+ file_offset = start
280
+ for content in response.iter_content(chunk_size=_HTTP_RES_CHUNK_SIZE_BYTES):
281
+ if download_has_error.is_set():
282
+ return
283
+ q.put(_ChunkContent(offset=file_offset, data=content))
284
+ file_offset += len(content)
285
+
286
+ def _multipart_file_download(
287
+ self,
288
+ executor: concurrent.futures.Executor,
289
+ download_url: str,
290
+ file_size_bytes: int,
291
+ cache_open: Opener,
292
+ ):
293
+ """Download file as multiple parts in parallel.
294
+
295
+ Only one thread for writing to file. Each part run one http request in one thread.
296
+ HTTP response chunk of a file part is sent to the writer thread via a queue.
297
+ """
298
+ q: queue.Queue[_ChunkContent | object] = queue.Queue(maxsize=500)
299
+ download_has_error = threading.Event()
300
+
301
+ # Put cache_open at top so we remove the tmp file when there is network error.
302
+ with cache_open("wb") as f:
303
+ # Start writer thread first.
304
+ write_handler = functools.partial(
305
+ self._write_chunks_to_file, f, q, download_has_error
306
+ )
307
+ write_future = executor.submit(write_handler)
308
+
309
+ # Start download threads for each part.
310
+ download_futures: deque[concurrent.futures.Future] = deque()
311
+ part_size = _DOWNLOAD_PART_SIZE_BYTES
312
+ num_parts = int(math.ceil(file_size_bytes / float(part_size)))
313
+ for i in range(num_parts):
314
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Range
315
+ # Start and end are both inclusive, empty end means use the actual end of the file.
316
+ start = i * part_size
317
+ bytes_range = f"bytes={start}-"
318
+ if i != (num_parts - 1):
319
+ # bytes=0-499
320
+ bytes_range += f"{start + part_size - 1}"
321
+ headers = {"Range": bytes_range}
322
+ download_handler = functools.partial(
323
+ self._download_part,
324
+ download_url,
325
+ headers,
326
+ start,
327
+ q,
328
+ download_has_error,
329
+ )
330
+ download_futures.append(executor.submit(download_handler))
331
+
332
+ # Wait for download
333
+ done, not_done = concurrent.futures.wait(
334
+ download_futures, return_when=concurrent.futures.FIRST_EXCEPTION
335
+ )
336
+ try:
337
+ for fut in done:
338
+ fut.result()
339
+ except Exception as e:
340
+ if env.is_debug():
341
+ logger.debug(f"Error downloading file: {e}")
342
+ download_has_error.set()
343
+ raise e
344
+ finally:
345
+ # Always signal the writer to stop
346
+ q.put(_CHUNK_QUEUE_SENTINEL)
347
+ write_future.result()
348
+
168
349
  def store_reference(
169
350
  self,
170
351
  artifact: Artifact,
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import concurrent.futures
5
6
  from typing import TYPE_CHECKING, Sequence
6
7
 
7
8
  from wandb.sdk.internal.internal_api import Api as InternalApi
@@ -40,6 +41,8 @@ class StoragePolicy:
40
41
  artifact: Artifact,
41
42
  manifest_entry: ArtifactManifestEntry,
42
43
  dest_path: str | None = None,
44
+ executor: concurrent.futures.Executor | None = None,
45
+ multipart: bool | None = None,
43
46
  ) -> FilePathStr:
44
47
  raise NotImplementedError
45
48
 
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Type, Union, ca
7
7
 
8
8
  import wandb
9
9
  from wandb import util
10
- from wandb._globals import _datatypes_callback
11
10
  from wandb.sdk.lib import filesystem
12
11
  from wandb.sdk.lib.paths import LogicalPath
13
12
 
@@ -192,7 +191,7 @@ class Media(WBValue):
192
191
  shutil.move(self._path, new_path)
193
192
  self._path = new_path
194
193
  self._is_tmp = False
195
- _datatypes_callback(media_path)
194
+ run._publish_file(media_path)
196
195
  else:
197
196
  try:
198
197
  shutil.copy(self._path, new_path)
@@ -200,7 +199,7 @@ class Media(WBValue):
200
199
  if not ignore_copy_err:
201
200
  raise e
202
201
  self._path = new_path
203
- _datatypes_callback(media_path)
202
+ run._publish_file(media_path)
204
203
 
205
204
  def to_json(self, run: Union["LocalRun", "Artifact"]) -> dict:
206
205
  """Serialize the object into a JSON blob.
@@ -1,7 +1,7 @@
1
1
  from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Type, Union
2
2
 
3
- import wandb
4
3
  from wandb import util
4
+ from wandb.sdk import wandb_setup
5
5
 
6
6
  if TYPE_CHECKING: # pragma: no cover
7
7
  from wandb.sdk.artifacts.artifact import Artifact
@@ -11,6 +11,31 @@ if TYPE_CHECKING: # pragma: no cover
11
11
  TypeMappingType = Dict[str, Type["WBValue"]]
12
12
 
13
13
 
14
+ def _is_maybe_offline() -> bool:
15
+ """Guess whether wandb is configured to be offline.
16
+
17
+ This is an anti-pattern because there is no library-level "offline" mode:
18
+ only runs can be offline. Online and offline runs can exist in the same
19
+ process. This function is a heuristic that works only if there is at most
20
+ one run in the process, and could otherwise produce unexpected results.
21
+
22
+ Returns:
23
+ Whether the user likely configured wandb to be offline.
24
+ """
25
+ singleton = wandb_setup._setup(start_service=False)
26
+
27
+ # First check: if there's a run, check if it is offline.
28
+ #
29
+ # This covers uses like `wandb.init(mode="offline")` which don't modify
30
+ # the singleton's settings.
31
+ if run := singleton.most_recent_active_run:
32
+ return run.offline
33
+
34
+ # Second check: default to global defaults derived from environment
35
+ # variables or passed explicitly to `wandb.setup()`.
36
+ return singleton.settings._offline
37
+
38
+
14
39
  def _server_accepts_client_ids() -> bool:
15
40
  from wandb.util import parse_version
16
41
 
@@ -25,15 +50,13 @@ def _server_accepts_client_ids() -> bool:
25
50
  # AS OF NOW, 2024/11/06, we assume that all customer's server deployments accept
26
51
  # client IDs.
27
52
 
28
- if util._is_offline():
29
- # If there are any users with issues on an older backend, customers can disable the
30
- # setting `allow_offline_artifacts` to revert the SDK's behavior back to not
31
- # using client IDs in offline mode.
32
- if wandb.run and not wandb.run.settings.allow_offline_artifacts:
33
- return False
34
- # Assume client IDs are accepted
53
+ if _is_maybe_offline():
54
+ singleton = wandb_setup._setup(start_service=False)
55
+
56
+ if run := singleton.most_recent_active_run:
57
+ return run._settings.allow_offline_artifacts
35
58
  else:
36
- return True
59
+ return singleton.settings.allow_offline_artifacts
37
60
 
38
61
  # If the script is online, request the max_cli_version and ensure the server
39
62
  # is of a high enough version.
@@ -240,7 +263,7 @@ class WBValue:
240
263
  self._artifact_target
241
264
  and self._artifact_target.name
242
265
  and self._artifact_target.artifact._is_draft_save_started()
243
- and not util._is_offline()
266
+ and not _is_maybe_offline()
244
267
  and not _server_accepts_client_ids()
245
268
  ):
246
269
  self._artifact_target.artifact.wait()
@@ -271,7 +294,7 @@ class WBValue:
271
294
  self._artifact_target
272
295
  and self._artifact_target.name
273
296
  and self._artifact_target.artifact._is_draft_save_started()
274
- and not util._is_offline()
297
+ and not _is_maybe_offline()
275
298
  and not _server_accepts_client_ids()
276
299
  ):
277
300
  self._artifact_target.artifact.wait()
@@ -16,19 +16,46 @@ if TYPE_CHECKING: # pragma: no cover
16
16
 
17
17
 
18
18
  class Html(BatchableMedia):
19
- """Wandb class for arbitrary html.
20
-
21
- Args:
22
- data: (string or io object) HTML to display in wandb
23
- inject: (boolean) Add a stylesheet to the HTML object. If set
24
- to False the HTML will pass through unchanged.
25
- """
19
+ """A class for logging HTML content to W&B."""
26
20
 
27
21
  _log_type = "html-file"
28
22
 
29
- def __init__(self, data: Union[str, "TextIO"], inject: bool = True) -> None:
23
+ def __init__(
24
+ self,
25
+ data: Union[str, "TextIO"],
26
+ inject: bool = True,
27
+ data_is_not_path: bool = False,
28
+ ) -> None:
29
+ """Creates a W&B HTML object.
30
+
31
+ It can be initialized by providing a path to a file:
32
+ ```
33
+ with wandb.init() as run:
34
+ run.log({"html": wandb.Html("./index.html")})
35
+ ```
36
+
37
+ Alternatively, it can be initialized by providing literal HTML,
38
+ in either a string or IO object:
39
+ ```
40
+ with wandb.init() as run:
41
+ run.log({"html": wandb.Html("<h1>Hello, world!</h1>")})
42
+ ```
43
+
44
+ Args:
45
+ data:
46
+ A string that is a path to a file with the extension ".html",
47
+ or a string or IO object containing literal HTML.
48
+ inject: Add a stylesheet to the HTML object. If set
49
+ to False the HTML will pass through unchanged.
50
+ data_is_not_path: If set to False, the data will be
51
+ treated as a path to a file.
52
+ """
30
53
  super().__init__()
31
- data_is_path = isinstance(data, str) and os.path.exists(data)
54
+ data_is_path = (
55
+ isinstance(data, str)
56
+ and os.path.isfile(data)
57
+ and os.path.splitext(data)[1] == ".html"
58
+ ) and not data_is_not_path
32
59
  data_path = ""
33
60
  if data_is_path:
34
61
  assert isinstance(data, str)
@@ -34,8 +34,8 @@ if TYPE_CHECKING: # pragma: no cover
34
34
  TorchTensorType = Union["torch.Tensor", "torch.Variable"]
35
35
 
36
36
 
37
- def _server_accepts_image_filenames() -> bool:
38
- if util._is_offline():
37
+ def _server_accepts_image_filenames(run: "LocalRun") -> bool:
38
+ if run.offline:
39
39
  return True
40
40
 
41
41
  # Newer versions of wandb accept large image filenames arrays
@@ -51,15 +51,15 @@ def _server_accepts_image_filenames() -> bool:
51
51
  return accepts_image_filenames
52
52
 
53
53
 
54
- def _server_accepts_artifact_path() -> bool:
55
- from wandb.util import parse_version
54
+ def _server_accepts_artifact_path(run: "LocalRun") -> bool:
55
+ if run.offline:
56
+ return False
57
+
58
+ max_cli_version = util._get_max_cli_version()
59
+ if max_cli_version is None:
60
+ return False
56
61
 
57
- target_version = "0.12.14"
58
- max_cli_version = util._get_max_cli_version() if not util._is_offline() else None
59
- accepts_artifact_path: bool = max_cli_version is not None and parse_version(
60
- target_version
61
- ) <= parse_version(max_cli_version)
62
- return accepts_artifact_path
62
+ return util.parse_version("0.12.14") <= util.parse_version(max_cli_version)
63
63
 
64
64
 
65
65
  class Image(BatchableMedia):
@@ -401,7 +401,7 @@ class Image(BatchableMedia):
401
401
  )
402
402
 
403
403
  if (
404
- not _server_accepts_artifact_path()
404
+ not _server_accepts_artifact_path(run)
405
405
  or self._get_artifact_entry_ref_url() is None
406
406
  ):
407
407
  super().bind_to_run(run, key, step, id_, ignore_copy_err=ignore_copy_err)
@@ -575,7 +575,7 @@ class Image(BatchableMedia):
575
575
  "format": format,
576
576
  "count": num_images_to_log,
577
577
  }
578
- if _server_accepts_image_filenames():
578
+ if _server_accepts_image_filenames(run):
579
579
  meta["filenames"] = [
580
580
  obj.get("path", obj.get("artifact_path")) for obj in jsons
581
581
  ]
@@ -480,6 +480,11 @@ class Table(Media):
480
480
  max_rows = Table.MAX_ROWS
481
481
  n_rows = len(self.data)
482
482
  if n_rows > max_rows and warn:
483
+ # NOTE: Never raises for reinit="create_new" runs.
484
+ # Since this is called by bind_to_run(), this can be fixed by
485
+ # propagating the run. It cannot be fixed for to_json() calls
486
+ # that are given an artifact, other than by deferring to singleton
487
+ # settings.
483
488
  if wandb.run and (
484
489
  wandb.run.settings.table_raise_on_max_row_limit_exceeded
485
490
  or wandb.run.settings.strict
@@ -431,6 +431,8 @@ class Trace:
431
431
  name: The name of the trace to be logged
432
432
  """
433
433
  trace_tree = WBTraceTree(self._span, self._model_dict)
434
+ # NOTE: Does not work for reinit="create_new" runs.
435
+ # This method should be deprecated and users should call run.log().
434
436
  assert (
435
437
  wandb.run is not None
436
438
  ), "You must call wandb.init() before logging a trace"
@@ -101,7 +101,7 @@ def val_to_json(
101
101
 
102
102
  items = _prune_max_seq(val)
103
103
 
104
- if _server_accepts_image_filenames():
104
+ if _server_accepts_image_filenames(run):
105
105
  for item in items:
106
106
  item.bind_to_run(
107
107
  run=run,