wandb 0.18.0rc1__py3-none-macosx_11_0_arm64.whl → 0.18.2__py3-none-macosx_11_0_arm64.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. wandb/__init__.py +4 -4
  2. wandb/__init__.pyi +67 -12
  3. wandb/apis/internal.py +3 -0
  4. wandb/apis/public/api.py +128 -2
  5. wandb/apis/public/artifacts.py +11 -7
  6. wandb/apis/public/jobs.py +8 -0
  7. wandb/apis/public/runs.py +18 -5
  8. wandb/bin/apple_gpu_stats +0 -0
  9. wandb/bin/wandb-core +0 -0
  10. wandb/cli/cli.py +0 -5
  11. wandb/data_types.py +9 -2019
  12. wandb/env.py +0 -5
  13. wandb/errors/__init__.py +11 -40
  14. wandb/errors/errors.py +37 -0
  15. wandb/errors/warnings.py +2 -0
  16. wandb/{sklearn → integration/sklearn}/calculate/calibration_curves.py +7 -7
  17. wandb/{sklearn → integration/sklearn}/calculate/class_proportions.py +1 -1
  18. wandb/{sklearn → integration/sklearn}/calculate/confusion_matrix.py +3 -2
  19. wandb/{sklearn → integration/sklearn}/calculate/elbow_curve.py +6 -6
  20. wandb/{sklearn → integration/sklearn}/calculate/learning_curve.py +2 -2
  21. wandb/{sklearn → integration/sklearn}/calculate/outlier_candidates.py +2 -2
  22. wandb/{sklearn → integration/sklearn}/calculate/residuals.py +8 -8
  23. wandb/{sklearn → integration/sklearn}/calculate/silhouette.py +2 -2
  24. wandb/{sklearn → integration/sklearn}/calculate/summary_metrics.py +2 -2
  25. wandb/{sklearn → integration/sklearn}/plot/classifier.py +5 -5
  26. wandb/{sklearn → integration/sklearn}/plot/clusterer.py +10 -6
  27. wandb/{sklearn → integration/sklearn}/plot/regressor.py +5 -5
  28. wandb/{sklearn → integration/sklearn}/plot/shared.py +3 -3
  29. wandb/{sklearn → integration/sklearn}/utils.py +8 -8
  30. wandb/integration/tensorboard/log.py +1 -1
  31. wandb/{wandb_torch.py → integration/torch/wandb_torch.py} +36 -32
  32. wandb/old/core.py +2 -80
  33. wandb/plot/bar.py +7 -4
  34. wandb/plot/confusion_matrix.py +5 -4
  35. wandb/plot/histogram.py +7 -4
  36. wandb/plot/line.py +7 -4
  37. wandb/proto/v3/wandb_base_pb2.py +2 -1
  38. wandb/proto/v3/wandb_internal_pb2.py +2 -1
  39. wandb/proto/v3/wandb_server_pb2.py +2 -1
  40. wandb/proto/v3/wandb_settings_pb2.py +3 -2
  41. wandb/proto/v3/wandb_telemetry_pb2.py +2 -1
  42. wandb/proto/v4/wandb_base_pb2.py +2 -1
  43. wandb/proto/v4/wandb_internal_pb2.py +2 -1
  44. wandb/proto/v4/wandb_server_pb2.py +2 -1
  45. wandb/proto/v4/wandb_settings_pb2.py +3 -2
  46. wandb/proto/v4/wandb_telemetry_pb2.py +2 -1
  47. wandb/proto/v5/wandb_base_pb2.py +3 -2
  48. wandb/proto/v5/wandb_internal_pb2.py +3 -2
  49. wandb/proto/v5/wandb_server_pb2.py +3 -2
  50. wandb/proto/v5/wandb_settings_pb2.py +4 -3
  51. wandb/proto/v5/wandb_telemetry_pb2.py +3 -2
  52. wandb/sdk/artifacts/_validators.py +48 -3
  53. wandb/sdk/artifacts/artifact.py +157 -183
  54. wandb/sdk/artifacts/artifact_file_cache.py +13 -11
  55. wandb/sdk/artifacts/artifact_instance_cache.py +4 -2
  56. wandb/sdk/artifacts/artifact_manifest.py +13 -11
  57. wandb/sdk/artifacts/artifact_manifest_entry.py +24 -22
  58. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +9 -7
  59. wandb/sdk/artifacts/artifact_saver.py +27 -25
  60. wandb/sdk/artifacts/exceptions.py +26 -25
  61. wandb/sdk/artifacts/storage_handler.py +11 -9
  62. wandb/sdk/artifacts/storage_handlers/azure_handler.py +16 -14
  63. wandb/sdk/artifacts/storage_handlers/gcs_handler.py +15 -13
  64. wandb/sdk/artifacts/storage_handlers/http_handler.py +15 -14
  65. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +10 -8
  66. wandb/sdk/artifacts/storage_handlers/multi_handler.py +14 -12
  67. wandb/sdk/artifacts/storage_handlers/s3_handler.py +19 -19
  68. wandb/sdk/artifacts/storage_handlers/tracking_handler.py +10 -8
  69. wandb/sdk/artifacts/storage_handlers/wb_artifact_handler.py +12 -10
  70. wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +9 -7
  71. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +31 -29
  72. wandb/sdk/artifacts/storage_policy.py +20 -20
  73. wandb/sdk/backend/backend.py +8 -26
  74. wandb/sdk/data_types/audio.py +165 -0
  75. wandb/sdk/data_types/base_types/wb_value.py +1 -3
  76. wandb/sdk/data_types/bokeh.py +70 -0
  77. wandb/sdk/data_types/graph.py +405 -0
  78. wandb/sdk/data_types/image.py +156 -0
  79. wandb/sdk/data_types/table.py +1204 -0
  80. wandb/sdk/data_types/trace_tree.py +2 -2
  81. wandb/sdk/data_types/utils.py +49 -0
  82. wandb/sdk/data_types/video.py +2 -2
  83. wandb/sdk/interface/interface.py +0 -24
  84. wandb/sdk/interface/interface_shared.py +0 -12
  85. wandb/sdk/internal/handler.py +0 -10
  86. wandb/sdk/internal/internal_api.py +71 -0
  87. wandb/sdk/internal/sender.py +0 -43
  88. wandb/sdk/internal/tb_watcher.py +1 -1
  89. wandb/sdk/lib/_settings_toposort_generated.py +1 -0
  90. wandb/sdk/lib/hashutil.py +34 -12
  91. wandb/sdk/lib/service_connection.py +216 -0
  92. wandb/sdk/lib/service_token.py +94 -0
  93. wandb/sdk/lib/sock_client.py +7 -3
  94. wandb/sdk/service/server.py +2 -5
  95. wandb/sdk/service/service.py +2 -31
  96. wandb/sdk/service/streams.py +0 -7
  97. wandb/sdk/wandb_init.py +42 -25
  98. wandb/sdk/wandb_run.py +18 -159
  99. wandb/sdk/wandb_settings.py +2 -0
  100. wandb/sdk/wandb_setup.py +25 -16
  101. wandb/sdk/wandb_sync.py +9 -3
  102. wandb/sdk/wandb_watch.py +31 -15
  103. wandb/sklearn.py +35 -0
  104. wandb/util.py +14 -3
  105. {wandb-0.18.0rc1.dist-info → wandb-0.18.2.dist-info}/METADATA +6 -5
  106. {wandb-0.18.0rc1.dist-info → wandb-0.18.2.dist-info}/RECORD +115 -111
  107. wandb/sdk/internal/update.py +0 -113
  108. wandb/sdk/lib/console.py +0 -39
  109. wandb/sdk/service/service_base.py +0 -50
  110. wandb/sdk/service/service_sock.py +0 -70
  111. wandb/sdk/wandb_manager.py +0 -232
  112. /wandb/{sklearn → integration/sklearn}/__init__.py +0 -0
  113. /wandb/{sklearn → integration/sklearn}/calculate/__init__.py +0 -0
  114. /wandb/{sklearn → integration/sklearn}/calculate/decision_boundaries.py +0 -0
  115. /wandb/{sklearn → integration/sklearn}/calculate/feature_importances.py +0 -0
  116. /wandb/{sklearn → integration/sklearn}/plot/__init__.py +0 -0
  117. /wandb/{sdk/lib → plot}/viz.py +0 -0
  118. {wandb-0.18.0rc1.dist-info → wandb-0.18.2.dist-info}/WHEEL +0 -0
  119. {wandb-0.18.0rc1.dist-info → wandb-0.18.2.dist-info}/entry_points.txt +0 -0
  120. {wandb-0.18.0rc1.dist-info → wandb-0.18.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,7 @@
1
1
  """Artifact class."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import atexit
4
6
  import concurrent.futures
5
7
  import contextlib
@@ -17,21 +19,7 @@ from copy import copy
17
19
  from datetime import datetime, timedelta
18
20
  from functools import partial
19
21
  from pathlib import PurePosixPath
20
- from typing import (
21
- IO,
22
- TYPE_CHECKING,
23
- Any,
24
- Dict,
25
- Generator,
26
- List,
27
- Optional,
28
- Sequence,
29
- Set,
30
- Tuple,
31
- Type,
32
- Union,
33
- cast,
34
- )
22
+ from typing import IO, TYPE_CHECKING, Any, Dict, Iterator, Sequence, Type, cast
35
23
 
36
24
  from wandb.sdk.artifacts.storage_handlers.gcs_handler import _GCSIsADirectoryError
37
25
 
@@ -50,7 +38,12 @@ from wandb.apis.normalize import normalize_exceptions
50
38
  from wandb.apis.public import ArtifactCollection, ArtifactFiles, RetryingClient, Run
51
39
  from wandb.data_types import WBValue
52
40
  from wandb.errors.term import termerror, termlog, termwarn
53
- from wandb.sdk.artifacts._validators import validate_aliases, validate_tags
41
+ from wandb.sdk.artifacts._validators import (
42
+ ensure_logged,
43
+ ensure_not_finalized,
44
+ validate_aliases,
45
+ validate_tags,
46
+ )
54
47
  from wandb.sdk.artifacts.artifact_download_logger import ArtifactDownloadLogger
55
48
  from wandb.sdk.artifacts.artifact_instance_cache import artifact_instance_cache
56
49
  from wandb.sdk.artifacts.artifact_manifest import ArtifactManifest
@@ -60,11 +53,7 @@ from wandb.sdk.artifacts.artifact_manifests.artifact_manifest_v1 import (
60
53
  )
61
54
  from wandb.sdk.artifacts.artifact_state import ArtifactState
62
55
  from wandb.sdk.artifacts.artifact_ttl import ArtifactTTL
63
- from wandb.sdk.artifacts.exceptions import (
64
- ArtifactFinalizedError,
65
- ArtifactNotLoggedError,
66
- WaitTimeoutError,
67
- )
56
+ from wandb.sdk.artifacts.exceptions import ArtifactNotLoggedError, WaitTimeoutError
68
57
  from wandb.sdk.artifacts.staging import get_staging_dir
69
58
  from wandb.sdk.artifacts.storage_layout import StorageLayout
70
59
  from wandb.sdk.artifacts.storage_policies import WANDB_STORAGE_POLICY
@@ -129,10 +118,10 @@ class Artifact:
129
118
  self,
130
119
  name: str,
131
120
  type: str,
132
- description: Optional[str] = None,
133
- metadata: Optional[Dict[str, Any]] = None,
121
+ description: str | None = None,
122
+ metadata: dict[str, Any] | None = None,
134
123
  incremental: bool = False,
135
- use_as: Optional[str] = None,
124
+ use_as: str | None = None,
136
125
  ) -> None:
137
126
  if not re.match(r"^[a-zA-Z0-9_\-.]+$", name):
138
127
  raise ValueError(
@@ -148,55 +137,53 @@ class Artifact:
148
137
  termwarn("Using experimental arg `incremental`")
149
138
 
150
139
  # Internal.
151
- self._client: Optional[RetryingClient] = None
140
+ self._client: RetryingClient | None = None
152
141
 
153
142
  storage_policy_cls = StoragePolicy.lookup_by_name(WANDB_STORAGE_POLICY)
154
143
  layout = StorageLayout.V1 if env.get_use_v1_artifacts() else StorageLayout.V2
155
144
  policy_config = {"storageLayout": layout}
156
145
  self._storage_policy = storage_policy_cls.from_config(config=policy_config)
157
146
 
158
- self._tmp_dir: Optional[tempfile.TemporaryDirectory] = None
159
- self._added_objs: Dict[
160
- int, Tuple[data_types.WBValue, ArtifactManifestEntry]
161
- ] = {}
162
- self._added_local_paths: Dict[str, ArtifactManifestEntry] = {}
163
- self._save_future: Optional[MessageFuture] = None
164
- self._download_roots: Set[str] = set()
147
+ self._tmp_dir: tempfile.TemporaryDirectory | None = None
148
+ self._added_objs: dict[int, tuple[WBValue, ArtifactManifestEntry]] = {}
149
+ self._added_local_paths: dict[str, ArtifactManifestEntry] = {}
150
+ self._save_future: MessageFuture | None = None
151
+ self._download_roots: set[str] = set()
165
152
  # Set by new_draft(), otherwise the latest artifact will be used as the base.
166
- self._base_id: Optional[str] = None
153
+ self._base_id: str | None = None
167
154
  # Properties.
168
- self._id: Optional[str] = None
155
+ self._id: str | None = None
169
156
  self._client_id: str = runid.generate_id(128)
170
157
  self._sequence_client_id: str = runid.generate_id(128)
171
- self._entity: Optional[str] = None
172
- self._project: Optional[str] = None
158
+ self._entity: str | None = None
159
+ self._project: str | None = None
173
160
  self._name: str = name # includes version after saving
174
- self._version: Optional[str] = None
175
- self._source_entity: Optional[str] = None
176
- self._source_project: Optional[str] = None
161
+ self._version: str | None = None
162
+ self._source_entity: str | None = None
163
+ self._source_project: str | None = None
177
164
  self._source_name: str = name # includes version after saving
178
- self._source_version: Optional[str] = None
165
+ self._source_version: str | None = None
179
166
  self._type: str = type
180
- self._description: Optional[str] = description
167
+ self._description: str | None = description
181
168
  self._metadata: dict = self._normalize_metadata(metadata)
182
- self._ttl_duration_seconds: Optional[int] = None
169
+ self._ttl_duration_seconds: int | None = None
183
170
  self._ttl_is_inherited: bool = True
184
171
  self._ttl_changed: bool = False
185
- self._aliases: List[str] = []
186
- self._saved_aliases: List[str] = []
187
- self._tags: List[str] = []
188
- self._saved_tags: List[str] = []
189
- self._distributed_id: Optional[str] = None
172
+ self._aliases: list[str] = []
173
+ self._saved_aliases: list[str] = []
174
+ self._tags: list[str] = []
175
+ self._saved_tags: list[str] = []
176
+ self._distributed_id: str | None = None
190
177
  self._incremental: bool = incremental
191
- self._use_as: Optional[str] = use_as
178
+ self._use_as: str | None = use_as
192
179
  self._state: ArtifactState = ArtifactState.PENDING
193
- self._manifest: Optional[ArtifactManifest] = ArtifactManifestV1(
180
+ self._manifest: ArtifactManifest | None = ArtifactManifestV1(
194
181
  self._storage_policy
195
182
  )
196
- self._commit_hash: Optional[str] = None
197
- self._file_count: Optional[int] = None
198
- self._created_at: Optional[str] = None
199
- self._updated_at: Optional[str] = None
183
+ self._commit_hash: str | None = None
184
+ self._file_count: int | None = None
185
+ self._created_at: str | None = None
186
+ self._updated_at: str | None = None
200
187
  self._final: bool = False
201
188
 
202
189
  # Cache.
@@ -206,7 +193,7 @@ class Artifact:
206
193
  return f"<Artifact {self.id or self.name}>"
207
194
 
208
195
  @classmethod
209
- def _from_id(cls, artifact_id: str, client: RetryingClient) -> Optional["Artifact"]:
196
+ def _from_id(cls, artifact_id: str, client: RetryingClient) -> Artifact | None:
210
197
  artifact = artifact_instance_cache.get(artifact_id)
211
198
  if artifact is not None:
212
199
  return artifact
@@ -245,7 +232,7 @@ class Artifact:
245
232
  @classmethod
246
233
  def _from_name(
247
234
  cls, entity: str, project: str, name: str, client: RetryingClient
248
- ) -> "Artifact":
235
+ ) -> Artifact:
249
236
  query = gql(
250
237
  """
251
238
  query ArtifactByName(
@@ -284,9 +271,9 @@ class Artifact:
284
271
  entity: str,
285
272
  project: str,
286
273
  name: str,
287
- attrs: Dict[str, Any],
274
+ attrs: dict[str, Any],
288
275
  client: RetryingClient,
289
- ) -> "Artifact":
276
+ ) -> Artifact:
290
277
  # Placeholder is required to skip validation.
