wandb 0.18.1__py3-none-any.whl → 0.18.2__py3-none-any.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 (75) hide show
  1. wandb/__init__.py +3 -3
  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 +16 -5
  8. wandb/bin/nvidia_gpu_stats +0 -0
  9. wandb/cli/cli.py +0 -3
  10. wandb/errors/__init__.py +11 -40
  11. wandb/errors/errors.py +37 -0
  12. wandb/errors/warnings.py +2 -0
  13. wandb/integration/tensorboard/log.py +1 -1
  14. wandb/old/core.py +2 -80
  15. wandb/plot/bar.py +7 -4
  16. wandb/plot/confusion_matrix.py +5 -4
  17. wandb/plot/histogram.py +7 -4
  18. wandb/plot/line.py +7 -4
  19. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  20. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  21. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  22. wandb/sdk/artifacts/_validators.py +48 -3
  23. wandb/sdk/artifacts/artifact.py +157 -183
  24. wandb/sdk/artifacts/artifact_file_cache.py +13 -11
  25. wandb/sdk/artifacts/artifact_instance_cache.py +4 -2
  26. wandb/sdk/artifacts/artifact_manifest.py +13 -11
  27. wandb/sdk/artifacts/artifact_manifest_entry.py +24 -22
  28. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +9 -7
  29. wandb/sdk/artifacts/artifact_saver.py +27 -25
  30. wandb/sdk/artifacts/exceptions.py +26 -25
  31. wandb/sdk/artifacts/storage_handler.py +11 -9
  32. wandb/sdk/artifacts/storage_handlers/azure_handler.py +16 -14
  33. wandb/sdk/artifacts/storage_handlers/gcs_handler.py +15 -13
  34. wandb/sdk/artifacts/storage_handlers/http_handler.py +15 -14
  35. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +10 -8
  36. wandb/sdk/artifacts/storage_handlers/multi_handler.py +14 -12
  37. wandb/sdk/artifacts/storage_handlers/s3_handler.py +19 -19
  38. wandb/sdk/artifacts/storage_handlers/tracking_handler.py +10 -8
  39. wandb/sdk/artifacts/storage_handlers/wb_artifact_handler.py +12 -10
  40. wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +9 -7
  41. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +31 -29
  42. wandb/sdk/artifacts/storage_policy.py +20 -20
  43. wandb/sdk/backend/backend.py +8 -26
  44. wandb/sdk/data_types/base_types/wb_value.py +1 -3
  45. wandb/sdk/data_types/video.py +2 -2
  46. wandb/sdk/interface/interface.py +0 -24
  47. wandb/sdk/interface/interface_shared.py +0 -12
  48. wandb/sdk/internal/handler.py +0 -10
  49. wandb/sdk/internal/internal_api.py +71 -0
  50. wandb/sdk/internal/sender.py +0 -43
  51. wandb/sdk/internal/tb_watcher.py +1 -1
  52. wandb/sdk/lib/_settings_toposort_generated.py +1 -0
  53. wandb/sdk/lib/hashutil.py +34 -12
  54. wandb/sdk/lib/service_connection.py +216 -0
  55. wandb/sdk/lib/service_token.py +94 -0
  56. wandb/sdk/lib/sock_client.py +7 -3
  57. wandb/sdk/service/server.py +2 -5
  58. wandb/sdk/service/service.py +0 -22
  59. wandb/sdk/wandb_init.py +32 -22
  60. wandb/sdk/wandb_run.py +12 -7
  61. wandb/sdk/wandb_settings.py +2 -0
  62. wandb/sdk/wandb_setup.py +25 -16
  63. wandb/sdk/wandb_sync.py +9 -3
  64. wandb/sdk/wandb_watch.py +31 -15
  65. wandb/util.py +8 -1
  66. {wandb-0.18.1.dist-info → wandb-0.18.2.dist-info}/METADATA +2 -1
  67. {wandb-0.18.1.dist-info → wandb-0.18.2.dist-info}/RECORD +71 -71
  68. wandb/sdk/internal/update.py +0 -113
  69. wandb/sdk/service/service_base.py +0 -50
  70. wandb/sdk/service/service_sock.py +0 -70
  71. wandb/sdk/wandb_manager.py +0 -232
  72. /wandb/{sdk/lib → plot}/viz.py +0 -0
  73. {wandb-0.18.1.dist-info → wandb-0.18.2.dist-info}/WHEEL +0 -0
  74. {wandb-0.18.1.dist-info → wandb-0.18.2.dist-info}/entry_points.txt +0 -0
  75. {wandb-0.18.1.dist-info → wandb-0.18.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,7 @@
1
1
  """Artifact cache."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import contextlib
4
6
  import errno
5
7
  import hashlib
@@ -9,7 +11,7 @@ import subprocess
9
11
  import sys
10
12
  from pathlib import Path
11
13
  from tempfile import NamedTemporaryFile
12
- from typing import IO, TYPE_CHECKING, ContextManager, Iterator, Optional, Tuple
14
+ from typing import IO, TYPE_CHECKING, ContextManager, Iterator
13
15
 
14
16
  import wandb
15
17
  from wandb import env, util
@@ -49,11 +51,11 @@ class ArtifactFileCache:
49
51
  # [1] https://stackoverflow.com/questions/10541760/can-i-set-the-umask-for-tempfile-namedtemporaryfile-in-python
50
52
  self._sys_umask = _get_sys_umask_threadsafe()
51
53
 
52
- self._override_cache_path: Optional[StrPath] = None
54
+ self._override_cache_path: StrPath | None = None
53
55
 
54
56
  def check_md5_obj_path(
55
57
  self, b64_md5: B64MD5, size: int
56
- ) -> Tuple[FilePathStr, bool, "Opener"]:
58
+ ) -> tuple[FilePathStr, bool, Opener]:
57
59
  # Check if we're using vs skipping the cache
58
60
  if self._override_cache_path is not None:
59
61
  skip_cache = True
@@ -71,7 +73,7 @@ class ArtifactFileCache:
71
73
  url: URIStr,
72
74
  etag: ETag,
73
75
  size: int,
74
- ) -> Tuple[FilePathStr, bool, "Opener"]:
76
+ ) -> tuple[FilePathStr, bool, Opener]:
75
77
  # Check if we're using vs skipping the cache
76
78
  if self._override_cache_path is not None:
77
79
  skip_cache = True
@@ -87,16 +89,16 @@ class ArtifactFileCache:
87
89
 
88
90
  def _check_or_create(
89
91
  self, path: Path, size: int, skip_cache: bool = False
90
- ) -> Tuple[FilePathStr, bool, "Opener"]:
92
+ ) -> tuple[FilePathStr, bool, Opener]:
91
93
  opener = self._opener(path, size, skip_cache=skip_cache)
92
94
  hit = path.is_file() and path.stat().st_size == size
93
95
  return FilePathStr(str(path)), hit, opener
94
96
 
95
97
  def cleanup(
96
98
  self,
97
- target_size: Optional[int] = None,
99
+ target_size: int | None = None,
98
100
  remove_temp: bool = False,
99
- target_fraction: Optional[float] = None,
101
+ target_fraction: float | None = None,
100
102
  ) -> int:
101
103
  """Clean up the cache, removing the least recently used files first.
102
104
 
@@ -121,9 +123,9 @@ class ArtifactFileCache:
121
123
  target_size = 0
122
124
  if target_size is not None and target_fraction is not None:
123
125
  raise ValueError("Cannot specify both target_size and target_fraction")
124
- if target_size and target_size < 0:
126
+ if target_size is not None and target_size < 0:
125
127
  raise ValueError("target_size must be non-negative")
126
- if target_fraction and (target_fraction < 0 or target_fraction > 1):
128
+ if target_fraction is not None and (target_fraction < 0 or target_fraction > 1):
127
129
  raise ValueError("target_fraction must be between 0 and 1")
128
130
 
129
131
  bytes_reclaimed = 0
@@ -198,7 +200,7 @@ class ArtifactFileCache:
198
200
  if size > self._free_space():
199
201
  raise OSError(errno.ENOSPC, f"Insufficient free space in {self._cache_dir}")
200
202
 
201
- def _opener(self, path: Path, size: int, skip_cache: bool = False) -> "Opener":
203
+ def _opener(self, path: Path, size: int, skip_cache: bool = False) -> Opener:
202
204
  @contextlib.contextmanager
203
205
  def atomic_open(mode: str = "w") -> Iterator[IO]:
204
206
  if "a" in mode:
@@ -240,7 +242,7 @@ class ArtifactFileCache:
240
242
  ) from e
