wandb 0.19.11__py3-none-win32.whl → 0.20.0__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 (172) hide show
  1. wandb/__init__.py +1 -2
  2. wandb/__init__.pyi +3 -6
  3. wandb/_iterutils.py +26 -7
  4. wandb/_pydantic/__init__.py +2 -1
  5. wandb/_pydantic/utils.py +7 -0
  6. wandb/agents/pyagent.py +9 -15
  7. wandb/analytics/sentry.py +1 -2
  8. wandb/apis/attrs.py +3 -4
  9. wandb/apis/importers/internals/util.py +1 -1
  10. wandb/apis/importers/validation.py +2 -2
  11. wandb/apis/importers/wandb.py +30 -25
  12. wandb/apis/normalize.py +2 -2
  13. wandb/apis/public/__init__.py +1 -0
  14. wandb/apis/public/api.py +37 -33
  15. wandb/apis/public/artifacts.py +103 -72
  16. wandb/apis/public/jobs.py +3 -2
  17. wandb/apis/public/registries/registries_search.py +4 -2
  18. wandb/apis/public/registries/registry.py +1 -1
  19. wandb/apis/public/registries/utils.py +9 -9
  20. wandb/apis/public/runs.py +18 -6
  21. wandb/automations/_filters/expressions.py +1 -1
  22. wandb/automations/_filters/operators.py +1 -1
  23. wandb/automations/_filters/run_metrics.py +1 -1
  24. wandb/beta/workflows.py +6 -5
  25. wandb/bin/gpu_stats.exe +0 -0
  26. wandb/bin/wandb-core +0 -0
  27. wandb/cli/cli.py +54 -73
  28. wandb/docker/__init__.py +21 -74
  29. wandb/docker/names.py +40 -0
  30. wandb/env.py +0 -1
  31. wandb/errors/util.py +1 -1
  32. wandb/filesync/step_checksum.py +1 -1
  33. wandb/filesync/step_upload.py +1 -1
  34. wandb/integration/diffusers/resolvers/multimodal.py +1 -2
  35. wandb/integration/gym/__init__.py +5 -6
  36. wandb/integration/keras/callbacks/model_checkpoint.py +2 -2
  37. wandb/integration/keras/keras.py +13 -19
  38. wandb/integration/kfp/kfp_patch.py +2 -3
  39. wandb/integration/langchain/wandb_tracer.py +1 -1
  40. wandb/integration/metaflow/metaflow.py +13 -13
  41. wandb/integration/openai/fine_tuning.py +3 -2
  42. wandb/integration/sagemaker/auth.py +2 -1
  43. wandb/integration/sklearn/utils.py +2 -1
  44. wandb/integration/tensorboard/__init__.py +1 -1
  45. wandb/integration/tensorboard/log.py +2 -5
  46. wandb/integration/tensorflow/__init__.py +2 -2
  47. wandb/jupyter.py +20 -17
  48. wandb/plot/confusion_matrix.py +1 -1
  49. wandb/plot/utils.py +8 -7
  50. wandb/proto/v3/wandb_internal_pb2.py +355 -335
  51. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  52. wandb/proto/v3/wandb_telemetry_pb2.py +12 -12
  53. wandb/proto/v4/wandb_internal_pb2.py +339 -335
  54. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  55. wandb/proto/v4/wandb_telemetry_pb2.py +12 -12
  56. wandb/proto/v5/wandb_internal_pb2.py +339 -335
  57. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  58. wandb/proto/v5/wandb_telemetry_pb2.py +12 -12
  59. wandb/proto/v6/wandb_internal_pb2.py +339 -335
  60. wandb/proto/v6/wandb_settings_pb2.py +2 -2
  61. wandb/proto/v6/wandb_telemetry_pb2.py +12 -12
  62. wandb/proto/wandb_deprecated.py +6 -8
  63. wandb/sdk/artifacts/_internal_artifact.py +43 -0
  64. wandb/sdk/artifacts/_validators.py +55 -35
  65. wandb/sdk/artifacts/artifact.py +117 -115
  66. wandb/sdk/artifacts/artifact_download_logger.py +2 -0
  67. wandb/sdk/artifacts/artifact_saver.py +1 -3
  68. wandb/sdk/artifacts/artifact_state.py +2 -0
  69. wandb/sdk/artifacts/artifact_ttl.py +2 -0
  70. wandb/sdk/artifacts/exceptions.py +14 -0
  71. wandb/sdk/artifacts/staging.py +2 -0
  72. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +2 -6
  73. wandb/sdk/artifacts/storage_handlers/multi_handler.py +1 -1
  74. wandb/sdk/artifacts/storage_handlers/tracking_handler.py +2 -6
  75. wandb/sdk/artifacts/storage_handlers/wb_artifact_handler.py +1 -5
  76. wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +1 -1
  77. wandb/sdk/artifacts/storage_layout.py +2 -0
  78. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +3 -3
  79. wandb/sdk/backend/backend.py +11 -182
  80. wandb/sdk/data_types/_dtypes.py +2 -6
  81. wandb/sdk/data_types/audio.py +20 -3
  82. wandb/sdk/data_types/base_types/media.py +12 -7
  83. wandb/sdk/data_types/base_types/wb_value.py +8 -18
  84. wandb/sdk/data_types/bokeh.py +19 -2
  85. wandb/sdk/data_types/helper_types/bounding_boxes_2d.py +17 -1
  86. wandb/sdk/data_types/helper_types/image_mask.py +7 -1
  87. wandb/sdk/data_types/html.py +4 -4
  88. wandb/sdk/data_types/image.py +164 -103
  89. wandb/sdk/data_types/molecule.py +6 -6
  90. wandb/sdk/data_types/object_3d.py +10 -5
  91. wandb/sdk/data_types/saved_model.py +11 -6
  92. wandb/sdk/data_types/table.py +313 -83
  93. wandb/sdk/data_types/table_decorators.py +108 -0
  94. wandb/sdk/data_types/utils.py +43 -7
  95. wandb/sdk/data_types/video.py +21 -3
  96. wandb/sdk/interface/interface.py +10 -0
  97. wandb/sdk/internal/datastore.py +2 -6
  98. wandb/sdk/internal/file_pusher.py +1 -5
  99. wandb/sdk/internal/file_stream.py +8 -17
  100. wandb/sdk/internal/handler.py +2 -2
  101. wandb/sdk/internal/incremental_table_util.py +53 -0
  102. wandb/sdk/internal/internal.py +3 -5
  103. wandb/sdk/internal/internal_api.py +66 -89
  104. wandb/sdk/internal/job_builder.py +2 -7
  105. wandb/sdk/internal/profiler.py +2 -2
  106. wandb/sdk/internal/progress.py +1 -3
  107. wandb/sdk/internal/run.py +1 -6
  108. wandb/sdk/internal/sender.py +24 -36
  109. wandb/sdk/internal/system/assets/aggregators.py +1 -7
  110. wandb/sdk/internal/system/assets/disk.py +3 -3
  111. wandb/sdk/internal/system/assets/gpu.py +4 -4
  112. wandb/sdk/internal/system/assets/gpu_amd.py +4 -4
  113. wandb/sdk/internal/system/assets/interfaces.py +6 -6
  114. wandb/sdk/internal/system/assets/tpu.py +1 -1
  115. wandb/sdk/internal/system/assets/trainium.py +6 -6
  116. wandb/sdk/internal/system/system_info.py +5 -7
  117. wandb/sdk/internal/system/system_monitor.py +4 -4
  118. wandb/sdk/internal/tb_watcher.py +5 -7
  119. wandb/sdk/launch/_launch.py +1 -1
  120. wandb/sdk/launch/_project_spec.py +19 -20
  121. wandb/sdk/launch/agent/agent.py +3 -3
  122. wandb/sdk/launch/agent/config.py +1 -1
  123. wandb/sdk/launch/agent/job_status_tracker.py +2 -2
  124. wandb/sdk/launch/builder/build.py +2 -3
  125. wandb/sdk/launch/builder/kaniko_builder.py +5 -4
  126. wandb/sdk/launch/environment/gcp_environment.py +1 -2
  127. wandb/sdk/launch/registry/azure_container_registry.py +2 -2
  128. wandb/sdk/launch/registry/elastic_container_registry.py +2 -2
  129. wandb/sdk/launch/registry/google_artifact_registry.py +3 -3
  130. wandb/sdk/launch/runner/abstract.py +5 -5
  131. wandb/sdk/launch/runner/kubernetes_monitor.py +2 -2
  132. wandb/sdk/launch/runner/kubernetes_runner.py +1 -1
  133. wandb/sdk/launch/runner/sagemaker_runner.py +2 -4
  134. wandb/sdk/launch/runner/vertex_runner.py +2 -7
  135. wandb/sdk/launch/sweeps/__init__.py +1 -1
  136. wandb/sdk/launch/sweeps/scheduler.py +2 -2
  137. wandb/sdk/launch/sweeps/utils.py +3 -3
  138. wandb/sdk/launch/utils.py +3 -4
  139. wandb/sdk/lib/apikey.py +5 -8
  140. wandb/sdk/lib/config_util.py +3 -3
  141. wandb/sdk/lib/fsm.py +3 -18
  142. wandb/sdk/lib/gitlib.py +6 -5
  143. wandb/sdk/lib/ipython.py +2 -2
  144. wandb/sdk/lib/json_util.py +9 -14
  145. wandb/sdk/lib/printer.py +3 -8
  146. wandb/sdk/lib/redirect.py +1 -1
  147. wandb/sdk/lib/retry.py +3 -7
  148. wandb/sdk/lib/run_moment.py +2 -2
  149. wandb/sdk/lib/service_connection.py +19 -21
  150. wandb/sdk/lib/service_token.py +1 -2
  151. wandb/sdk/mailbox/mailbox_handle.py +3 -7
  152. wandb/sdk/mailbox/response_handle.py +2 -6
  153. wandb/sdk/service/streams.py +3 -7
  154. wandb/sdk/verify/verify.py +5 -6
  155. wandb/sdk/wandb_config.py +1 -1
  156. wandb/sdk/wandb_init.py +38 -106
  157. wandb/sdk/wandb_login.py +7 -6
  158. wandb/sdk/wandb_run.py +52 -240
  159. wandb/sdk/wandb_settings.py +71 -60
  160. wandb/sdk/wandb_setup.py +43 -17
  161. wandb/sdk/wandb_watch.py +5 -7
  162. wandb/sync/__init__.py +1 -1
  163. wandb/sync/sync.py +13 -13
  164. wandb/util.py +17 -35
  165. wandb/wandb_agent.py +8 -11
  166. {wandb-0.19.11.dist-info → wandb-0.20.0.dist-info}/METADATA +5 -5
  167. {wandb-0.19.11.dist-info → wandb-0.20.0.dist-info}/RECORD +170 -168
  168. wandb/docker/auth.py +0 -435
  169. wandb/docker/www_authenticate.py +0 -94
  170. {wandb-0.19.11.dist-info → wandb-0.20.0.dist-info}/WHEEL +0 -0
  171. {wandb-0.19.11.dist-info → wandb-0.20.0.dist-info}/entry_points.txt +0 -0
  172. {wandb-0.19.11.dist-info → wandb-0.20.0.dist-info}/licenses/LICENSE +0 -0