291
278
  artifact = cls("placeholder", type="placeholder")
292
279
  artifact._client = client
@@ -352,7 +339,8 @@ class Artifact:
352
339
  artifact_instance_cache[artifact.id] = artifact
353
340
  return artifact
354
341
 
355
- def new_draft(self) -> "Artifact":
342
+ @ensure_logged
343
+ def new_draft(self) -> Artifact:
356
344
  """Create a new draft artifact with the same content as this committed artifact.
357
345
 
358
346
  The artifact returned can be extended or modified and logged as a new version.
@@ -363,8 +351,6 @@ class Artifact:
363
351
  Raises:
364
352
  ArtifactNotLoggedError: If the artifact is not logged.
365
353
  """
366
- self._ensure_logged("new_draft")
367
-
368
354
  # Name, _entity and _project are set to the *source* name/entity/project:
369
355
  # if this artifact is saved it must be saved to the source sequence.
370
356
  artifact = Artifact(self.source_name.split(":")[0], self.type)
@@ -389,7 +375,7 @@ class Artifact:
389
375
  # Properties (Python Class managed attributes).
390
376
 
391
377
  @property
392
- def id(self) -> Optional[str]:
378
+ def id(self) -> str | None:
393
379
  """The artifact's ID."""
394
380
  if self.is_draft():
