wandb 0.21.0__py3-none-win_amd64.whl → 0.21.2__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. wandb/__init__.py +16 -14
  2. wandb/__init__.pyi +427 -450
  3. wandb/agents/pyagent.py +41 -12
  4. wandb/analytics/sentry.py +7 -2
  5. wandb/apis/importers/mlflow.py +1 -1
  6. wandb/apis/public/__init__.py +1 -1
  7. wandb/apis/public/api.py +525 -360
  8. wandb/apis/public/artifacts.py +207 -13
  9. wandb/apis/public/automations.py +19 -3
  10. wandb/apis/public/files.py +172 -33
  11. wandb/apis/public/history.py +67 -15
  12. wandb/apis/public/integrations.py +25 -2
  13. wandb/apis/public/jobs.py +90 -2
  14. wandb/apis/public/projects.py +130 -79
  15. wandb/apis/public/query_generator.py +11 -1
  16. wandb/apis/public/registries/_utils.py +14 -16
  17. wandb/apis/public/registries/registries_search.py +183 -304
  18. wandb/apis/public/reports.py +96 -15
  19. wandb/apis/public/runs.py +299 -105
  20. wandb/apis/public/sweeps.py +222 -22
  21. wandb/apis/public/teams.py +41 -4
  22. wandb/apis/public/users.py +45 -4
  23. wandb/automations/_generated/delete_automation.py +1 -3
  24. wandb/automations/_generated/enums.py +13 -11
  25. wandb/beta/workflows.py +66 -30
  26. wandb/bin/gpu_stats.exe +0 -0
  27. wandb/bin/wandb-core +0 -0
  28. wandb/cli/cli.py +127 -3
  29. wandb/env.py +8 -0
  30. wandb/errors/errors.py +4 -1
  31. wandb/integration/lightning/fabric/logger.py +3 -4
  32. wandb/integration/metaflow/__init__.py +6 -0
  33. wandb/integration/metaflow/data_pandas.py +74 -0
  34. wandb/integration/metaflow/data_pytorch.py +75 -0
  35. wandb/integration/metaflow/data_sklearn.py +76 -0
  36. wandb/integration/metaflow/errors.py +13 -0
  37. wandb/integration/metaflow/metaflow.py +167 -223
  38. wandb/integration/openai/fine_tuning.py +1 -2
  39. wandb/integration/weave/__init__.py +6 -0
  40. wandb/integration/weave/interface.py +49 -0
  41. wandb/integration/weave/weave.py +63 -0
  42. wandb/jupyter.py +5 -5
  43. wandb/plot/custom_chart.py +30 -7
  44. wandb/proto/v3/wandb_internal_pb2.py +281 -280
  45. wandb/proto/v3/wandb_telemetry_pb2.py +4 -4
  46. wandb/proto/v4/wandb_internal_pb2.py +280 -280
  47. wandb/proto/v4/wandb_telemetry_pb2.py +4 -4
  48. wandb/proto/v5/wandb_internal_pb2.py +280 -280
  49. wandb/proto/v5/wandb_telemetry_pb2.py +4 -4
  50. wandb/proto/v6/wandb_internal_pb2.py +280 -280
  51. wandb/proto/v6/wandb_telemetry_pb2.py +4 -4
  52. wandb/proto/wandb_deprecated.py +6 -0
  53. wandb/sdk/artifacts/_factories.py +17 -0
  54. wandb/sdk/artifacts/_generated/__init__.py +221 -13
  55. wandb/sdk/artifacts/_generated/artifact_by_id.py +17 -0
  56. wandb/sdk/artifacts/_generated/artifact_by_name.py +22 -0
  57. wandb/sdk/artifacts/_generated/artifact_collection_membership_file_urls.py +43 -0
  58. wandb/sdk/artifacts/_generated/artifact_created_by.py +47 -0
  59. wandb/sdk/artifacts/_generated/artifact_file_urls.py +22 -0
  60. wandb/sdk/artifacts/_generated/artifact_type.py +31 -0
  61. wandb/sdk/artifacts/_generated/artifact_used_by.py +43 -0
  62. wandb/sdk/artifacts/_generated/artifact_via_membership_by_name.py +26 -0
  63. wandb/sdk/artifacts/_generated/delete_artifact.py +28 -0
  64. wandb/sdk/artifacts/_generated/enums.py +5 -0
  65. wandb/sdk/artifacts/_generated/fetch_artifact_manifest.py +38 -0
  66. wandb/sdk/artifacts/_generated/fetch_registries.py +32 -0
  67. wandb/sdk/artifacts/_generated/fragments.py +279 -41
  68. wandb/sdk/artifacts/_generated/link_artifact.py +6 -0
  69. wandb/sdk/artifacts/_generated/operations.py +654 -51
  70. wandb/sdk/artifacts/_generated/registry_collections.py +34 -0
  71. wandb/sdk/artifacts/_generated/registry_versions.py +34 -0
  72. wandb/sdk/artifacts/_generated/unlink_artifact.py +25 -0
  73. wandb/sdk/artifacts/_graphql_fragments.py +3 -86
  74. wandb/sdk/artifacts/_internal_artifact.py +19 -8
  75. wandb/sdk/artifacts/_validators.py +14 -4
  76. wandb/sdk/artifacts/artifact.py +512 -618
  77. wandb/sdk/artifacts/artifact_file_cache.py +10 -6
  78. wandb/sdk/artifacts/artifact_manifest.py +10 -9
  79. wandb/sdk/artifacts/artifact_manifest_entry.py +9 -10
  80. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +5 -3
  81. wandb/sdk/artifacts/storage_handlers/http_handler.py +1 -1
  82. wandb/sdk/artifacts/storage_handlers/s3_handler.py +1 -1
  83. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +1 -1
  84. wandb/sdk/data_types/audio.py +38 -10
  85. wandb/sdk/data_types/base_types/media.py +6 -56
  86. wandb/sdk/data_types/graph.py +48 -14
  87. wandb/sdk/data_types/helper_types/bounding_boxes_2d.py +1 -3
  88. wandb/sdk/data_types/helper_types/image_mask.py +1 -3
  89. wandb/sdk/data_types/histogram.py +34 -21
  90. wandb/sdk/data_types/html.py +35 -12
  91. wandb/sdk/data_types/image.py +104 -68
  92. wandb/sdk/data_types/molecule.py +32 -19
  93. wandb/sdk/data_types/object_3d.py +36 -17
  94. wandb/sdk/data_types/plotly.py +18 -5
  95. wandb/sdk/data_types/saved_model.py +4 -6
  96. wandb/sdk/data_types/table.py +59 -30
  97. wandb/sdk/data_types/video.py +53 -26
  98. wandb/sdk/integration_utils/auto_logging.py +2 -2
  99. wandb/sdk/interface/interface_queue.py +1 -4
  100. wandb/sdk/interface/interface_shared.py +26 -37
  101. wandb/sdk/interface/interface_sock.py +24 -14
  102. wandb/sdk/internal/internal_api.py +6 -0
  103. wandb/sdk/internal/job_builder.py +6 -0
  104. wandb/sdk/internal/settings_static.py +2 -3
  105. wandb/sdk/launch/agent/agent.py +8 -1
  106. wandb/sdk/launch/agent/run_queue_item_file_saver.py +2 -2
  107. wandb/sdk/launch/create_job.py +15 -2
  108. wandb/sdk/launch/inputs/internal.py +3 -4
  109. wandb/sdk/launch/inputs/schema.py +1 -0
  110. wandb/sdk/launch/runner/kubernetes_monitor.py +1 -0
  111. wandb/sdk/launch/runner/kubernetes_runner.py +323 -1
  112. wandb/sdk/launch/sweeps/scheduler.py +2 -3
  113. wandb/sdk/lib/asyncio_compat.py +19 -16
  114. wandb/sdk/lib/asyncio_manager.py +252 -0
  115. wandb/sdk/lib/deprecate.py +1 -7
  116. wandb/sdk/lib/disabled.py +1 -1
  117. wandb/sdk/lib/hashutil.py +27 -5
  118. wandb/sdk/lib/module.py +7 -13
  119. wandb/sdk/lib/printer.py +2 -2
  120. wandb/sdk/lib/printer_asyncio.py +3 -1
  121. wandb/sdk/lib/progress.py +0 -19
  122. wandb/sdk/lib/retry.py +185 -78
  123. wandb/sdk/lib/service/service_client.py +106 -0
  124. wandb/sdk/lib/service/service_connection.py +20 -26
  125. wandb/sdk/lib/service/service_token.py +30 -13
  126. wandb/sdk/mailbox/mailbox.py +13 -5
  127. wandb/sdk/mailbox/mailbox_handle.py +22 -13
  128. wandb/sdk/mailbox/response_handle.py +42 -106
  129. wandb/sdk/mailbox/wait_with_progress.py +7 -42
  130. wandb/sdk/wandb_init.py +77 -116
  131. wandb/sdk/wandb_login.py +19 -15
  132. wandb/sdk/wandb_metric.py +2 -0
  133. wandb/sdk/wandb_run.py +497 -469
  134. wandb/sdk/wandb_settings.py +145 -4
  135. wandb/sdk/wandb_setup.py +204 -124
  136. wandb/sdk/wandb_sweep.py +14 -13
  137. wandb/sdk/wandb_watch.py +4 -6
  138. wandb/sync/sync.py +10 -0
  139. wandb/util.py +58 -1
  140. wandb/wandb_run.py +1 -2
  141. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/METADATA +1 -1
  142. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/RECORD +145 -129
  143. wandb/sdk/interface/interface_relay.py +0 -38
  144. wandb/sdk/interface/router.py +0 -89
  145. wandb/sdk/interface/router_queue.py +0 -43
  146. wandb/sdk/interface/router_relay.py +0 -50
  147. wandb/sdk/interface/router_sock.py +0 -32
  148. wandb/sdk/lib/sock_client.py +0 -236
  149. wandb/vendor/pynvml/__init__.py +0 -0
  150. wandb/vendor/pynvml/pynvml.py +0 -4779
  151. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/WHEEL +0 -0
  152. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/entry_points.txt +0 -0
  153. {wandb-0.21.0.dist-info → wandb-0.21.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,56 +1,53 @@
1
1
  """Public API: registries search."""
2
2
 
3
- import json
4
- from typing import TYPE_CHECKING, Any, Dict, Optional
3
+ from __future__ import annotations
5
4
 
6
- if TYPE_CHECKING:
7
- from wandb_gql import Client
5
+ import json
6
+ from typing import TYPE_CHECKING, Any
8
7
 
8
+ from pydantic import ValidationError
9
+ from typing_extensions import override
9
10
  from wandb_gql import gql
10
11
 
11
- import wandb
12
12
  from wandb.apis.paginator import Paginator
13
- from wandb.sdk.artifacts._graphql_fragments import (
14
- _gql_artifact_fragment,
15
- _gql_registry_fragment,
13
+ from wandb.apis.public.utils import gql_compat
14
+ from wandb.sdk.artifacts._generated import (
15
+ FETCH_REGISTRIES_GQL,
16
+ REGISTRY_COLLECTIONS_GQL,
17
+ REGISTRY_VERSIONS_GQL,
18
+ ArtifactCollectionType,
19
+ FetchRegistries,
20
+ RegistriesPage,
21
+ RegistryCollections,
22
+ RegistryCollectionsPage,
23
+ RegistryVersions,
24
+ RegistryVersionsPage,
16
25
  )
26
+ from wandb.sdk.artifacts._graphql_fragments import omit_artifact_fields
27
+ from wandb.sdk.artifacts._validators import remove_registry_prefix
17
28
 
18
29
  from ._utils import ensure_registry_prefix_on_names
19
30
 
31
+ if TYPE_CHECKING:
32
+ from wandb_gql import Client
33
+
34
+ from wandb.sdk.artifacts.artifact import Artifact
35
+
20
36
 
21
37
  class Registries(Paginator):
22
- """Iterator that returns Registries."""
23
-
24
- QUERY = gql(
25
- """
26
- query Registries($organization: String!, $filters: JSONString, $cursor: String, $perPage: Int) {
27
- organization(name: $organization) {
28
- orgEntity {
29
- name
30
- projects(filters: $filters, after: $cursor, first: $perPage) {
31
- pageInfo {
32
- endCursor
33
- hasNextPage
34
- }
35
- edges {
36
- node {
37
- ...RegistryFragment
38
- }
39
- }
40
- }
41
- }
42
- }
43
- }
44
- """
45
- + _gql_registry_fragment()
46
- )
38
+ """An lazy iterator of `Registry` objects."""
39
+
40
+ QUERY = gql(FETCH_REGISTRIES_GQL)
41
+
42
+ last_response: RegistriesPage | None
43
+ _last_org_entity: str | None
47
44
 
48
45
  def __init__(
49
46
  self,
50
- client: "Client",
47
+ client: Client,
51
48
  organization: str,
52
- filter: Optional[Dict[str, Any]] = None,
53
- per_page: Optional[int] = 100,
49
+ filter: dict[str, Any] | None = None,
50
+ per_page: int | None = 100,
54
51
  ):
55
52
  self.client = client
56
53
  self.organization = organization
@@ -62,8 +59,7 @@ class Registries(Paginator):
62
59
 
63
60
  super().__init__(client, variables, per_page)
64
61
 
65
- def __bool__(self):
66
- return bool(self.objects)
62
+ self._last_org_entity = None
67
63
 
68
64
  def __next__(self):
69
65
  # Implement custom next since its possible to load empty pages because of auth
@@ -73,18 +69,18 @@ class Registries(Paginator):
73
69
  raise StopIteration
74
70
  return self.objects[self.index]
75
71
 
76
- def collections(self, filter: Optional[Dict[str, Any]] = None) -> "Collections":
72
+ def collections(self, filter: dict[str, Any] | None = None) -> Collections:
77
73
  return Collections(
78
- self.client,
79
- self.organization,
74
+ client=self.client,
75
+ organization=self.organization,
80
76
  registry_filter=self.filter,
81
77
  collection_filter=filter,
82
78
  )
83
79
 
84
- def versions(self, filter: Optional[Dict[str, Any]] = None) -> "Versions":
80
+ def versions(self, filter: dict[str, Any] | None = None) -> Versions:
85
81
  return Versions(
86
- self.client,
87
- self.organization,
82
+ client=self.client,
83
+ organization=self.organization,
88
84
  registry_filter=self.filter,
89
85
  collection_filter=None,
90
86
  artifact_filter=filter,
@@ -92,132 +88,71 @@ class Registries(Paginator):
92
88
 
93
89
  @property
94
90
  def length(self):
95
- if self.last_response:
96
- return len(
97
- self.last_response["organization"]["orgEntity"]["projects"]["edges"]
98
- )
99
- else:
91
+ if self.last_response is None:
100
92
  return None
93
+ return len(self.last_response.edges)
101
94
 
102
95
  @property
103
96
  def more(self):
104
- if self.last_response:
105
- return self.last_response["organization"]["orgEntity"]["projects"][
106
- "pageInfo"
107
- ]["hasNextPage"]
108
- else:
97
+ if self.last_response is None:
109
98
  return True
99
+ return self.last_response.page_info.has_next_page
110
100
 
111
101
  @property
112
102
  def cursor(self):
113
- if self.last_response:
114
- return self.last_response["organization"]["orgEntity"]["projects"][
115
- "pageInfo"
116
- ]["endCursor"]
117
- else:
103
+ if self.last_response is None:
118
104
  return None
105
+ return self.last_response.page_info.end_cursor
119
106
 
120
- def convert_objects(self):
121
- if not self.last_response:
122
- return []
123
- if (
124
- not self.last_response["organization"]
125
- or not self.last_response["organization"]["orgEntity"]
126
- ):
107
+ @override
108
+ def _update_response(self) -> None:
109
+ data = self.client.execute(self.QUERY, variable_values=self.variables)
110
+ result = FetchRegistries.model_validate(data)
111
+ if not ((org := result.organization) and (org_entity := org.org_entity)):
127
112
  raise ValueError(
128
- f"Organization '{self.organization}' not found. Please verify the organization name is correct"
113
+ f"Organization {self.organization!r} not found. Please verify the organization name is correct."
129
114
  )
130
115
 
116
+ try:
117
+ page_data = org_entity.projects
118
+ self.last_response = RegistriesPage.model_validate(page_data)
119
+ self._last_org_entity = org_entity.name
120
+ except (LookupError, AttributeError, ValidationError) as e:
121
+ raise ValueError("Unexpected response data") from e
122
+
123
+ def convert_objects(self):
131
124
  from wandb.apis.public.registries.registry import Registry
132
125
 
126
+ if (self.last_response is None) or (self._last_org_entity is None):
127
+ return []
128
+
129
+ nodes = (e.node for e in self.last_response.edges)
133
130
  return [
134
131
  Registry(
135
- self.client,
136
- self.organization,
137
- self.last_response["organization"]["orgEntity"]["name"],
138
- r["node"]["name"],
139
- r["node"],
132
+ client=self.client,
133
+ organization=self.organization,
134
+ entity=self._last_org_entity,
135
+ name=remove_registry_prefix(node.name),
136
+ attrs=node.model_dump(),
140
137
  )
141
- for r in self.last_response["organization"]["orgEntity"]["projects"][
142
- "edges"
143
- ]
138
+ for node in nodes
144
139
  ]
145
140
 
146
141
 
147
- class Collections(Paginator):
148
- """Iterator that returns Artifact collections in the Registry."""
149
-
150
- QUERY = gql(
151
- """
152
- query Collections(
153
- $organization: String!,
154
- $registryFilter: JSONString,
155
- $collectionFilter: JSONString,
156
- $collectionTypes: [ArtifactCollectionType!],
157
- $cursor: String,
158
- $perPage: Int
159
- ) {
160
- organization(name: $organization) {
161
- orgEntity {
162
- name
163
- artifactCollections(
164
- projectFilters: $registryFilter,
165
- filters: $collectionFilter,
166
- collectionTypes: $collectionTypes,
167
- after: $cursor,
168
- first: $perPage
169
- ) {
170
- totalCount
171
- pageInfo {
172
- endCursor
173
- hasNextPage
174
- }
175
- edges {
176
- cursor
177
- node {
178
- id
179
- name
180
- description
181
- createdAt
182
- tags {
183
- edges {
184
- node {
185
- name
186
- }
187
- }
188
- }
189
- project {
190
- name
191
- entity {
192
- name
193
- }
194
- }
195
- defaultArtifactType {
196
- name
197
- }
198
- aliases {
199
- edges {
200
- node {
201
- alias
202
- }
203
- }
204
- }
205
- }
206
- }
207
- }
208
- }
209
- }
210
- }
211
- """
212
- )
142
+ class Collections(Paginator["ArtifactCollection"]):
143
+ """An lazy iterator of `ArtifactCollection` objects in a Registry."""
144
+
145
+ QUERY = gql(REGISTRY_COLLECTIONS_GQL)
146
+
147
+ last_response: RegistryCollectionsPage | None
213
148
 
214
149
  def __init__(
215
150
  self,
216
- client: "Client",
151
+ client: Client,
217
152
  organization: str,
218
- registry_filter: Optional[Dict[str, Any]] = None,
219
- collection_filter: Optional[Dict[str, Any]] = None,
220
- per_page: Optional[int] = 100,
153
+ registry_filter: dict[str, Any] | None = None,
154
+ collection_filter: dict[str, Any] | None = None,
155
+ per_page: int | None = 100,
221
156
  ):
222
157
  self.client = client
223
158
  self.organization = organization
@@ -225,22 +160,15 @@ class Collections(Paginator):
225
160
  self.collection_filter = collection_filter or {}
226
161
 
227
162
  variables = {
228
- "registryFilter": (
229
- json.dumps(self.registry_filter) if self.registry_filter else None
230
- ),
231
- "collectionFilter": (
232
- json.dumps(self.collection_filter) if self.collection_filter else None
233
- ),
234
- "organization": self.organization,
235
- "collectionTypes": ["PORTFOLIO"],
163
+ "registryFilter": json.dumps(f) if (f := registry_filter) else None,
164
+ "collectionFilter": json.dumps(f) if (f := collection_filter) else None,
165
+ "organization": organization,
166
+ "collectionTypes": [ArtifactCollectionType.PORTFOLIO],
236
167
  "perPage": per_page,
237
168
  }
238
169
 
239
170
  super().__init__(client, variables, per_page)
240
171
 
241
- def __bool__(self):
242
- return len(self) > 0 or len(self.objects) > 0
243
-
244
172
  def __next__(self):
245
173
  # Implement custom next since its possible to load empty pages because of auth
246
174
  self.index += 1
@@ -249,10 +177,10 @@ class Collections(Paginator):
249
177
  raise StopIteration
250
178
  return self.objects[self.index]
251
179
 
252
- def versions(self, filter: Optional[Dict[str, Any]] = None) -> "Versions":
180
+ def versions(self, filter: dict[str, Any] | None = None) -> Versions:
253
181
  return Versions(
254
- self.client,
255
- self.organization,
182
+ client=self.client,
183
+ organization=self.organization,
256
184
  registry_filter=self.registry_filter,
257
185
  collection_filter=self.collection_filter,
258
186
  artifact_filter=filter,
@@ -260,71 +188,75 @@ class Collections(Paginator):
260
188
 
261
189
  @property
262
190
  def length(self):
263
- if self.last_response:
264
- return self.last_response["organization"]["orgEntity"][
265
- "artifactCollections"
266
- ]["totalCount"]
267
- else:
191
+ if self.last_response is None:
268
192
  return None
193
+ return self.last_response.total_count
269
194
 
270
195
  @property
271
196
  def more(self):
272
- if self.last_response:
273
- return self.last_response["organization"]["orgEntity"][
274
- "artifactCollections"
275
- ]["pageInfo"]["hasNextPage"]
276
- else:
197
+ if self.last_response is None:
277
198
  return True
199
+ return self.last_response.page_info.has_next_page
278
200
 
279
201
  @property
280
202
  def cursor(self):
281
- if self.last_response:
282
- return self.last_response["organization"]["orgEntity"][
283
- "artifactCollections"
284
- ]["pageInfo"]["endCursor"]
285
- else:
203
+ if self.last_response is None:
286
204
  return None
205
+ return self.last_response.page_info.end_cursor
206
+
207
+ @override
208
+ def _update_response(self) -> None:
209
+ data = self.client.execute(self.QUERY, variable_values=self.variables)
210
+ result = RegistryCollections.model_validate(data)
211
+ if not (
212
+ (org_data := result.organization)
213
+ and (org_entity_data := org_data.org_entity)
214
+ ):
215
+ raise ValueError(
216
+ f"Organization {self.organization!r} not found. Please verify the organization name is correct."
217
+ )
218
+
219
+ try:
220
+ page_data = org_entity_data.artifact_collections
221
+ self.last_response = RegistryCollectionsPage.model_validate(page_data)
222
+ except (LookupError, AttributeError, ValidationError) as e:
223
+ raise ValueError("Unexpected response data") from e
287
224
 
288
225
  def convert_objects(self):
289
226
  from wandb.apis.public import ArtifactCollection
290
227
 
291
- if not self.last_response:
228
+ if self.last_response is None:
292
229
  return []
293
- if (
294
- not self.last_response["organization"]
295
- or not self.last_response["organization"]["orgEntity"]
296
- ):
297
- raise ValueError(
298
- f"Organization '{self.organization}' not found. Please verify the organization name is correct"
299
- )
300
230
 
231
+ nodes = (e.node for e in self.last_response.edges)
301
232
  return [
302
233
  ArtifactCollection(
303
- self.client,
304
- r["node"]["project"]["entity"]["name"],
305
- r["node"]["project"]["name"],
306
- r["node"]["name"],
307
- r["node"]["defaultArtifactType"]["name"],
308
- self.organization,
309
- r["node"],
234
+ client=self.client,
235
+ entity=project.entity.name,
236
+ project=project.name,
237
+ name=node.name,
238
+ type=node.default_artifact_type.name,
239
+ organization=self.organization,
240
+ attrs=node.model_dump(),
310
241
  is_sequence=False,
311
242
  )
312
- for r in self.last_response["organization"]["orgEntity"][
313
- "artifactCollections"
314
- ]["edges"]
243
+ for node in nodes
244
+ if (project := node.project)
315
245
  ]
316
246
 
317
247
 
318
- class Versions(Paginator):
319
- """Iterator that returns Artifact versions in the Registry."""
248
+ class Versions(Paginator["Artifact"]):
249
+ """An lazy iterator of `Artifact` objects in a Registry."""
250
+
251
+ last_response: RegistryVersionsPage | None
320
252
 
321
253
  def __init__(
322
254
  self,
323
- client: "Client",
255
+ client: Client,
324
256
  organization: str,
325
- registry_filter: Optional[Dict[str, Any]] = None,
326
- collection_filter: Optional[Dict[str, Any]] = None,
327
- artifact_filter: Optional[Dict[str, Any]] = None,
257
+ registry_filter: dict[str, Any] | None = None,
258
+ collection_filter: dict[str, Any] | None = None,
259
+ artifact_filter: dict[str, Any] | None = None,
328
260
  per_page: int = 100,
329
261
  ):
330
262
  self.client = client
@@ -332,69 +264,16 @@ class Versions(Paginator):
332
264
  self.registry_filter = registry_filter
333
265
  self.collection_filter = collection_filter
334
266
  self.artifact_filter = artifact_filter or {}
335
- self.QUERY = gql(
336
- """
337
- query Versions(
338
- $organization: String!,
339
- $registryFilter: JSONString,
340
- $collectionFilter: JSONString,
341
- $artifactFilter: JSONString,
342
- $cursor: String,
343
- $perPage: Int
344
- ) {
345
- organization(name: $organization) {
346
- orgEntity {
347
- name
348
- artifactMemberships(
349
- projectFilters: $registryFilter,
350
- collectionFilters: $collectionFilter,
351
- filters: $artifactFilter,
352
- after: $cursor,
353
- first: $perPage
354
- ) {
355
- pageInfo {
356
- endCursor
357
- hasNextPage
358
- }
359
- edges {
360
- node {
361
- artifactCollection {
362
- project {
363
- name
364
- entity {
365
- name
366
- }
367
- }
368
- name
369
- }
370
- versionIndex
371
- artifact {
372
- ...ArtifactFragment
373
- }
374
- aliases {
375
- alias
376
- }
377
- }
378
- }
379
- }
380
- }
381
- }
382
- }
383
- """
384
- + _gql_artifact_fragment(include_aliases=False)
267
+
268
+ self.QUERY = gql_compat(
269
+ REGISTRY_VERSIONS_GQL, omit_fields=omit_artifact_fields()
385
270
  )
386
271
 
387
272
  variables = {
388
- "registryFilter": (
389
- json.dumps(self.registry_filter) if self.registry_filter else None
390
- ),
391
- "collectionFilter": (
392
- json.dumps(self.collection_filter) if self.collection_filter else None
393
- ),
394
- "artifactFilter": (
395
- json.dumps(self.artifact_filter) if self.artifact_filter else None
396
- ),
397
- "organization": self.organization,
273
+ "registryFilter": json.dumps(f) if (f := registry_filter) else None,
274
+ "collectionFilter": json.dumps(f) if (f := collection_filter) else None,
275
+ "artifactFilter": json.dumps(f) if (f := artifact_filter) else None,
276
+ "organization": organization,
398
277
  }
399
278
 
400
279
  super().__init__(client, variables, per_page)
@@ -407,62 +286,62 @@ class Versions(Paginator):
407
286
  raise StopIteration
408
287
  return self.objects[self.index]
409
288
 
410
- def __bool__(self):
411
- return len(self) > 0 or len(self.objects) > 0
412
-
413
289
  @property
414
- def length(self):
415
- if self.last_response:
416
- return len(
417
- self.last_response["organization"]["orgEntity"]["artifactMemberships"][
418
- "edges"
419
- ]
420
- )
421
- else:
290
+ def length(self) -> int | None:
291
+ if self.last_response is None:
422
292
  return None
293
+ return len(self.last_response.edges)
423
294
 
424
295
  @property
425
- def more(self):
426
- if self.last_response:
427
- return self.last_response["organization"]["orgEntity"][
428
- "artifactMemberships"
429
- ]["pageInfo"]["hasNextPage"]
430
- else:
296
+ def more(self) -> bool:
297
+ if self.last_response is None:
431
298
  return True
299
+ return self.last_response.page_info.has_next_page
432
300
 
433
301
  @property
434
- def cursor(self):
435
- if self.last_response:
436
- return self.last_response["organization"]["orgEntity"][
437
- "artifactMemberships"
438
- ]["pageInfo"]["endCursor"]
439
- else:
302
+ def cursor(self) -> str | None:
303
+ if self.last_response is None:
440
304
  return None
441
-
442
- def convert_objects(self):
443
- if not self.last_response:
444
- return []
445
- if (
446
- not self.last_response["organization"]
447
- or not self.last_response["organization"]["orgEntity"]
305
+ return self.last_response.page_info.end_cursor
306
+
307
+ @override
308
+ def _update_response(self) -> None:
309
+ data = self.client.execute(self.QUERY, variable_values=self.variables)
310
+ result = RegistryVersions.model_validate(data)
311
+ if not (
312
+ (org_data := result.organization)
313
+ and (org_entity_data := org_data.org_entity)
448
314
  ):
449
315
  raise ValueError(
450
- f"Organization '{self.organization}' not found. Please verify the organization name is correct"
316
+ f"Organization {self.organization!r} not found. Please verify the organization name is correct."
451
317
  )
452
318
 
453
- artifacts = (
454
- wandb.Artifact._from_attrs(
455
- a["node"]["artifactCollection"]["project"]["entity"]["name"],
456
- a["node"]["artifactCollection"]["project"]["name"],
457
- a["node"]["artifactCollection"]["name"]
458
- + ":v"
459
- + str(a["node"]["versionIndex"]),
460
- a["node"]["artifact"],
461
- self.client,
462
- [alias["alias"] for alias in a["node"]["aliases"]],
319
+ try:
320
+ page_data = org_entity_data.artifact_memberships
321
+ self.last_response = RegistryVersionsPage.model_validate(page_data)
322
+ except (LookupError, AttributeError, ValidationError) as e:
323
+ raise ValueError("Unexpected response data") from e
324
+
325
+ def convert_objects(self) -> list[Artifact]:
326
+ from wandb.sdk.artifacts.artifact import Artifact
327
+
328
+ if self.last_response is None:
329
+ return []
330
+
331
+ nodes = (e.node for e in self.last_response.edges)
332
+ return [
333
+ Artifact._from_attrs(
334
+ entity=project.entity.name,
335
+ project=project.name,
336
+ name=f"{collection.name}:v{node.version_index}",
337
+ attrs=artifact,
338
+ client=self.client,
339
+ aliases=[alias.alias for alias in node.aliases],
463
340
  )
464
- for a in self.last_response["organization"]["orgEntity"][
465
- "artifactMemberships"
466
- ]["edges"]
467
- )
468
- return artifacts
341
+ for node in nodes
342
+ if (
343
+ (collection := node.artifact_collection)
344
+ and (project := collection.project)
345
+ and (artifact := node.artifact)
346
+ )
347
+ ]