wandb/__init__.py CHANGED
@@ -10,7 +10,7 @@ For reference documentation, see https://docs.wandb.com/ref/python.
10
10
  """
11
11
  from __future__ import annotations
12
12
 
13
- __version__ = "0.19.11"
13
+ __version__ = "0.20.0"
14
14
 
15
15
 
16
16
  from wandb.errors import Error
@@ -169,7 +169,6 @@ gym = _lazyloader.LazyLoader("wandb.gym", globals(), "wandb.integration.gym")
169
169
  lightgbm = _lazyloader.LazyLoader(
170
170
  "wandb.lightgbm", globals(), "wandb.integration.lightgbm"
171
171
  )
172
- docker = _lazyloader.LazyLoader("wandb.docker", globals(), "wandb.docker")
173
172
  jupyter = _lazyloader.LazyLoader("wandb.jupyter", globals(), "wandb.jupyter")
174
173
  sacred = _lazyloader.LazyLoader("wandb.sacred", globals(), "wandb.integration.sacred")
175
174
 
wandb/__init__.pyi CHANGED
@@ -106,7 +106,7 @@ if TYPE_CHECKING:
106
106
  import wandb
107
107
  from wandb.plot import CustomChart
108
108
 
109
- __version__: str = "0.19.11"
109
+ __version__: str = "0.20.0"
110
110
 
111
111
  run: Run | None
112
112
  config: wandb_config.Config
@@ -512,7 +512,6 @@ def log(
512
512
  data: dict[str, Any],
513
513
  step: int | None = None,
514
514
  commit: bool | None = None,
515
- sync: bool | None = None,
516
515
  ) -> None:
517
516
  """Upload run data.