395
381
  return None
@@ -397,16 +383,16 @@ class Artifact:
397
383
  return self._id
398
384
 
399
385
  @property
386
+ @ensure_logged
400
387
  def entity(self) -> str:
401
388
  """The name of the entity of the secondary (portfolio) artifact collection."""
402
- self._ensure_logged("entity")
403
389
  assert self._entity is not None
404
390
  return self._entity
405
391
 
406
392
  @property
393
+ @ensure_logged
407
394
  def project(self) -> str:
408
395
  """The name of the project of the secondary (portfolio) artifact collection."""
409
- self._ensure_logged("project")
410
396
  assert self._project is not None
411
397
  return self._project
412
398
 
@@ -425,13 +411,14 @@ class Artifact:
425
411
  return f"{self.entity}/{self.project}/{self.name}"
426
412
 
427
413
  @property
414
+ @ensure_logged
428
415
  def version(self) -> str:
429
416
  """The artifact's version in its secondary (portfolio) collection."""
430
- self._ensure_logged("version")
431
417
  assert self._version is not None
432
418
  return self._version
433
419
 
434
420
  @property
421
+ @ensure_logged
435
422
  def collection(self) -> ArtifactCollection:
436
423
  """The collection this artifact was retrieved from.
437
424
 
@@ -441,23 +428,22 @@ class Artifact:
441
428
  that an artifact version originated from. The collection
442
429
  that an artifact originates from is known as the source sequence.
443
430
  """
444
- self._ensure_logged("collection")
445
431
  base_name = self.name.split(":")[0]
446
432
  return ArtifactCollection(
447
433
  self._client, self.entity, self.project, base_name, self.type
448
434
  )
449
435
 
450
436
  @property
437
+ @ensure_logged
451
438
  def source_entity(self) -> str:
452
439
  """The name of the entity of the primary (sequence) artifact collection."""
453
- self._ensure_logged("source_entity")
454
440
  assert self._source_entity is not None
455
441
  return self._source_entity
456
442
 
457
443
  @property
444
+ @ensure_logged
458
445
  def source_project(self) -> str:
459
446
  """The name of the project of the primary (sequence) artifact collection."""
460
- self._ensure_logged("source_project")
461
447
  assert self._source_project is not None
462
448
  return self._source_project
463
449
 
@@ -476,19 +462,19 @@ class Artifact:
476
462
  return f"{self.source_entity}/{self.source_project}/{self.source_name}"
477
463
 
478
464
  @property