241
243
 
242
244
 
243
- _artifact_file_cache: Optional[ArtifactFileCache] = None
245
+ _artifact_file_cache: ArtifactFileCache | None = None
244
246
 
245
247
 
246
248
  def get_artifact_file_cache() -> ArtifactFileCache:
@@ -4,7 +4,9 @@ Artifacts are registered in the cache to ensure they won't be immediately garbag
4
4
  collected and can be retrieved by their ID.
5
5
  """
6
6
 
7
- from typing import TYPE_CHECKING, Dict
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING
8
10
 
9
11
  from wandb.sdk.lib.capped_dict import CappedDict
10
12
 
@@ -12,4 +14,4 @@ if TYPE_CHECKING:
12
14
  from wandb.sdk.artifacts.artifact import Artifact
13
15
 
14
16
  # There is nothing special about the artifact cache, it's just a global capped dict.
15
- artifact_instance_cache: Dict[str, "Artifact"] = CappedDict(100)
17
+ artifact_instance_cache: dict[str, Artifact] = CappedDict(100)
@@ -1,6 +1,8 @@
1
1
  """Artifact manifest."""
2
2
 
3
- from typing import TYPE_CHECKING, Dict, List, Mapping, Optional
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Mapping
4
6
 
5
7
  from wandb.sdk.internal.internal_api import Api as InternalApi
6
8
  from wandb.sdk.lib.hashutil import HexMD5
@@ -11,12 +13,12 @@ if TYPE_CHECKING:
11
13
 
12
14
 
13
15
  class ArtifactManifest:
14
- entries: Dict[str, "ArtifactManifestEntry"]
16
+ entries: dict[str, ArtifactManifestEntry]
15
17
 
16
18
  @classmethod
17
19
  def from_manifest_json(
18
- cls, manifest_json: Dict, api: Optional[InternalApi] = None
19
- ) -> "ArtifactManifest":
20
+ cls, manifest_json: dict, api: InternalApi | None = None
21
+ ) -> ArtifactManifest:
20
22
  if "version" not in manifest_json:
21
23
  raise ValueError("Invalid manifest format. Must contain version field.")
22
24
  version = manifest_json["version"]
@@ -31,8 +33,8 @@ class ArtifactManifest:
31
33
 
32
34
  def __init__(
33
35
  self,
34
- storage_policy: "StoragePolicy",
35
- entries: Optional[Mapping[str, "ArtifactManifestEntry"]] = None,
36
+ storage_policy: StoragePolicy,
37
+ entries: Mapping[str, ArtifactManifestEntry] | None = None,
36
38
  ) -> None:
37
39
  self.storage_policy = storage_policy
38
40
  self.entries = dict(entries) if entries else {}
@@ -40,13 +42,13 @@ class ArtifactManifest:
40
42
  def __len__(self) -> int:
41
43
  return len(self.entries)
42
44
 
43
- def to_manifest_json(self) -> Dict:
45
+ def to_manifest_json(self) -> dict:
44
46
  raise NotImplementedError
45
47
 
46
48
  def digest(self) -> HexMD5:
47
49
  raise NotImplementedError
48
50
 
49
- def add_entry(self, entry: "ArtifactManifestEntry") -> None:
51
+ def add_entry(self, entry: ArtifactManifestEntry) -> None:
50
52
  if (
51
53
  entry.path in self.entries
52
54
  and entry.digest != self.entries[entry.path].digest
@@ -54,15 +56,15 @@ class ArtifactManifest:
54
56
  raise ValueError("Cannot add the same path twice: {}".format(entry.path))
55
57
  self.entries[entry.path] = entry
56
58
 
57
- def remove_entry(self, entry: "ArtifactManifestEntry") -> None:
59
+ def remove_entry(self, entry: ArtifactManifestEntry) -> None:
58
60
  if entry.path not in self.entries:
59
61
  raise FileNotFoundError(f"Cannot remove missing entry: '{entry.path}'")
60
62
  del self.entries[entry.path]
61
63
 
62
- def get_entry_by_path(self, path: str) -> Optional["ArtifactManifestEntry"]:
64
+ def get_entry_by_path(self, path: str) -> ArtifactManifestEntry | None:
63
65
  return self.entries.get(path)
64
66
 
65
- def get_entries_in_directory(self, directory: str) -> List["ArtifactManifestEntry"]:
67
+ def get_entries_in_directory(self, directory: str) -> list[ArtifactManifestEntry]:
66
68
  return [
67
69
  self.entries[entry_key]
68
70
  for entry_key in self.entries
@@ -1,10 +1,12 @@
1
1
  """Artifact manifest entry."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import json