518
517
 
@@ -617,7 +616,6 @@ def log(
617
616
  accumulate data for the step. See the notes in the description.
618
617
  If `step` is `None`, then the default is `commit=True`;
619
618
  otherwise, the default is `commit=False`.
620
- sync: This argument is deprecated and does nothing.
621
619
 
622
620
  Examples:
623
621
  For more and more detailed examples, see
@@ -748,7 +746,7 @@ def log(
748
746
  ...
749
747
 
750
748
  def save(
751
- glob_str: str | os.PathLike | None = None,
749
+ glob_str: str | os.PathLike,
752
750
  base_path: str | os.PathLike | None = None,
753
751
  policy: PolicyName = "live",
754
752
  ) -> bool | list[str]:
@@ -978,8 +976,7 @@ def use_artifact(
978
976
  You can also pass an Artifact object created by calling `wandb.Artifact`
979
977
  type: (str, optional) The type of artifact to use.
980
978
  aliases: (list, optional) Aliases to apply to this artifact
981
- use_as: (string, optional) Optional string indicating what purpose the artifact was used with.
982
- Will be shown in UI.
979
+ use_as: This argument is deprecated and does nothing.
983
980
 
984
981
  Returns:
985
982
  An `Artifact` object.
wandb/_iterutils.py CHANGED
@@ -1,14 +1,31 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Iterable, TypeVar
3
+ from typing import TYPE_CHECKING, Any, Iterable, TypeVar, Union, overload
4
4
 
5
- T = TypeVar("T")
5
+ if TYPE_CHECKING:
6
+ T = TypeVar("T")
7
+ ClassInfo = Union[type[T], tuple[type[T], ...]]
8
+
9
+
10
+ @overload
11
+ def always_list(obj: Iterable[T], base_type: ClassInfo = ...) -> list[T]: ...
12
+ @overload
13
+ def always_list(obj: T, base_type: ClassInfo = ...) -> list[T]: ...
14
+ def always_list(obj: Any, base_type: Any = (str, bytes)) -> list[T]:
15
+ """Return a guaranteed list of objects from a single instance OR iterable of such objects.
16
+
17
+ By default, assume the returned list should have string-like elements (i.e. `str`/`bytes`).
18
+
19
+ Adapted from `more_itertools.always_iterable`, but simplified for internal use. See:
20
+ https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable
21
+ """
22
+ return [obj] if isinstance(obj, base_type) else list(obj)
6
23
 
7
24
 
8
25
  def one(
9
26
  iterable: Iterable[T],
10
- too_short: Exception | None = None,
11
- too_long: Exception | None = None,
27
+ too_short: type[Exception] | Exception | None = None,
28
+ too_long: type[Exception] | Exception | None = None,
12
29
  ) -> T:
13
30
  """Return the only item in the iterable.
14
31
 
@@ -35,12 +52,14 @@ def one(
35
52
  it = iter(iterable)
36
53
  try:
37
54
  obj = next(it)
38
- except StopIteration as e:
39
- raise too_short or ValueError("Expected 1 item in iterable, got 0") from e
55
+ except StopIteration:
56
+ raise (too_short or ValueError("Expected 1 item in iterable, got 0")) from None
40
57
 
41
58
  # ...the second item doesn't
42
59
  try:
43
60
  _ = next(it)
44
61
  except StopIteration:
45
62
  return obj
46
- raise too_long or ValueError("Expected 1 item in iterable, got multiple")
63
+ raise (
64
+ too_long or ValueError("Expected 1 item in iterable, got multiple")
65
+ ) from None
@@ -8,7 +8,7 @@ from .base import (
8
8
  Typename,
9
9
  ensure_json,
10
10
  )
11
- from .utils import IS_PYDANTIC_V2, from_json, pydantic_isinstance, to_json
11
+ from .utils import IS_PYDANTIC_V2, from_json, gql_typename, pydantic_isinstance, to_json
12
12
  from .v1_compat import AliasChoices, computed_field, field_validator, model_validator
13
13
 
14
14
  __all__ = [
@@ -26,4 +26,5 @@ __all__ = [
26
26
  "to_json",
27
27
  "from_json",
28
28
  "ensure_json",
29
+ "gql_typename",
29
30
  ]
wandb/_pydantic/utils.py CHANGED
@@ -20,6 +20,13 @@ IS_PYDANTIC_V2: bool = int(pydantic_major) >= 2
20
20
  BaseModelType: TypeAlias = Type[BaseModel]
21
21
 
22
22
 
23
+ def gql_typename(cls: type[BaseModel]) -> str:
24
+ """Get the GraphQL typename for a Pydantic model."""
25
+ if (field := cls.model_fields.get("typename__")) and (typename := field.default):
26
+ return typename
27
+ raise TypeError(f"Cannot extract GraphQL typename from: {cls.__qualname__!r}.")
28
+
29
+
23
30
  if IS_PYDANTIC_V2:
24
31
  import pydantic_core # pydantic_core is only installed by pydantic v2
25
32
 
wandb/agents/pyagent.py CHANGED
@@ -176,7 +176,7 @@ class Agent:
176
176
  return
177
177
  time.sleep(5)
178
178
 
179
- def _run_jobs_from_queue(self): # noqa:C901
179
+ def _run_jobs_from_queue(self):
180
180
  global _INSTANCES
181
181
  _INSTANCES += 1
182
182
  try:
@@ -239,9 +239,7 @@ class Agent:
239
239
  elif (
240
240
  time.time() - self._start_time < self.FLAPPING_MAX_SECONDS
241
241
  ) and (len(self._exceptions) >= self.FLAPPING_MAX_FAILURES):
242
- msg = "Detected {} failed runs in the first {} seconds, killing sweep.".format(
243
- self.FLAPPING_MAX_FAILURES, self.FLAPPING_MAX_SECONDS
244
- )
242
+ msg = f"Detected {self.FLAPPING_MAX_FAILURES} failed runs in the first {self.FLAPPING_MAX_SECONDS} seconds, killing sweep."
245
243
  logger.error(msg)
246
244
  wandb.termerror(msg)
247
245
  wandb.termlog(
@@ -253,9 +251,7 @@ class Agent:
253
251
  self._max_initial_failures < len(self._exceptions)
254
252
  and len(self._exceptions) >= count
255
253
  ):
256
- msg = "Detected {} failed runs in a row at start, killing sweep.".format(
257
- self._max_initial_failures
258
- )
254
+ msg = f"Detected {self._max_initial_failures} failed runs in a row at start, killing sweep."
259
255
  logger.error(msg)
260
256
  wandb.termerror(msg)
261
257
  wandb.termlog(
@@ -272,13 +268,13 @@ class Agent:
272
268
  wandb.termlog("Ctrl + C detected. Stopping sweep.")
273
269
  self._exit()
274
270
  return
275
- except Exception as e:
271
+ except Exception:
276
272
  if self._exit_flag:
277
273
  logger.debug("Exiting main loop due to exit flag.")
278
274
  wandb.termlog("Sweep Agent: Killed.")
279
275
  return
280
276
  else:
281
- raise e
277
+ raise
282
278
  finally:
283
279
  _INSTANCES -= 1
284
280
 
@@ -305,8 +301,8 @@ class Agent:
305
301
 
306
302
  self._function()
307
303
  wandb.finish()
308
- except KeyboardInterrupt as ki:
309
- raise ki
304
+ except KeyboardInterrupt:
305
+ raise
310
306
  except Exception as e:
311
307
  wandb.finish(exit_code=1)
312
308
  if self._run_status[run_id] == RunStatus.RUNNING:
@@ -320,9 +316,7 @@ class Agent:
320
316
 
321
317
  def run(self):
322
318
  logger.info(
323
- "Starting sweep agent: entity={}, project={}, count={}".format(
324
- self._entity, self._project, self._count
325
- )
319
+ f"Starting sweep agent: entity={self._entity}, project={self._project}, count={self._count}"
326
320
  )
327
321
  self._setup()
328
322
  # self._main_thread = threading.Thread(target=self._run_jobs_from_queue)
@@ -345,7 +339,7 @@ def pyagent(sweep_id, function, entity=None, project=None, count=None):
345
339
  count (int, optional): the number of trials to run.
346
340
  """
347
341
  if not callable(function):
348
- raise Exception("function parameter must be callable!")
342
+ raise TypeError("function parameter must be callable!")
349
343
  agent = Agent(
350
344
  sweep_id,
351
345
  function=function,
wandb/analytics/sentry.py CHANGED
@@ -209,7 +209,6 @@ class Sentry:
209
209
  "sweep_url",
210
210
  "sweep_id",
211
211
  "deployment",
212
- "x_disable_service",
213
212
  "x_require_legacy_service",
214
213
  "launch",
215
214
  "_platform",
@@ -256,7 +255,7 @@ class Sentry:
256
255
 
257
256
  email = tags.get("email")
258
257
  if email:
259
- self.scope.user = {"email": email} # noqa
258
+ self.scope.user = {"email": email}
260
259
 
261
260
  # todo: add back the option to pass general tags see: c645f625d1c1a3db4a6b0e2aa8e924fee101904c (wandb/util.py)
262
261
 
wandb/apis/attrs.py CHANGED
@@ -28,14 +28,13 @@ class Attrs:
28
28
 
29
29
  try:
30
30
  from IPython import display
31
-
32
- display.display(display.HTML(html))
33
- return True
34
-
35
31
  except ImportError:
36
32
  wandb.termwarn(".display() only works in jupyter environments")
37
33
  return False
38
34
 
35
+ display.display(display.HTML(html))
36
+ return True
37
+
39
38
  def to_html(self, *args, **kwargs):
40
39
  return None
41
40
 
@@ -55,7 +55,7 @@ def parallelize(
55
55
  f"Exception: {func=} {args=} {kwargs=} {e=} {filename=} {lineno=}. {traceback_details=}"
56
56
  )
57
57
  if raise_on_error:
58
- raise e
58
+ raise
59
59
 
60
60
  results = []
61
61
  with ThreadPoolExecutor(max_workers) as exc:
@@ -92,8 +92,8 @@ def _check_entry_is_downloable(entry):
92
92
 
93
93
  try:
94
94
  resp = requests.head(url, allow_redirects=True)
95
- except Exception as e:
96
- logger.error(f"Problem validating {entry=}, {e=}")
95
+ except Exception:
96
+ logger.exception(f"Problem validating {entry=}")
97
97
  return False
98
98
 
99
99
  if resp.status_code != 200:
@@ -151,7 +151,7 @@ class WandbRun:
151
151
  if self._parquet_history_paths:
152
152
  rows = self._get_rows_from_parquet_history_paths()
153
153
  else:
154
- logger.warn(
154
+ logger.warning(
155
155
  "No parquet files detected; using scan history (this may not be reliable)"
156
156
  )
157
157
  rows = self.run.scan_history()
@@ -453,13 +453,15 @@ class WandbImporter:
453
453
  try:
454
454
  dst_collection = self.dst_api.artifact_collection(art_type, art_name)
455
455
  except (wandb.CommError, ValueError):
456
- logger.warn(f"Collection doesn't exist {art_type=}, {art_name=}")
456
+ logger.warning(f"Collection doesn't exist {art_type=}, {art_name=}")
457
457
  return
458
458
 
459
459
  try:
460
460
  dst_collection.delete()
461
461
  except (wandb.CommError, ValueError) as e:
462
- logger.warn(f"Collection can't be deleted, {art_type=}, {art_name=}, {e=}")
462
+ logger.warning(
463
+ f"Collection can't be deleted, {art_type=}, {art_name=}, {e=}"
464
+ )
463
465
  return
464
466
 
465
467
  def _import_artifact_sequence(
@@ -475,7 +477,7 @@ class WandbImporter:
475
477
  if not seq.artifacts:
476
478
  # The artifact sequence has no versions. This usually means all artifacts versions were deleted intentionally,
477
479
  # but it can also happen if the sequence represents run history and that run was deleted.
478
- logger.warn(f"Artifact {seq=} has no artifacts, skipping.")
480
+ logger.warning(f"Artifact {seq=} has no artifacts, skipping.")
479
481
  return
480
482
 
481
483
  if namespace is None:
@@ -517,7 +519,7 @@ class WandbImporter:
517
519
 
518
520
  # Could be logged by None (rare) or ValueError
519
521
  if wandb_run is None:
520
- logger.warn(
522
+ logger.warning(
521
523
  f"Run for {art.name=} does not exist (deleted?), using {run_or_dummy=}"
522
524
  )
523
525
  wandb_run = run_or_dummy
@@ -546,10 +548,12 @@ class WandbImporter:
546
548
  retry_arts_func = internal.exp_retry(self._dst_api.artifacts)
547
549
  dst_arts = list(retry_arts_func(seq.type_, seq.name))
548
550
  except wandb.CommError:
549
- logger.warn(f"{seq=} does not exist in dst. Has it already been deleted?")
551
+ logger.warning(
552
+ f"{seq=} does not exist in dst. Has it already been deleted?"
553
+ )
550
554
  return
551
- except TypeError as e:
552
- logger.error(f"Problem getting dst versions (try again later) {e=}")
555
+ except TypeError:
556
+ logger.exception("Problem getting dst versions (try again later).")
553
557
  return
554
558
 
555
559
  for art in dst_arts:
@@ -562,9 +566,9 @@ class WandbImporter:
562
566
  art.delete(delete_aliases=True)
563
567
  except wandb.CommError as e:
564
568
  if "cannot delete system managed artifact" in str(e):
565
- logger.warn("Cannot delete system managed artifact")
569
+ logger.warning("Cannot delete system managed artifact")
566
570
  else:
567
- raise e
571
+ raise
568
572
 
569
573
  def _get_dst_art(
570
574
  self, src_art: Run, entity: Optional[str] = None, project: Optional[str] = None
@@ -701,7 +705,7 @@ class WandbImporter:
701
705
  )
702
706
  except ValueError as e:
703
707
  if "Could not find project" in str(e):
704
- logger.error("Could not find project, does it exist?")
708
+ logger.exception("Could not find project, does it exist?")
705
709
  continue
706
710
 
707
711
  for run in runs:
@@ -732,7 +736,7 @@ class WandbImporter:
732
736
  api.create_project(project, entity)
733
737
  except requests.exceptions.HTTPError as e:
734
738
  if e.response.status_code != 409:
735
- logger.warn(f"Issue upserting {entity=}/{project=}, {e=}")
739
+ logger.warning(f"Issue upserting {entity=}/{project=}, {e=}")
736
740
 
737
741
  logger.debug(f"Upserting report {entity=}, {project=}, {name=}, {title=}")
738
742
  api.client.execute(
@@ -1169,8 +1173,8 @@ class WandbImporter:
1169
1173
  for art in seq:
1170
1174
  try:
1171
1175
  logged_by = _get_run_or_dummy_from_art(art, self.src_api)
1172
- except requests.HTTPError as e:
1173
- logger.error(f"Failed to get run, skipping: {art=}, {e=}")
1176
+ except requests.HTTPError:
1177
+ logger.exception(f"Failed to get run, skipping: {art=}")
1174
1178
  continue
1175
1179
 
1176
1180
  if art.type == "wandb-history" and isinstance(logged_by, _DummyRun):
@@ -1215,9 +1219,10 @@ class WandbImporter:
1215
1219
  art = seq.artifacts[0]
1216
1220
  try:
1217
1221
  logged_by = _get_run_or_dummy_from_art(art, self.src_api)
1218
- except requests.HTTPError as e:
1219
- logger.error(
1220
- f"Validate Artifact http error: {art.entity=}, {art.project=}, {art.name=}, {e=}"
1222
+ except requests.HTTPError:
1223
+ logger.exception(
1224
+ f"Validate Artifact http error: {art.entity=},"
1225
+ f" {art.project=}, {art.name=}"
1221
1226
  )
1222
1227
  continue
1223
1228
 
@@ -1347,8 +1352,8 @@ class WandbImporter:
1347
1352
  types = []
1348
1353
  try:
1349
1354
  types = [t for t in api.artifact_types(ns.path)]
1350
- except Exception as e:
1351
- logger.error(f"Failed to get artifact types {e=}")
1355
+ except Exception:
1356
+ logger.exception("Failed to get artifact types.")
1352
1357
 
1353
1358
  for t in types:
1354
1359
  collections = []
@@ -1359,8 +1364,8 @@ class WandbImporter:
1359
1364
 
1360
1365
  try:
1361
1366
  collections = t.collections()
1362
- except Exception as e:
1363
- logger.error(f"Failed to get artifact collections {e=}")
1367
+ except Exception:
1368
+ logger.exception("Failed to get artifact collections.")
1364
1369
 
1365
1370
  for c in collections:
1366
1371
  if c.is_sequence():
@@ -1471,7 +1476,7 @@ def _read_ndjson(fname: str) -> Optional[pl.DataFrame]:
1471
1476
  return None
1472
1477
  if "error parsing ndjson" in str(e):
1473
1478
  return None
1474
- raise e
1479
+ raise
1475
1480
 
1476
1481
  return df
1477
1482
 
@@ -1482,7 +1487,7 @@ def _get_run_or_dummy_from_art(art: Artifact, api=None):
1482
1487
  try:
1483
1488
  run = art.logged_by()
1484
1489
  except ValueError as e:
1485
- logger.warn(
1490
+ logger.warning(
1486
1491
  f"Can't log artifact because run doesn't exist, {art=}, {run=}, {e=}"
1487
1492
  )
1488
1493
 
@@ -1538,8 +1543,8 @@ def _download_art(art: Artifact, root: str) -> Optional[str]:
1538
1543
  try:
1539
1544
  with patch("click.echo"):
1540
1545
  return art.download(root=root, skip_cache=True)
1541
- except Exception as e:
1542
- logger.error(f"Error downloading artifact {art=}, {e=}")
1546
+ except Exception:
1547
+ logger.exception(f"Error downloading artifact {art=}")
1543
1548
 
1544
1549
 
1545
1550
  def _clone_art(art: Artifact, root: Optional[str] = None):
wandb/apis/normalize.py CHANGED
@@ -61,8 +61,8 @@ def normalize_exceptions(func: _F) -> _F:
61
61
  raise CommError(message, err.last_exception).with_traceback(
62
62
  sys.exc_info()[2]
63
63
  )
64
- except Error as err:
65
- raise err
64
+ except Error:
65
+ raise
66
66
  except Exception as err:
67
67
  # gql raises server errors with dict's as strings...
68
68
  if len(err.args) > 0:
@@ -22,6 +22,7 @@ from wandb.apis.public.jobs import (
22
22
  )
23
23
  from wandb.apis.public.projects import PROJECT_FRAGMENT, Project, Projects
24
24
  from wandb.apis.public.query_generator import QueryGenerator
25
+ from wandb.apis.public.registries.registry import Registry
25
26
  from wandb.apis.public.reports import (
26
27
  BetaReport,
27
28
  PanelMetricsHelper,
wandb/apis/public/api.py CHANGED
@@ -103,7 +103,9 @@ class RetryingClient:
103
103
  check_retry_fn=util.no_retry_auth,
104
104
  retryable_exceptions=(RetryError, requests.RequestException),
105
105
  )
106
- def execute(self, *args, **kwargs): # noqa: D102 # User not encouraged to use this class directly
106
+ def execute(
107
+ self, *args, **kwargs
108
+ ): # User not encouraged to use this class directly
107
109
  try:
108
110
  return self._client.execute(*args, **kwargs)
109
111
  except requests.exceptions.ReadTimeout:
@@ -122,10 +124,12 @@ class RetryingClient:
122
124
  self._server_info = self.execute(self.INFO_QUERY).get("serverInfo")
123
125
  return self._server_info
124
126
 
125
- def version_supported(self, min_version: str) -> bool: # noqa: D102 # User not encouraged to use this class directly
126
- from wandb.util import parse_version
127
+ def version_supported(
128
+ self, min_version: str
129
+ ) -> bool: # User not encouraged to use this class directly
130
+ from packaging.version import parse
127
131
 
128
- return parse_version(min_version) <= parse_version(
132
+ return parse(min_version) <= parse(
129
133
  self.server_info["cliVersionInfo"]["max_cli_version"]
130
134
  )
131
135
 
@@ -272,20 +276,21 @@ class Api:
272
276
  api_key: Optional[str] = None,
273
277
  ) -> None:
274
278
  self.settings = InternalApi().settings()
279
+
275
280
  _overrides = overrides or {}
276
- self._api_key = api_key
277
- if self.api_key is None and _thread_local_api_settings.cookies is None:
278
- wandb.login(host=_overrides.get("base_url"))
279
281
  self.settings.update(_overrides)
282
+ self.settings["base_url"] = self.settings["base_url"].rstrip("/")
283
+ if "organization" in _overrides:
284
+ self.settings["organization"] = _overrides["organization"]
280
285
  if "username" in _overrides and "entity" not in _overrides:
281
286
  wandb.termwarn(
282
287
  'Passing "username" to Api is deprecated. please use "entity" instead.'
283
288
  )
284
289
  self.settings["entity"] = _overrides["username"]
285
- self.settings["base_url"] = self.settings["base_url"].rstrip("/")
286
290
 
287
- if "organization" in _overrides:
288
- self.settings["organization"] = _overrides["organization"]
291
+ self._api_key = api_key
292
+ if self.api_key is None and _thread_local_api_settings.cookies is None:
293
+ wandb.login(host=_overrides.get("base_url"))
289
294
 
290
295
  self._viewer = None
291
296
  self._projects = {}
@@ -318,7 +323,6 @@ class Api:
318
323
  )
319
324
  )
320
325
  self._client = RetryingClient(self._base_client)
321
- self._server_features_cache: Optional[dict[str, bool]] = None
322
326
 
323
327
  def create_project(self, name: str, entity: str) -> None:
324
328
  """Create a new project.
@@ -1464,10 +1468,11 @@ class Api:
1464
1468
  """
1465
1469
  try:
1466
1470
  self._artifact(name, type)
1467
- return True
1468
1471
  except wandb.errors.CommError:
1469
1472
  return False
1470
1473
 
1474
+ return True
1475
+
1471
1476
  @normalize_exceptions
1472
1477
  def artifact_collection_exists(self, name: str, type: str) -> bool:
1473
1478
  """Return whether an artifact collection exists within a specified project and entity.
@@ -1483,10 +1488,11 @@ class Api:
1483
1488
  """
1484
1489
  try:
1485
1490
  self.artifact_collection(type, name)
1486
- return True
1487
1491
  except wandb.errors.CommError:
1488
1492
  return False
1489
1493
 
1494
+ return True
1495
+
1490
1496
  def registries(
1491
1497
  self,
1492
1498
  organization: Optional[str] = None,
@@ -1539,9 +1545,7 @@ class Api:
1539
1545
  Returns:
1540
1546
  A registry iterator.
1541
1547
  """
1542
- if not InternalApi()._check_server_feature_with_fallback(
1543
- ServerFeature.ARTIFACT_REGISTRY_SEARCH
1544
- ):
1548
+ if not InternalApi()._server_supports(ServerFeature.ARTIFACT_REGISTRY_SEARCH):
1545
1549
  raise RuntimeError(
1546
1550
  "Registry search API is not enabled on this wandb server version. "
1547
1551
  "Please upgrade your server version or contact support at support@wandb.com."
@@ -1577,9 +1581,7 @@ class Api:
1577
1581
  registry.save()
1578
1582
  ```
1579
1583
  """
1580
- if not InternalApi()._check_server_feature_with_fallback(
1581
- ServerFeature.ARTIFACT_REGISTRY_SEARCH
1582
- ):
1584
+ if not InternalApi()._server_supports(ServerFeature.ARTIFACT_REGISTRY_SEARCH):
1583
1585
  raise RuntimeError(
1584
1586
  "api.registry() is not enabled on this wandb server version. "
1585
1587
  "Please upgrade your server version or contact support at support@wandb.com."
@@ -1635,7 +1637,7 @@ class Api:
1635
1637
  )
1636
1638
  ```
1637
1639
  """
1638
- if not InternalApi()._check_server_feature_with_fallback(
1640
+ if not InternalApi()._server_supports(
1639
1641
  ServerFeature.INCLUDE_ARTIFACT_TYPES_IN_REGISTRY_CREATION
1640
1642
  ):
1641
1643
  raise RuntimeError(
@@ -1783,19 +1785,18 @@ class Api:
1783
1785
  ALWAYS_SUPPORTED_EVENTS,
1784
1786
  )
1785
1787
 
1786
- server_features = InternalApi()._server_features()
1787
- return bool(
1788
- (
1789
- (event is None)
1790
- or (event in ALWAYS_SUPPORTED_EVENTS)
1791
- or server_features.get(f"AUTOMATION_EVENT_{event.value}")
1792
- )
1793
- and (
1794
- (action is None)
1795
- or (action in ALWAYS_SUPPORTED_ACTIONS)
1796
- or server_features.get(f"AUTOMATION_ACTION_{action.value}")
1797
- )
1788
+ api = InternalApi()
1789
+ supports_event = (
1790
+ (event is None)
1791
+ or (event in ALWAYS_SUPPORTED_EVENTS)
1792
+ or api._server_supports(f"AUTOMATION_EVENT_{event.value}")
1793
+ )
1794
+ supports_action = (
1795
+ (action is None)
1796
+ or (action in ALWAYS_SUPPORTED_ACTIONS)
1797
+ or api._server_supports(f"AUTOMATION_ACTION_{action.value}")
1798
1798
  )
1799
+ return supports_event and supports_action
1799
1800
 
1800
1801
  def _omitted_automation_fragments(self) -> Set[str]:
1801
1802
  """Returns the names of unsupported automation-related fragments.
@@ -1938,6 +1939,7 @@ class Api:
1938
1939
  iterator = filter(lambda x: x.name == name, iterator)
1939
1940
  yield from iterator
1940
1941
 
1942
+ @normalize_exceptions
1941
1943
  def create_automation(
1942
1944
  self,
1943
1945
  obj: "NewAutomation",
@@ -2044,6 +2046,7 @@ class Api:
2044
2046
 
2045
2047
  return Automation.model_validate(result.trigger)
2046
2048
 
2049
+ @normalize_exceptions
2047
2050
  def update_automation(
2048
2051
  self,
2049
2052
  obj: "Automation",
@@ -2151,7 +2154,7 @@ class Api:
2151
2154
 
2152
2155
  # Not a (known) recoverable HTTP error
2153
2156
  wandb.termerror(f"Got response status {status!r}: {e.response.text!r}")
2154
- raise e
2157
+ raise
2155
2158
 
2156
2159
  try:
2157
2160
  result = UpdateAutomation.model_validate(data).result
@@ -2165,6 +2168,7 @@ class Api:
2165
2168
 
2166
2169
  return Automation.model_validate(result.trigger)
2167
2170
 
2171
+ @normalize_exceptions
2168
2172
  def delete_automation(self, obj: Union["Automation", str]) -> Literal[True]:
2169
2173
  """Delete an automation.
2170
2174