465
+ @ensure_logged
479
466
  def source_version(self) -> str:
480
467
  """The artifact's version in its primary (sequence) collection.
481
468
 
482
469
  A string with the format "v{number}".
483
470
  """
484
- self._ensure_logged("source_version")
485
471
  assert self._source_version is not None
486
472
  return self._source_version
487
473
 
488
474
  @property
475
+ @ensure_logged
489
476
  def source_collection(self) -> ArtifactCollection:
490
477
  """The artifact's primary (sequence) collection."""
491
- self._ensure_logged("source_collection")
492
478
  base_name = self.source_name.split(":")[0]
493
479
  return ArtifactCollection(
494
480
  self._client, self.source_entity, self.source_project, base_name, self.type
@@ -500,12 +486,12 @@ class Artifact:
500
486
  return self._type
501
487
 
502
488
  @property
503
- def description(self) -> Optional[str]:
489
+ def description(self) -> str | None:
504
490
  """A description of the artifact."""
505
491
  return self._description
506
492
 
507
493
  @description.setter
508
- def description(self, description: Optional[str]) -> None:
494
+ def description(self, description: str | None) -> None:
509
495
  """Set the description of the artifact.
510
496
 
511
497
  For model or dataset Artifacts, add documentation for your
@@ -540,7 +526,7 @@ class Artifact:
540
526
  self._metadata = self._normalize_metadata(metadata)
541
527
 
542
528
  @property
543
- def ttl(self) -> Union[timedelta, None]:
529
+ def ttl(self) -> timedelta | None:
544
530
  """The time-to-live (TTL) policy of an artifact.
545
531
 
546
532
  Artifacts are deleted shortly after a TTL policy's duration passes.
@@ -554,13 +540,13 @@ class Artifact:
554
540
  ArtifactNotLoggedError: Unable to fetch inherited TTL if the artifact has not been logged or saved
555
541
  """
556
542
  if self._ttl_is_inherited and (self.is_draft() or self._ttl_changed):
557
- raise ArtifactNotLoggedError(self, "ttl")
543
+ raise ArtifactNotLoggedError(f"{type(self).__name__}.ttl", self)
558
544
  if self._ttl_duration_seconds is None:
559
545
  return None
560
546
  return timedelta(seconds=self._ttl_duration_seconds)
561
547
 
562
548
  @ttl.setter
563
- def ttl(self, ttl: Union[timedelta, ArtifactTTL, None]) -> None:
549
+ def ttl(self, ttl: timedelta | ArtifactTTL | None) -> None:
564
550
  """The time-to-live (TTL) policy of an artifact.
565
551
 
566
552
  Artifacts are deleted shortly after a TTL policy's duration passes.
@@ -595,7 +581,8 @@ class Artifact:
595
581
  self._ttl_duration_seconds = int(ttl.total_seconds())
596
582
 
597
583
  @property
598
- def aliases(self) -> List[str]:
584
+ @ensure_logged
585
+ def aliases(self) -> list[str]:
599
586
  """List of one or more semantically-friendly references or identifying "nicknames" assigned to an artifact version.
600
587
 
601
588
  Aliases are mutable references that you can programmatically reference.
@@ -603,33 +590,32 @@ class Artifact:
603
590
  See [Create new artifact versions](https://docs.wandb.ai/guides/artifacts/create-a-new-artifact-version)
604
591
  for more information.
605
592
  """
606
- self._ensure_logged("aliases")
607
593
  return self._aliases
608
594
 
609
595
  @aliases.setter
610
- def aliases(self, aliases: List[str]) -> None:
596
+ @ensure_logged
597
+ def aliases(self, aliases: list[str]) -> None:
611
598
  """Set the aliases associated with this artifact."""
612
- self._ensure_logged("aliases")
613
599
  self._aliases = validate_aliases(aliases)
614
600
 
615
601
  @property
616
- def tags(self) -> List[str]:
602
+ @ensure_logged
603
+ def tags(self) -> list[str]:
617
604
  """List of one or more tags assigned to this artifact version."""
618
- self._ensure_logged("tags")
619
605
  return self._tags
620
606
 
621
607
  @tags.setter
622
- def tags(self, tags: List[str]) -> None:
608
+ @ensure_logged
609
+ def tags(self, tags: list[str]) -> None:
623
610
  """Set the tags associated with this artifact."""
624
- self._ensure_logged("tags")
625
611
  self._tags = validate_tags(tags)
626
612
 
627
613
  @property
628
- def distributed_id(self) -> Optional[str]:
614
+ def distributed_id(self) -> str | None:
629
615
  return self._distributed_id
630
616
 
631
617
  @distributed_id.setter
632
- def distributed_id(self, distributed_id: Optional[str]) -> None:
618
+ def distributed_id(self, distributed_id: str | None) -> None:
633
619
  self._distributed_id = distributed_id
634
620
 
635
621
  @property
@@ -637,7 +623,7 @@ class Artifact:
637
623
  return self._incremental
638
624
 
639
625
  @property
640
- def use_as(self) -> Optional[str]:
626
+ def use_as(self) -> str | None:
641
627
  return self._use_as
642
628
 
643
629
  @property
@@ -708,30 +694,30 @@ class Artifact:
708
694
  return total_size
709
695
 
710
696
  @property
697
+ @ensure_logged
711
698
  def commit_hash(self) -> str:
712
699
  """The hash returned when this artifact was committed."""
713
- self._ensure_logged("commit_hash")
714
700
  assert self._commit_hash is not None
715
701
  return self._commit_hash
716
702
 
717
703
  @property
704
+ @ensure_logged
718
705
  def file_count(self) -> int:
719
706
  """The number of files (including references)."""
720
- self._ensure_logged("file_count")
721
707
  assert self._file_count is not None
722
708
  return self._file_count
723
709
 
724
710
  @property
711
+ @ensure_logged
725
712
  def created_at(self) -> str:
726
713
  """Timestamp when the artifact was created."""
727
- self._ensure_logged("created_at")
728
714
  assert self._created_at is not None
729
715
  return self._created_at
730
716
 
731
717
  @property
718
+ @ensure_logged
732
719
  def updated_at(self) -> str:
733
720
  """The time when the artifact was last updated."""
734
- self._ensure_logged("updated_at")
735
721
  assert self._created_at is not None
736
722
  return self._updated_at or self._created_at
737
723
 
@@ -747,14 +733,6 @@ class Artifact:
747
733
  """
748
734
  self._final = True
749
735
 
750
- def _ensure_can_add(self) -> None:
751
- if self._final:
752
- raise ArtifactFinalizedError(artifact=self)
753
-
754
- def _ensure_logged(self, attr: Optional[str] = None) -> None:
755
- if self.is_draft():
756
- raise ArtifactNotLoggedError(self, attr)
757
-
758
736
  def is_draft(self) -> bool:
759
737
  """Check if artifact is not saved.
760
738
 
@@ -767,8 +745,8 @@ class Artifact:
767
745
 
768
746
  def save(
769
747
  self,
770
- project: Optional[str] = None,
771
- settings: Optional["wandb.sdk.wandb_settings.Settings"] = None,
748
+ project: str | None = None,
749
+ settings: wandb.Settings | None = None,
772
750
  ) -> None:
773
751
  """Persist any changes made to the artifact.
774
752
 
@@ -807,12 +785,12 @@ class Artifact:
807
785
  wandb.run.log_artifact(self)
808
786
 
809
787
  def _set_save_future(
810
- self, save_future: "MessageFuture", client: RetryingClient
788
+ self, save_future: MessageFuture, client: RetryingClient
811
789
  ) -> None:
812
790
  self._save_future = save_future
813
791
  self._client = client
814
792
 
815
- def wait(self, timeout: Optional[int] = None) -> "Artifact":
793
+ def wait(self, timeout: int | None = None) -> Artifact:
816
794
  """If needed, wait for this artifact to finish logging.
817
795
 
818
796
  Arguments:
@@ -823,7 +801,7 @@ class Artifact:
823
801
  """
824
802
  if self.is_draft():
825
803
  if self._save_future is None:
826
- raise ArtifactNotLoggedError(self, "wait")
804
+ raise ArtifactNotLoggedError(type(self).wait.__qualname__, self)
827
805
  result = self._save_future.get(timeout)
828
806
  if not result:
829
807
  raise WaitTimeoutError(
@@ -1134,7 +1112,7 @@ class Artifact:
1134
1112
 
1135
1113
  # Adding, removing, getting entries.
1136
1114
 
1137
- def __getitem__(self, name: str) -> Optional[data_types.WBValue]:
1115
+ def __getitem__(self, name: str) -> WBValue | None:
1138
1116
  """Get the WBValue object located at the artifact relative `name`.
1139
1117
 
1140
1118
  Arguments:
@@ -1148,7 +1126,7 @@ class Artifact:
1148
1126
  """
1149
1127
  return self.get(name)
1150
1128
 
1151
- def __setitem__(self, name: str, item: data_types.WBValue) -> ArtifactManifestEntry:
1129
+ def __setitem__(self, name: str, item: WBValue) -> ArtifactManifestEntry:
1152
1130
  """Add `item` to the artifact at path `name`.
1153
1131
 
1154
1132
  Arguments:
@@ -1165,9 +1143,10 @@ class Artifact:
1165
1143
  return self.add(item, name)
1166
1144
 
1167
1145
  @contextlib.contextmanager
1146
+ @ensure_not_finalized
1168
1147
  def new_file(
1169
- self, name: str, mode: str = "w", encoding: Optional[str] = None
1170
- ) -> Generator[IO, None, None]:
1148
+ self, name: str, mode: str = "w", encoding: str | None = None
1149
+ ) -> Iterator[IO]:
1171
1150
  """Open a new temporary file and add it to the artifact.
1172
1151
 
1173
1152
  Arguments:
@@ -1183,7 +1162,6 @@ class Artifact:
1183
1162
  ArtifactFinalizedError: You cannot make changes to the current artifact
1184
1163
  version because it is finalized. Log a new artifact version instead.
1185
1164
  """