4
6
  import logging
5
7
  import os
6
8
  from pathlib import Path
7
- from typing import TYPE_CHECKING, Dict, Optional, Union
9
+ from typing import TYPE_CHECKING
8
10
  from urllib.parse import urlparse
9
11
 
10
12
  from wandb.sdk.lib import filesystem
@@ -32,7 +34,7 @@ if TYPE_CHECKING:
32
34
  ref: str
33
35
  birthArtifactID: str
34
36
  size: int
35
- extra: Dict
37
+ extra: dict
36
38
  local_path: str
37
39
 
38
40
 
@@ -40,27 +42,27 @@ class ArtifactManifestEntry:
40
42
  """A single entry in an artifact manifest."""
41
43
 
42
44
  path: LogicalPath
43
- digest: Union[B64MD5, URIStr, FilePathStr, ETag]
45
+ digest: B64MD5 | URIStr | FilePathStr | ETag
44
46
  skip_cache: bool
45
- ref: Optional[Union[FilePathStr, URIStr]]
46
- birth_artifact_id: Optional[str]
47
- size: Optional[int]
48
- extra: Dict
49
- local_path: Optional[str]
47
+ ref: FilePathStr | URIStr | None
48
+ birth_artifact_id: str | None
49
+ size: int | None
50
+ extra: dict
51
+ local_path: str | None
50
52
 