1186
- self._ensure_can_add()
1187
1165
  if self._tmp_dir is None:
1188
1166
  self._tmp_dir = tempfile.TemporaryDirectory()
1189
1167
  path = os.path.join(self._tmp_dir.name, name.lstrip("/"))
@@ -1203,13 +1181,14 @@ class Artifact:
1203
1181
 
1204
1182
  self.add_file(path, name=name, policy="immutable", skip_cache=True)
1205
1183
 
1184
+ @ensure_not_finalized
1206
1185
  def add_file(
1207
1186
  self,
1208
1187
  local_path: str,
1209
- name: Optional[str] = None,
1210
- is_tmp: Optional[bool] = False,
1211
- skip_cache: Optional[bool] = False,
1212
- policy: Optional[Literal["mutable", "immutable"]] = "mutable",
1188
+ name: str | None = None,
1189
+ is_tmp: bool | None = False,
1190
+ skip_cache: bool | None = False,
1191
+ policy: Literal["mutable", "immutable"] | None = "mutable",
1213
1192
  ) -> ArtifactManifestEntry:
1214
1193
  """Add a local file to the artifact.
1215
1194
 
@@ -1232,7 +1211,6 @@ class Artifact:
1232
1211
  version because it is finalized. Log a new artifact version instead.
1233
1212
  ValueError: Policy must be "mutable" or "immutable"
1234
1213
  """
1235
- self._ensure_can_add()
1236
1214
  if not os.path.isfile(local_path):
1237
1215
  raise ValueError("Path is not a file: {}".format(local_path))
1238
1216
 
@@ -1249,12 +1227,13 @@ class Artifact:
1249
1227
  name, local_path, digest=digest, skip_cache=skip_cache, policy=policy
1250
1228
  )
1251
1229
 
1230
+ @ensure_not_finalized
1252
1231
  def add_dir(
1253
1232
  self,
1254
1233
  local_path: str,
1255
- name: Optional[str] = None,
1256
- skip_cache: Optional[bool] = False,
1257
- policy: Optional[Literal["mutable", "immutable"]] = "mutable",
1234
+ name: str | None = None,
1235
+ skip_cache: bool | None = False,
1236
+ policy: Literal["mutable", "immutable"] | None = "mutable",
1258
1237
  ) -> None:
1259
1238
  """Add a local directory to the artifact.
1260
1239
 
@@ -1273,7 +1252,6 @@ class Artifact:
1273
1252
  version because it is finalized. Log a new artifact version instead.
1274
1253
  ValueError: Policy must be "mutable" or "immutable"
1275
1254
  """
1276
- self._ensure_can_add()
1277
1255
  if not os.path.isdir(local_path):
1278
1256
  raise ValueError("Path is not a directory: {}".format(local_path))
1279
1257
 
@@ -1294,7 +1272,7 @@ class Artifact:
1294
1272
  logical_path = os.path.join(name, logical_path)
1295
1273
  paths.append((logical_path, physical_path))
1296
1274
 
1297
- def add_manifest_file(log_phy_path: Tuple[str, str]) -> None:
1275
+ def add_manifest_file(log_phy_path: tuple[str, str]) -> None:
1298
1276
  logical_path, physical_path = log_phy_path