51
- _parent_artifact: Optional["Artifact"] = None
52
- _download_url: Optional[str] = None
53
+ _parent_artifact: Artifact | None = None
54
+ _download_url: str | None = None
53
55
 
54
56
  def __init__(
55
57
  self,
56
58
  path: StrPath,
57
- digest: Union[B64MD5, URIStr, FilePathStr, ETag],
58
- skip_cache: Optional[bool] = False,
59
- ref: Optional[Union[FilePathStr, URIStr]] = None,
60
- birth_artifact_id: Optional[str] = None,
61
- size: Optional[int] = None,
62
- extra: Optional[Dict] = None,
63
- local_path: Optional[StrPath] = None,
59
+ digest: B64MD5 | URIStr | FilePathStr | ETag,
60
+ skip_cache: bool | None = False,
61
+ ref: FilePathStr | URIStr | None = None,
62
+ birth_artifact_id: str | None = None,
63
+ size: int | None = None,
64
+ extra: dict | None = None,
65
+ local_path: StrPath | None = None,
64
66
  ) -> None:
65
67
  self.path = LogicalPath(path)
66
68
  self.digest = digest
@@ -116,7 +118,7 @@ class ArtifactManifestEntry:
116
118
  )
117
119
  return self.path
118
120
 
119
- def parent_artifact(self) -> "Artifact":
121
+ def parent_artifact(self) -> Artifact:
120
122
  """Get the artifact to which this artifact entry belongs.
121
123
 
122
124
  Returns:
@@ -127,7 +129,7 @@ class ArtifactManifestEntry:
127
129
  return self._parent_artifact
128
130
 
129
131
  def download(
130
- self, root: Optional[str] = None, skip_cache: Optional[bool] = None
132
+ self, root: str | None = None, skip_cache: bool | None = None
131
133
  ) -> FilePathStr:
132
134
  """Download this artifact entry to the specified root path.
133
135
 
@@ -177,7 +179,7 @@ class ArtifactManifestEntry:
177
179
  str(filesystem.copy_or_overwrite_changed(cache_path, dest_path))
178
180
  )
179
181
 