1299
1277
  self._add_local_file(
1300
1278
  name=logical_path,
@@ -1311,12 +1289,13 @@ class Artifact:
1311
1289
 
1312
1290
  termlog("Done. %.1fs" % (time.time() - start_time), prefix=False)
1313
1291
 
1292
+ @ensure_not_finalized
1314
1293
  def add_reference(
1315
1294
  self,
1316
- uri: Union[ArtifactManifestEntry, str],
1317
- name: Optional[StrPath] = None,
1295
+ uri: ArtifactManifestEntry | str,
1296
+ name: StrPath | None = None,
1318
1297
  checksum: bool = True,
1319
- max_objects: Optional[int] = None,
1298
+ max_objects: int | None = None,
1320
1299
  ) -> Sequence[ArtifactManifestEntry]:
1321
1300
  """Add a reference denoted by a URI to the artifact.
1322
1301
 
@@ -1366,7 +1345,6 @@ class Artifact:
1366
1345
  ArtifactFinalizedError: You cannot make changes to the current artifact
1367
1346
  version because it is finalized. Log a new artifact version instead.
1368
1347
  """
1369
- self._ensure_can_add()
1370
1348
  if name is not None:
1371
1349
  name = LogicalPath(name)
1372
1350
 
@@ -1394,7 +1372,8 @@ class Artifact:
1394
1372
 
1395
1373
  return manifest_entries
1396
1374
 
1397
- def add(self, obj: data_types.WBValue, name: StrPath) -> ArtifactManifestEntry:
1375
+ @ensure_not_finalized
1376
+ def add(self, obj: WBValue, name: StrPath) -> ArtifactManifestEntry:
1398
1377
  """Add wandb.WBValue `obj` to the artifact.
1399
1378
 
1400
1379
  Arguments:
@@ -1410,7 +1389,6 @@ class Artifact:
1410
1389
  ArtifactFinalizedError: You cannot make changes to the current artifact
1411
1390
  version because it is finalized. Log a new artifact version instead.
1412
1391
  """
1413
- self._ensure_can_add()
1414
1392
  name = LogicalPath(name)
1415
1393
 
1416
1394
  # This is a "hack" to automatically rename tables added to
@@ -1497,9 +1475,9 @@ class Artifact:
1497
1475
  self,
1498
1476
  name: StrPath,
1499
1477
  path: StrPath,
1500
- digest: Optional[B64MD5] = None,
1501
- skip_cache: Optional[bool] = False,
1502
- policy: Optional[Literal["mutable", "immutable"]] = "mutable",
1478
+ digest: B64MD5 | None = None,
1479
+ skip_cache: bool | None = False,
1480
+ policy: Literal["mutable", "immutable"] | None = "mutable",
1503
1481
  ) -> ArtifactManifestEntry:
1504
1482
  policy = policy or "mutable"
1505
1483
  if policy not in ["mutable", "immutable"]:
@@ -1526,7 +1504,8 @@ class Artifact:
1526
1504
  self._added_local_paths[os.fspath(path)] = entry
1527
1505
  return entry
1528
1506
 
1529
- def remove(self, item: Union[StrPath, "ArtifactManifestEntry"]) -> None:
1507
+ @ensure_not_finalized
1508
+ def remove(self, item: StrPath | ArtifactManifestEntry) -> None:
1530
1509
  """Remove an item from the artifact.
1531
1510
 
1532
1511
  Arguments:
@@ -1539,8 +1518,6 @@ class Artifact:
1539
1518
  version because it is finalized. Log a new artifact version instead.
1540
1519
  FileNotFoundError: If the item isn't found in the artifact.
1541
1520
  """
1542
- self._ensure_can_add()
1543
-
1544
1521
  if isinstance(item, ArtifactManifestEntry):
1545
1522
  self.manifest.remove_entry(item)
1546
1523
  return
@@ -1565,6 +1542,7 @@ class Artifact:
1565
1542
  )
1566
1543
  return self.get_entry(name)
1567
1544
 
1545
+ @ensure_logged
1568
1546
  def get_entry(self, name: StrPath) -> ArtifactManifestEntry:
1569
1547
  """Get the entry with the given name.
1570
1548
 
@@ -1578,8 +1556,6 @@ class Artifact:
1578
1556
  ArtifactNotLoggedError: if the artifact isn't logged or the run is offline.
1579
1557
  KeyError: if the artifact doesn't contain an entry with the given name.
1580
1558
  """
1581
- self._ensure_logged("get_entry")
1582
-
1583
1559
  name = LogicalPath(name)
1584
1560
  entry = self.manifest.entries.get(name) or self._get_obj_entry(name)[0]
1585
1561
  if entry is None:
@@ -1587,7 +1563,8 @@ class Artifact:
1587
1563
  entry._parent_artifact = self
1588
1564
  return entry
1589
1565
 
1590
- def get(self, name: str) -> Optional[data_types.WBValue]:
1566
+ @ensure_logged
1567
+ def get(self, name: str) -> WBValue | None:
1591
1568
  """Get the WBValue object located at the artifact relative `name`.
1592
1569
 
1593
1570
  Arguments:
@@ -1599,8 +1576,6 @@ class Artifact:
1599
1576
  Raises:
1600
1577
  ArtifactNotLoggedError: if the artifact isn't logged or the run is offline
1601
1578
  """
1602
- self._ensure_logged("get")
1603
-
1604
1579
  entry, wb_class = self._get_obj_entry(name)
1605
1580
  if entry is None or wb_class is None:
1606
1581
  return None
@@ -1635,7 +1610,7 @@ class Artifact:
1635
1610
  result._set_artifact_source(self, name)
1636
1611
  return result
1637
1612
 
1638
- def get_added_local_path_name(self, local_path: str) -> Optional[str]:
1613
+ def get_added_local_path_name(self, local_path: str) -> str | None:
1639
1614
  """Get the artifact relative name of a file added by a local filesystem path.
1640
1615
 
1641
1616
  Arguments:
@@ -1651,7 +1626,7 @@ class Artifact:
1651
1626
 
1652
1627
  def _get_obj_entry(
1653
1628
  self, name: str
1654
- ) -> Tuple[Optional["ArtifactManifestEntry"], Optional[Type[WBValue]]]:
1629
+ ) -> tuple[ArtifactManifestEntry, Type[WBValue]] | tuple[None, None]: # noqa: UP006 # `type` shadows `Artifact.type`
1655
1630
  """Return an object entry by name, handling any type suffixes.
1656
1631
 
1657
1632
  When objects are added with `.add(obj, name)`, the name is typically changed to
@@ -1671,12 +1646,13 @@ class Artifact:
1671
1646
 
1672
1647
  # Downloading.
1673
1648
 
1649
+ @ensure_logged
1674
1650
  def download(
1675
1651
  self,
1676
- root: Optional[StrPath] = None,
1652
+ root: StrPath | None = None,
1677
1653
  allow_missing_references: bool = False,
1678
- skip_cache: Optional[bool] = None,
1679
- path_prefix: Optional[StrPath] = None,
1654
+ skip_cache: bool | None = None,
1655
+ path_prefix: StrPath | None = None,
1680
1656
  ) -> FilePathStr:
1681
1657
  """Download the contents of the artifact to the specified root directory.
1682
1658
 
@@ -1701,8 +1677,6 @@ class Artifact:
1701
1677
  ArtifactNotLoggedError: If the artifact is not logged.
1702
1678
  RuntimeError: If the artifact is attempted to be downloaded in offline mode.
1703
1679
  """
1704
- self._ensure_logged("download")
1705
-
1706
1680
  root = FilePathStr(str(root or self._default_root()))
1707
1681
  self._add_download_root(root)
1708
1682
 
@@ -1730,7 +1704,7 @@ class Artifact:
1730
1704
  root: str,
1731
1705
  allow_missing_references: bool = False,
1732
1706
  skip_cache: bool = False,
1733
- path_prefix: Optional[StrPath] = None,
1707
+ path_prefix: StrPath | None = None,
1734
1708
  ) -> FilePathStr:
1735
1709
  import pathlib
1736
1710
 
@@ -1738,7 +1712,9 @@ class Artifact:
1738
1712
 
1739
1713
  if wandb.run is None:
1740
1714
  # ensure wandb-core is up and running
1741
- wl = wandb.sdk.wandb_setup.setup()
1715
+ from wandb.sdk import wandb_setup
1716
+
1717
+ wl = wandb_setup.setup()
1742
1718
  assert wl is not None
1743
1719
 
1744
1720
  stream_id = generate_id()
@@ -1752,11 +1728,17 @@ class Artifact:
1752
1728
  settings.files_dir.value = str(tmp_dir / "files")
1753
1729
  settings.run_id.value = stream_id
1754
1730
 
1755
- manager = wl._get_manager()
1756
- manager._inform_init(settings=settings, run_id=stream_id)
1731
+ service = wl.service
1732
+ assert service
1733
+
1734
+ service.inform_init(settings=settings, run_id=stream_id)
1757
1735
 
1758
1736
  mailbox = Mailbox()
1759
- backend = Backend(settings=wl.settings, manager=manager, mailbox=mailbox)
1737
+ backend = Backend(
1738
+ settings=wl.settings,
1739
+ service=service,
1740
+ mailbox=mailbox,
1741
+ )
1760
1742
  backend.ensure_launched()
1761
1743
 
1762
1744
  assert backend.interface
@@ -1797,8 +1779,8 @@ class Artifact:
1797
1779
  self,
1798
1780
  root: str,
1799
1781
  allow_missing_references: bool = False,
1800
- skip_cache: Optional[bool] = None,
1801
- path_prefix: Optional[StrPath] = None,
1782
+ skip_cache: bool | None = None,
1783
+ path_prefix: StrPath | None = None,
1802
1784
  ) -> FilePathStr:
1803
1785
  nfiles = len(self.manifest.entries)
1804
1786
  size = sum(e.size or 0 for e in self.manifest.entries.values())
@@ -1815,9 +1797,9 @@ class Artifact:
1815
1797
 
1816
1798
  def _download_entry(
1817
1799
  entry: ArtifactManifestEntry,
1818
- api_key: Optional[str],
1819
- cookies: Optional[Dict],
1820
- headers: Optional[Dict],
1800
+ api_key: str | None,
1801
+ cookies: dict | None,
1802
+ headers: dict | None,
1821
1803
  ) -> None:
1822
1804
  _thread_local_api_settings.api_key = api_key
1823
1805
  _thread_local_api_settings.cookies = cookies
@@ -1888,9 +1870,7 @@ class Artifact:
1888
1870
  retry_timedelta=timedelta(minutes=3),
1889
1871
  retryable_exceptions=(requests.RequestException),
1890
1872
  )
1891
- def _fetch_file_urls(
1892
- self, cursor: Optional[str], per_page: Optional[int] = 5000
1893
- ) -> Any:
1873
+ def _fetch_file_urls(self, cursor: str | None, per_page: int | None = 5000) -> Any:
1894
1874
  query = gql(
1895
1875
  """
1896
1876
  query ArtifactFileURLs($id: ID!, $cursor: String, $perPage: Int) {
@@ -1919,7 +1899,8 @@ class Artifact:
1919
1899
  )
1920
1900
  return response["artifact"]["files"]
1921
1901
 
1922
- def checkout(self, root: Optional[str] = None) -> str:
1902
+ @ensure_logged
1903
+ def checkout(self, root: str | None = None) -> str:
1923
1904
  """Replace the specified root directory with the contents of the artifact.
1924
1905
 
1925
1906
  WARNING: This will delete all files in `root` that are not included in the
@@ -1934,8 +1915,6 @@ class Artifact:
1934
1915
  Raises:
1935
1916
  ArtifactNotLoggedError: If the artifact is not logged.
1936
1917
  """
1937
- self._ensure_logged("checkout")
1938
-
1939
1918
  root = root or self._default_root(include_version=False)
1940
1919
 
1941
1920
  for dirpath, _, files in os.walk(root):
@@ -1950,7 +1929,8 @@ class Artifact:
1950
1929
 
1951
1930
  return self.download(root=root)
1952
1931
 
1953
- def verify(self, root: Optional[str] = None) -> None:
1932
+ @ensure_logged
1933
+ def verify(self, root: str | None = None) -> None:
1954
1934
  """Verify that the contents of an artifact match the manifest.
1955
1935
 
1956
1936
  All files in the directory are checksummed and the checksums are then
@@ -1964,8 +1944,6 @@ class Artifact:
1964
1944
  ArtifactNotLoggedError: If the artifact is not logged.
1965
1945
  ValueError: If the verification fails.
1966
1946
  """
1967
- self._ensure_logged("verify")
1968
-
1969
1947
  root = root or self._default_root()
1970
1948
 
1971
1949
  for dirpath, _, files in os.walk(root):
@@ -1991,7 +1969,8 @@ class Artifact:
1991
1969
  if ref_count > 0:
1992
1970
  print("Warning: skipped verification of {} refs".format(ref_count))
1993
1971
 
1994
- def file(self, root: Optional[str] = None) -> StrPath:
1972
+ @ensure_logged
1973
+ def file(self, root: str | None = None) -> StrPath:
1995
1974
  """Download a single file artifact to the directory you specify with `root`.
1996
1975
 
1997
1976
  Arguments:
@@ -2005,8 +1984,6 @@ class Artifact:
2005
1984
  ArtifactNotLoggedError: If the artifact is not logged.
2006
1985
  ValueError: If the artifact contains more than one file.
2007
1986
  """
2008
- self._ensure_logged("file")
2009
-
2010
1987
  if root is None:
2011
1988
  root = os.path.join(".", "artifacts", self.name)
2012
1989
 
@@ -2018,8 +1995,9 @@ class Artifact:
2018
1995
 
2019
1996
  return self.get_entry(list(self.manifest.entries)[0]).download(root)
2020
1997
 
1998
+ @ensure_logged
2021
1999
  def files(
2022
- self, names: Optional[List[str]] = None, per_page: int = 50
2000
+ self, names: list[str] | None = None, per_page: int = 50
2023
2001
  ) -> ArtifactFiles:
2024
2002
  """Iterate over all files stored in this artifact.
2025
2003
 
@@ -2034,7 +2012,6 @@ class Artifact:
2034
2012
  Raises:
2035
2013
  ArtifactNotLoggedError: If the artifact is not logged.
2036
2014
  """
2037
- self._ensure_logged("files")
2038
2015
  return ArtifactFiles(self._client, self, names, per_page)
2039
2016
 
2040
2017
  def _default_root(self, include_version: bool = True) -> FilePathStr:
@@ -2049,7 +2026,7 @@ class Artifact:
2049
2026
  def _add_download_root(self, dir_path: str) -> None:
2050
2027
  self._download_roots.add(os.path.abspath(dir_path))
2051
2028
 
2052
- def _local_path_to_name(self, file_path: str) -> Optional[str]:
2029
+ def _local_path_to_name(self, file_path: str) -> str | None:
2053
2030
  """Convert a local file path to a path entry in the artifact."""
2054
2031
  abs_file_path = os.path.abspath(file_path)
2055
2032
  abs_file_parts = abs_file_path.split(os.sep)
@@ -2060,6 +2037,7 @@ class Artifact:
2060
2037
 
2061
2038
  # Others.
2062
2039
 
2040
+ @ensure_logged
2063
2041
  def delete(self, delete_aliases: bool = False) -> None:
2064
2042
  """Delete an artifact and its files.
2065
2043
 
@@ -2075,7 +2053,6 @@ class Artifact:
2075
2053
  Raises:
2076
2054
  ArtifactNotLoggedError: If the artifact is not logged.
2077
2055
  """
2078
- self._ensure_logged("delete")
2079
2056
  if self.collection.is_sequence():
2080
2057
  self._delete(delete_aliases)
2081
2058
  else:
@@ -2107,7 +2084,7 @@ class Artifact:
2107
2084
  )
2108
2085
 
2109
2086
  @normalize_exceptions
2110
- def link(self, target_path: str, aliases: Optional[List[str]] = None) -> None:
2087
+ def link(self, target_path: str, aliases: list[str] | None = None) -> None:
2111
2088
  """Link this artifact to a portfolio (a promoted collection of artifacts).
2112
2089
 
2113
2090
  Arguments:
@@ -2136,6 +2113,7 @@ class Artifact:
2136
2113
  else:
2137
2114
  wandb.run.link_artifact(self, target_path, aliases)
2138
2115
 
2116
+ @ensure_logged
2139
2117
  def unlink(self) -> None:
2140
2118
  """Unlink this artifact if it is currently a member of a portfolio (a promoted collection of artifacts).
2141
2119
 
@@ -2143,8 +2121,6 @@ class Artifact:
2143
2121
  ArtifactNotLoggedError: If the artifact is not logged.
2144
2122
  ValueError: If the artifact is not linked, i.e. it is not a member of a portfolio collection.
2145
2123
  """
2146
- self._ensure_logged("unlink")
2147
-
2148
2124
  # Fail early if this isn't a linked artifact to begin with
2149
2125
  if self.collection.is_sequence():
2150
2126
  raise ValueError(
@@ -2178,7 +2154,8 @@ class Artifact:
2178
2154
  },
2179
2155
  )