180
- def ref_target(self) -> Union[FilePathStr, URIStr]:
182
+ def ref_target(self) -> FilePathStr | URIStr:
181
183
  """Get the reference URL that is targeted by this artifact entry.
182
184
 
183
185
  Returns:
@@ -219,7 +221,7 @@ class ArtifactManifestEntry:
219
221
  + self.path
220
222
  )
221
223
 
222
- def to_json(self) -> "ArtifactManifestEntryDict":
224
+ def to_json(self) -> ArtifactManifestEntryDict:
223
225
  contents: ArtifactManifestEntryDict = {
224
226
  "path": self.path,
225
227
  "digest": self.digest,
@@ -241,7 +243,7 @@ class ArtifactManifestEntry:
241
243
  def _is_artifact_reference(self) -> bool:
242
244
  return self.ref is not None and urlparse(self.ref).scheme == "wandb-artifact"
243
245
 
244
- def _referenced_artifact_id(self) -> Optional[str]:
246
+ def _referenced_artifact_id(self) -> str | None:
245
247
  if not self._is_artifact_reference():
246
248
  return None
247
249
  return hex_to_b64_id(urlparse(self.ref).netloc)
@@ -1,6 +1,8 @@
1
1
  """Artifact manifest v1."""
2
2
 
3
- from typing import Any, Dict, Mapping, Optional
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Mapping
4
6
 
5
7
  from wandb.sdk.artifacts.artifact_manifest import ArtifactManifest
6
8
  from wandb.sdk.artifacts.artifact_manifest_entry import ArtifactManifestEntry
@@ -16,8 +18,8 @@ class ArtifactManifestV1(ArtifactManifest):
16
18
 
17
19
  @classmethod
18
20
  def from_manifest_json(
19
- cls, manifest_json: Dict, api: Optional[InternalApi] = None
20
- ) -> "ArtifactManifestV1":
21
+ cls, manifest_json: dict, api: InternalApi | None = None
22
+ ) -> ArtifactManifestV1:
21
23
  if manifest_json["version"] != cls.version():
22
24
  raise ValueError(
23
25
  "Expected manifest version 1, got {}".format(manifest_json["version"])
@@ -48,12 +50,12 @@ class ArtifactManifestV1(ArtifactManifest):
48
50
 
49
51
  def __init__(
50
52
  self,
51
- storage_policy: "StoragePolicy",
52
- entries: Optional[Mapping[str, ArtifactManifestEntry]] = None,
53
+ storage_policy: StoragePolicy,
54
+ entries: Mapping[str, ArtifactManifestEntry] | None = None,
53
55
  ) -> None:
54
56
  super().__init__(storage_policy, entries=entries)
55
57
 
56
- def to_manifest_json(self) -> Dict:
58
+ def to_manifest_json(self) -> dict:
57
59
  """This is the JSON that's stored in wandb_manifest.json.
58
60
 
59
61
  If include_local is True we also include the local paths to files. This is
@@ -63,7 +65,7 @@ class ArtifactManifestV1(ArtifactManifest):
63
65
  """
64
66
  contents = {}
65
67
  for entry in sorted(self.entries.values(), key=lambda k: k.path):
66
- json_entry: Dict[str, Any] = {
68
+ json_entry: dict[str, Any] = {
67
69
  "digest": entry.digest,
68
70
  }
69
71
  if entry.birth_artifact_id:
@@ -1,11 +1,13 @@
1
1
  """Artifact saver."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import concurrent.futures
4
6
  import json
5
7
  import os
6
8
  import sys
7
9
  import tempfile
8
- from typing import TYPE_CHECKING, Awaitable, Dict, Optional, Sequence
10
+ from typing import TYPE_CHECKING, Awaitable, Sequence
9
11
 
10
12
  import wandb
11
13
  import wandb.filesync.step_prepare
@@ -27,26 +29,26 @@ if TYPE_CHECKING:
27
29
 
28
30
  class SaveFn(Protocol):
29
31
  def __call__(
30
- self, entry: "ArtifactManifestEntry", progress_callback: "ProgressFn"
32
+ self, entry: ArtifactManifestEntry, progress_callback: ProgressFn
31
33
  ) -> bool:
32
34
  pass
33
35
 
34
36
  class SaveFnAsync(Protocol):
35
37
  def __call__(
36
- self, entry: "ArtifactManifestEntry", progress_callback: "ProgressFn"
38
+ self, entry: ArtifactManifestEntry, progress_callback: ProgressFn
37
39
  ) -> Awaitable[bool]:
38
40
  pass
39
41
 
40
42
 
41
43
  class ArtifactSaver:
42
- _server_artifact: Optional[Dict] # TODO better define this dict
44
+ _server_artifact: dict | None # TODO better define this dict
43
45
 
44
46
  def __init__(
45
47
  self,
46
- api: "InternalApi",
48
+ api: InternalApi,
47
49
  digest: str,
48
- manifest_json: Dict,
49
- file_pusher: "FilePusher",
50
+ manifest_json: dict,
51
+ file_pusher: FilePusher,
50
52
  is_user_created: bool = False,
51
53
  ) -> None:
52
54
  self._api = api
@@ -65,18 +67,18 @@ class ArtifactSaver:
65
67
  name: str,
66
68
  client_id: str,
67
69
  sequence_client_id: str,
68
- distributed_id: Optional[str] = None,
70
+ distributed_id: str | None = None,
69
71
  finalize: bool = True,
70
- metadata: Optional[Dict] = None,
71
- ttl_duration_seconds: Optional[int] = None,
72
- description: Optional[str] = None,
73
- aliases: Optional[Sequence[str]] = None,
74
- tags: Optional[Sequence[str]] = None,
72
+ metadata: dict | None = None,
73
+ ttl_duration_seconds: int | None = None,
74
+ description: str | None = None,
75
+ aliases: Sequence[str] | None = None,
76
+ tags: Sequence[str] | None = None,
75
77
  use_after_commit: bool = False,
76
78
  incremental: bool = False,
77
- history_step: Optional[int] = None,
78
- base_id: Optional[str] = None,
79
- ) -> Optional[Dict]:
79
+ history_step: int | None = None,
80
+ base_id: str | None = None,
81
+ ) -> dict | None:
80
82
  return self._save_internal(
81
83
  type,
82
84
  name,
@@ -101,18 +103,18 @@ class ArtifactSaver:
101
103
  name: str,
102
104
  client_id: str,
103
105
  sequence_client_id: str,
104
- distributed_id: Optional[str] = None,
106
+ distributed_id: str | None = None,
105
107
  finalize: bool = True,
106
- metadata: Optional[Dict] = None,
107
- ttl_duration_seconds: Optional[int] = None,
108
- description: Optional[str] = None,
109
- aliases: Optional[Sequence[str]] = None,
110
- tags: Optional[Sequence[str]] = None,
108
+ metadata: dict | None = None,
109
+ ttl_duration_seconds: int | None = None,
110
+ description: str | None = None,
111
+ aliases: Sequence[str] | None = None,
112
+ tags: Sequence[str] | None = None,
111
113
  use_after_commit: bool = False,
112
114
  incremental: bool = False,
113
- history_step: Optional[int] = None,
114
- base_id: Optional[str] = None,
115
- ) -> Optional[Dict]:
115
+ history_step: int | None = None,
116
+ base_id: str | None = None,
117
+ ) -> dict | None:
116
118
  alias_specs = []
117
119
  for alias in aliases or []:
118
120
  alias_specs.append({"artifactCollectionName": name, "alias": alias})
@@ -1,55 +1,56 @@
1
1
  """Artifact exceptions."""
2
2
 
3
- from typing import TYPE_CHECKING, Optional
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, TypeVar
4
6
 
5
7
  from wandb import errors
6
8
 
7
9
  if TYPE_CHECKING:
8
10
  from wandb.sdk.artifacts.artifact import Artifact
9
11
 
12
+ ArtifactT = TypeVar("ArtifactT", bound=Artifact)
13
+
10
14
 
11
15
  class ArtifactStatusError(AttributeError):
12
16
  """Raised when an artifact is in an invalid state for the requested operation."""
13
17
 
14
18
  def __init__(
15
19
  self,
16
- artifact: Optional["Artifact"] = None,
17
- attr: Optional[str] = None,
18
20
  msg: str = "Artifact is in an invalid state for the requested operation.",
21
+ name: str | None = None,
22
+ obj: ArtifactT | None = None,
19
23
  ):
20
- object_name = artifact.__class__.__name__ if artifact else "Artifact"
21
- method_id = f"{object_name}.{attr}" if attr else object_name
22
- super().__init__(msg.format(artifact=artifact, attr=attr, method_id=method_id))
23
- # Follow the same pattern as AttributeError.
24
- self.obj = artifact
25
- self.name = attr or ""
24
+ # Follow the same pattern as AttributeError in python 3.10+ by `name/obj` attributes
25
+ # See: https://docs.python.org/3/library/exceptions.html#AttributeError
26
+ try:
27
+ super().__init__(msg, name=name, obj=obj)
28
+ except TypeError:
29
+ # The `name`/`obj` keyword args and attributes were only added in python >= 3.10
30
+ super().__init__(msg)
31
+ self.name = name or ""
32
+ self.obj = obj
26
33
 
27
34
 
28
35
  class ArtifactNotLoggedError(ArtifactStatusError):
29
36
  """Raised for Artifact methods or attributes only available after logging."""
30
37
 
31
- def __init__(
32
- self, artifact: Optional["Artifact"] = None, attr: Optional[str] = None
33
- ):
34
- super().__init__(
35
- artifact,
36
- attr,
37
- "'{method_id}' used prior to logging artifact or while in offline mode. "
38
- "Call wait() before accessing logged artifact properties.",
38
+ def __init__(self, fullname: str, obj: ArtifactT):
39
+ *_, name = fullname.split(".")
40
+ msg = (
41
+ f"{fullname!r} used prior to logging artifact or while in offline mode. "
42
+ f"Call {type(obj).wait.__qualname__}() before accessing logged artifact properties."
39
43
  )
44
+ super().__init__(msg=msg, name=name, obj=obj)
40
45
 
41
46
 
42
47
  class ArtifactFinalizedError(ArtifactStatusError):
43
48
  """Raised for Artifact methods or attributes that can't be changed after logging."""
44
49
 
45
- def __init__(
46
- self, artifact: Optional["Artifact"] = None, attr: Optional[str] = None
47
- ):
48
- super().__init__(
49
- artifact,
50
- attr,
51
- "'{method_id}' used on logged artifact. Can't modify finalized artifact.",
52
- )
50
+ def __init__(self, fullname: str, obj: ArtifactT):
51
+ *_, name = fullname.split(".")
52
+ msg = f"{fullname!r} used on logged artifact. Can't modify finalized artifact."
53
+ super().__init__(msg=msg, name=name, obj=obj)
53
54
 
54
55
 
55
56
  class WaitTimeoutError(errors.Error):
@@ -1,6 +1,8 @@
1
1
  """Storage handler."""
2
2
 
3
- from typing import TYPE_CHECKING, Optional, Sequence, Union
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Sequence
4
6
 
5
7
  from wandb.sdk.lib.paths import FilePathStr, URIStr
6
8
 
@@ -14,7 +16,7 @@ DEFAULT_MAX_OBJECTS = 10**7
14
16
 
15
17
 
16
18
  class StorageHandler:
17
- def can_handle(self, parsed_url: "ParseResult") -> bool:
19
+ def can_handle(self, parsed_url: ParseResult) -> bool:
18
20
  """Checks whether this handler can handle the given url.
19
21
 
20
22
  Returns:
@@ -24,9 +26,9 @@ class StorageHandler:
24
26
 
25
27
  def load_path(
26
28
  self,
27
- manifest_entry: "ArtifactManifestEntry",
29
+ manifest_entry: ArtifactManifestEntry,
28
30
  local: bool = False,
29
- ) -> Union[URIStr, FilePathStr]:
31
+ ) -> URIStr | FilePathStr:
30
32
  """Load a file or directory given the corresponding index entry.
31
33
 
32
34
  Args:
@@ -40,12 +42,12 @@ class StorageHandler:
40
42
 
41
43
  def store_path(
42
44
  self,
43
- artifact: "Artifact",
44
- path: Union[URIStr, FilePathStr],
45
- name: Optional[str] = None,
45
+ artifact: Artifact,
46
+ path: URIStr | FilePathStr,
47
+ name: str | None = None,
46
48
  checksum: bool = True,
47
- max_objects: Optional[int] = None,
48
- ) -> Sequence["ArtifactManifestEntry"]:
49
+ max_objects: int | None = None,
50
+ ) -> Sequence[ArtifactManifestEntry]:
49
51
  """Store the file or directory at the given path to the specified artifact.
50
52
 
51
53
  Args:
@@ -1,8 +1,10 @@
1
1
  """Azure storage handler."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from pathlib import PurePosixPath
4
6
  from types import ModuleType
5
- from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple, Union
7
+ from typing import TYPE_CHECKING, Sequence
6
8
  from urllib.parse import ParseResult, parse_qsl, urlparse
7
9
 
8
10
  import wandb
@@ -21,16 +23,16 @@ if TYPE_CHECKING:
21
23
 
22
24
 
23
25
  class AzureHandler(StorageHandler):
24
- def can_handle(self, parsed_url: "ParseResult") -> bool:
26
+ def can_handle(self, parsed_url: ParseResult) -> bool:
25
27
  return parsed_url.scheme == "https" and parsed_url.netloc.endswith(
26
28
  ".blob.core.windows.net"
27
29
  )
28
30
 
29
31
  def load_path(
30
32
  self,
31
- manifest_entry: "ArtifactManifestEntry",
33
+ manifest_entry: ArtifactManifestEntry,
32
34
  local: bool = False,
33
- ) -> Union[URIStr, FilePathStr]:
35
+ ) -> URIStr | FilePathStr:
34
36
  assert manifest_entry.ref is not None
35
37
  if not local:
36
38
  return manifest_entry.ref
@@ -91,12 +93,12 @@ class AzureHandler(StorageHandler):
91
93
 
92
94
  def store_path(
93
95
  self,
94
- artifact: "Artifact",
95
- path: Union[URIStr, FilePathStr],
96
- name: Optional[StrPath] = None,
96
+ artifact: Artifact,
97
+ path: URIStr | FilePathStr,
98
+ name: StrPath | None = None,
97
99
  checksum: bool = True,
98
- max_objects: Optional[int] = None,
99
- ) -> Sequence["ArtifactManifestEntry"]:
100
+ max_objects: int | None = None,
101
+ ) -> Sequence[ArtifactManifestEntry]:
100
102
  account_url, container_name, blob_name, query = self._parse_uri(path)
101
103
  path = URIStr(f"{account_url}/{container_name}/{blob_name}")
102
104
 
@@ -127,7 +129,7 @@ class AzureHandler(StorageHandler):
127
129
  )
128
130
  ]
129
131
 
130
- entries: List[ArtifactManifestEntry] = []
132
+ entries: list[ArtifactManifestEntry] = []
131
133
  container_client = blob_service_client.get_container_client(container_name)
132
134
  max_objects = max_objects or DEFAULT_MAX_OBJECTS
133
135
  for blob_properties in container_client.list_blobs(
@@ -163,7 +165,7 @@ class AzureHandler(StorageHandler):
163
165
 
164
166
  def _get_credential(
165
167
  self, account_url: str
166
- ) -> Union["azure.identity.DefaultAzureCredential", str]:
168
+ ) -> azure.identity.DefaultAzureCredential | str:
167
169
  if (
168
170
  wandb.run
169
171
  and wandb.run.settings.azure_account_url_to_access_key is not None
@@ -172,7 +174,7 @@ class AzureHandler(StorageHandler):
172
174
  return wandb.run.settings.azure_account_url_to_access_key[account_url]
173
175
  return self._get_module("azure.identity").DefaultAzureCredential()
174
176
 
175
- def _parse_uri(self, uri: str) -> Tuple[str, str, str, Dict[str, str]]:
177
+ def _parse_uri(self, uri: str) -> tuple[str, str, str, dict[str, str]]:
176
178
  parsed_url = urlparse(uri)
177
179
  query = dict(parse_qsl(parsed_url.query))
178
180
  account_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
@@ -181,7 +183,7 @@ class AzureHandler(StorageHandler):
181
183
 
182
184
  def _create_entry(
183
185
  self,
184
- blob_properties: "azure.storage.blob.BlobProperties",
186
+ blob_properties: azure.storage.blob.BlobProperties,
185
187
  path: StrPath,
186
188
  ref: URIStr,
187
189
  ) -> ArtifactManifestEntry:
@@ -197,7 +199,7 @@ class AzureHandler(StorageHandler):
197
199
  )
198
200
 
199
201
  def _is_directory_stub(
200
- self, blob_properties: "azure.storage.blob.BlobProperties"
202
+ self, blob_properties: azure.storage.blob.BlobProperties
201
203
  ) -> bool:
202
204
  return (
203
205
  blob_properties.has_key("metadata")