2180
2156
 
2181
- def used_by(self) -> List[Run]:
2157
+ @ensure_logged
2158
+ def used_by(self) -> list[Run]:
2182
2159
  """Get a list of the runs that have used this artifact.
2183
2160
 
2184
2161
  Returns:
@@ -2187,8 +2164,6 @@ class Artifact:
2187
2164
  Raises:
2188
2165
  ArtifactNotLoggedError: If the artifact is not logged.
2189
2166
  """
2190
- self._ensure_logged("used_by")
2191
-
2192
2167
  query = gql(
2193
2168
  """
2194
2169
  query ArtifactUsedBy(
@@ -2225,7 +2200,8 @@ class Artifact:
2225
2200
  for edge in response.get("artifact", {}).get("usedBy", {}).get("edges", [])
2226
2201
  ]
2227
2202
 
2228
- def logged_by(self) -> Optional[Run]:
2203
+ @ensure_logged
2204
+ def logged_by(self) -> Run | None:
2229
2205
  """Get the W&B run that originally logged the artifact.
2230
2206
 
2231
2207
  Returns:
@@ -2234,8 +2210,6 @@ class Artifact:
2234
2210
  Raises:
2235
2211
  ArtifactNotLoggedError: If the artifact is not logged.
2236
2212
  """
2237
- self._ensure_logged("logged_by")
2238
-
2239
2213
  query = gql(
2240
2214
  """
2241
2215
  query ArtifactCreatedBy(
@@ -2270,19 +2244,19 @@ class Artifact:
2270
2244
  creator["name"],
2271
2245
  )
2272
2246
 
2273
- def json_encode(self) -> Dict[str, Any]:
2247
+ @ensure_logged
2248
+ def json_encode(self) -> dict[str, Any]:
2274
2249
  """Returns the artifact encoded to the JSON format.
2275
2250
 
2276
2251
  Returns:
2277
2252
  A `dict` with `string` keys representing attributes of the artifact.
2278
2253
  """
2279
- self._ensure_logged("json_encode")
2280
2254
  return util.artifact_to_json(self)
2281
2255
 
2282
2256
  @staticmethod
2283
2257
  def _expected_type(
2284
2258
  entity_name: str, project_name: str, name: str, client: RetryingClient
2285
- ) -> Optional[str]:
2259
+ ) -> str | None:
2286
2260
  """Returns the expected type for a given artifact name and project."""
2287
2261
  query = gql(
2288
2262
  """
@@ -2317,7 +2291,7 @@ class Artifact:
2317
2291
  ).get("name")
2318
2292
 
2319
2293
  @staticmethod
2320
- def _normalize_metadata(metadata: Optional[Dict[str, Any]]) -> Dict[str, Any]:
2294
+ def _normalize_metadata(metadata: dict[str, Any] | None) -> dict[str, Any]:
2321
2295
  if metadata is None:
2322
2296
  return {}
2323
2297
  if not isinstance(metadata, dict):
@@ -2384,7 +2358,7 @@ class Artifact:
2384
2358
 
2385
2359
  return fragment
2386
2360
 
2387
- def _ttl_duration_seconds_to_gql(self) -> Optional[int]:
2361
+ def _ttl_duration_seconds_to_gql(self) -> int | None:
2388
2362
  # Set artifact ttl value to ttl_duration_seconds if the user set a value
2389
2363
  # otherwise use ttl_status to indicate the backend INHERIT(-1) or DISABLED(-2) when the TTL is None
2390
2364
  # When ttl_change = None its a no op since nothing changed
@@ -2398,8 +2372,8 @@ class Artifact:
2398
2372
  return self._ttl_duration_seconds or DISABLED
2399
2373
 
2400
2374
  def _ttl_duration_seconds_from_gql(
2401
- self, gql_ttl_duration_seconds: Optional[int]
2402
- ) -> Optional[int]:
2375
+ self, gql_ttl_duration_seconds: int | None
2376
+ ) -> int | None:
2403
2377
  # If gql_ttl_duration_seconds is not positive, its indicating that TTL is DISABLED(-2)
2404
2378
  # gql_ttl_duration_seconds only returns None if the server is not compatible with setting Artifact TTLs
2405
2379
  if gql_ttl_duration_seconds and gql_ttl_duration_seconds > 0: