wandb 0.22.2__py3-none-win_amd64.whl → 0.22.3__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 (147) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +2 -2
  3. wandb/_pydantic/__init__.py +8 -1
  4. wandb/_pydantic/base.py +54 -18
  5. wandb/_pydantic/field_types.py +8 -3
  6. wandb/_pydantic/pagination.py +46 -0
  7. wandb/_pydantic/utils.py +2 -2
  8. wandb/apis/public/api.py +24 -19
  9. wandb/apis/public/artifacts.py +259 -270
  10. wandb/apis/public/registries/_utils.py +40 -54
  11. wandb/apis/public/registries/registries_search.py +70 -85
  12. wandb/apis/public/registries/registry.py +173 -156
  13. wandb/apis/public/runs.py +27 -6
  14. wandb/apis/public/utils.py +43 -20
  15. wandb/automations/_generated/create_automation.py +2 -2
  16. wandb/automations/_generated/create_generic_webhook_integration.py +4 -4
  17. wandb/automations/_generated/delete_automation.py +2 -2
  18. wandb/automations/_generated/fragments.py +31 -52
  19. wandb/automations/_generated/generic_webhook_integrations_by_entity.py +3 -3
  20. wandb/automations/_generated/get_automations.py +3 -3
  21. wandb/automations/_generated/get_automations_by_entity.py +3 -3
  22. wandb/automations/_generated/input_types.py +9 -9
  23. wandb/automations/_generated/integrations_by_entity.py +3 -3
  24. wandb/automations/_generated/operations.py +6 -6
  25. wandb/automations/_generated/slack_integrations_by_entity.py +3 -3
  26. wandb/automations/_generated/update_automation.py +2 -2
  27. wandb/automations/_utils.py +3 -3
  28. wandb/automations/actions.py +3 -3
  29. wandb/automations/automations.py +6 -5
  30. wandb/bin/gpu_stats.exe +0 -0
  31. wandb/bin/wandb-core +0 -0
  32. wandb/cli/beta.py +8 -2
  33. wandb/cli/beta_leet.py +2 -1
  34. wandb/cli/beta_sync.py +1 -1
  35. wandb/errors/term.py +8 -8
  36. wandb/jupyter.py +0 -51
  37. wandb/old/settings.py +6 -6
  38. wandb/proto/v3/wandb_internal_pb2.py +351 -352
  39. wandb/proto/v3/wandb_server_pb2.py +38 -37
  40. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  41. wandb/proto/v3/wandb_sync_pb2.py +19 -6
  42. wandb/proto/v4/wandb_internal_pb2.py +351 -352
  43. wandb/proto/v4/wandb_server_pb2.py +38 -37
  44. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  45. wandb/proto/v4/wandb_sync_pb2.py +10 -6
  46. wandb/proto/v5/wandb_internal_pb2.py +351 -352
  47. wandb/proto/v5/wandb_server_pb2.py +38 -37
  48. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  49. wandb/proto/v5/wandb_sync_pb2.py +10 -6
  50. wandb/proto/v6/wandb_internal_pb2.py +351 -352
  51. wandb/proto/v6/wandb_server_pb2.py +38 -37
  52. wandb/proto/v6/wandb_settings_pb2.py +2 -2
  53. wandb/proto/v6/wandb_sync_pb2.py +10 -6
  54. wandb/sdk/artifacts/_generated/__init__.py +96 -40
  55. wandb/sdk/artifacts/_generated/add_aliases.py +3 -3
  56. wandb/sdk/artifacts/_generated/add_artifact_collection_tags.py +26 -0
  57. wandb/sdk/artifacts/_generated/artifact_by_id.py +2 -2
  58. wandb/sdk/artifacts/_generated/artifact_by_name.py +3 -3
  59. wandb/sdk/artifacts/_generated/artifact_collection_membership_file_urls.py +27 -8
  60. wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +27 -8
  61. wandb/sdk/artifacts/_generated/artifact_created_by.py +7 -20
  62. wandb/sdk/artifacts/_generated/artifact_file_urls.py +19 -6
  63. wandb/sdk/artifacts/_generated/artifact_membership_by_name.py +26 -0
  64. wandb/sdk/artifacts/_generated/artifact_type.py +5 -5
  65. wandb/sdk/artifacts/_generated/artifact_used_by.py +8 -17
  66. wandb/sdk/artifacts/_generated/artifact_version_files.py +19 -8
  67. wandb/sdk/artifacts/_generated/delete_aliases.py +3 -3
  68. wandb/sdk/artifacts/_generated/delete_artifact.py +4 -4
  69. wandb/sdk/artifacts/_generated/delete_artifact_collection_tags.py +23 -0
  70. wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +4 -4
  71. wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +4 -4
  72. wandb/sdk/artifacts/_generated/delete_registry.py +21 -0
  73. wandb/sdk/artifacts/_generated/fetch_artifact_manifest.py +8 -20
  74. wandb/sdk/artifacts/_generated/fetch_linked_artifacts.py +13 -35
  75. wandb/sdk/artifacts/_generated/fetch_org_info_from_entity.py +28 -0
  76. wandb/sdk/artifacts/_generated/fetch_registries.py +18 -8
  77. wandb/sdk/{projects → artifacts}/_generated/fetch_registry.py +4 -4
  78. wandb/sdk/artifacts/_generated/fragments.py +183 -333
  79. wandb/sdk/artifacts/_generated/input_types.py +133 -7
  80. wandb/sdk/artifacts/_generated/link_artifact.py +5 -5
  81. wandb/sdk/artifacts/_generated/operations.py +1053 -548
  82. wandb/sdk/artifacts/_generated/project_artifact_collection.py +9 -77
  83. wandb/sdk/artifacts/_generated/project_artifact_collections.py +21 -9
  84. wandb/sdk/artifacts/_generated/project_artifact_type.py +3 -3
  85. wandb/sdk/artifacts/_generated/project_artifact_types.py +19 -6
  86. wandb/sdk/artifacts/_generated/project_artifacts.py +7 -8
  87. wandb/sdk/artifacts/_generated/registry_collections.py +21 -9
  88. wandb/sdk/artifacts/_generated/registry_versions.py +20 -9
  89. wandb/sdk/artifacts/_generated/rename_registry.py +25 -0
  90. wandb/sdk/artifacts/_generated/run_input_artifacts.py +5 -9
  91. wandb/sdk/artifacts/_generated/run_output_artifacts.py +5 -9
  92. wandb/sdk/artifacts/_generated/type_info.py +2 -2
  93. wandb/sdk/artifacts/_generated/unlink_artifact.py +3 -5
  94. wandb/sdk/artifacts/_generated/update_artifact.py +3 -3
  95. wandb/sdk/artifacts/_generated/update_artifact_collection_type.py +28 -0
  96. wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +7 -16
  97. wandb/sdk/artifacts/_generated/update_artifact_sequence.py +7 -16
  98. wandb/sdk/artifacts/_generated/upsert_registry.py +25 -0
  99. wandb/sdk/artifacts/_gqlutils.py +170 -6
  100. wandb/sdk/artifacts/_models/__init__.py +9 -0
  101. wandb/sdk/artifacts/_models/artifact_collection.py +109 -0
  102. wandb/sdk/artifacts/_models/manifest.py +26 -0
  103. wandb/sdk/artifacts/_models/pagination.py +26 -0
  104. wandb/sdk/artifacts/_models/registry.py +100 -0
  105. wandb/sdk/artifacts/_validators.py +45 -27
  106. wandb/sdk/artifacts/artifact.py +220 -215
  107. wandb/sdk/artifacts/artifact_file_cache.py +1 -1
  108. wandb/sdk/artifacts/artifact_manifest.py +37 -32
  109. wandb/sdk/artifacts/artifact_manifest_entry.py +80 -125
  110. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +43 -61
  111. wandb/sdk/artifacts/storage_handlers/gcs_handler.py +8 -6
  112. wandb/sdk/data_types/image.py +2 -2
  113. wandb/sdk/interface/interface.py +72 -64
  114. wandb/sdk/interface/interface_queue.py +27 -18
  115. wandb/sdk/interface/interface_shared.py +61 -23
  116. wandb/sdk/interface/interface_sock.py +9 -5
  117. wandb/sdk/internal/_generated/server_features_query.py +4 -4
  118. wandb/sdk/launch/inputs/schema.py +13 -10
  119. wandb/sdk/lib/apikey.py +8 -12
  120. wandb/sdk/lib/asyncio_compat.py +1 -1
  121. wandb/sdk/lib/asyncio_manager.py +5 -5
  122. wandb/sdk/lib/console_capture.py +38 -30
  123. wandb/sdk/lib/progress.py +159 -64
  124. wandb/sdk/lib/retry.py +3 -2
  125. wandb/sdk/lib/service/service_connection.py +2 -2
  126. wandb/sdk/lib/wb_logging.py +2 -1
  127. wandb/sdk/mailbox/mailbox.py +1 -1
  128. wandb/sdk/wandb_init.py +10 -13
  129. wandb/sdk/wandb_run.py +9 -46
  130. wandb/sdk/wandb_settings.py +102 -19
  131. {wandb-0.22.2.dist-info → wandb-0.22.3.dist-info}/METADATA +2 -1
  132. {wandb-0.22.2.dist-info → wandb-0.22.3.dist-info}/RECORD +135 -134
  133. wandb/sdk/artifacts/_generated/artifact_via_membership_by_name.py +0 -26
  134. wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +0 -36
  135. wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +0 -25
  136. wandb/sdk/artifacts/_generated/move_artifact_collection.py +0 -35
  137. wandb/sdk/projects/_generated/__init__.py +0 -26
  138. wandb/sdk/projects/_generated/delete_project.py +0 -22
  139. wandb/sdk/projects/_generated/enums.py +0 -4
  140. wandb/sdk/projects/_generated/fragments.py +0 -41
  141. wandb/sdk/projects/_generated/input_types.py +0 -13
  142. wandb/sdk/projects/_generated/operations.py +0 -88
  143. wandb/sdk/projects/_generated/rename_project.py +0 -27
  144. wandb/sdk/projects/_generated/upsert_registry_project.py +0 -27
  145. {wandb-0.22.2.dist-info → wandb-0.22.3.dist-info}/WHEEL +0 -0
  146. {wandb-0.22.2.dist-info → wandb-0.22.3.dist-info}/entry_points.txt +0 -0
  147. {wandb-0.22.2.dist-info → wandb-0.22.3.dist-info}/licenses/LICENSE +0 -0
@@ -7,14 +7,25 @@ collections.
7
7
  from __future__ import annotations
8
8
 
9
9
  import json
10
- import re
11
10
  from copy import copy
12
- from typing import TYPE_CHECKING, Any, Iterable, Literal, Mapping, Sequence
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Any,
14
+ ClassVar,
15
+ Collection,
16
+ Iterable,
17
+ List,
18
+ Literal,
19
+ Mapping,
20
+ Sequence,
21
+ )
13
22
 
14
23
  from typing_extensions import override
15
- from wandb_gql import Client, gql
24
+ from wandb_gql import gql
16
25
 
17
26
  import wandb
27
+ from wandb._iterutils import always_list
28
+ from wandb._pydantic import ConnectionWithTotal, Edge
18
29
  from wandb._strutils import nameof
19
30
  from wandb.apis import public
20
31
  from wandb.apis.normalize import normalize_exceptions
@@ -23,13 +34,12 @@ from wandb.errors.term import termlog
23
34
  from wandb.proto.wandb_deprecated import Deprecated
24
35
  from wandb.proto.wandb_internal_pb2 import ServerFeature
25
36
  from wandb.sdk.artifacts._generated import (
37
+ ADD_ARTIFACT_COLLECTION_TAGS_GQL,
26
38
  ARTIFACT_COLLECTION_MEMBERSHIP_FILES_GQL,
27
39
  ARTIFACT_VERSION_FILES_GQL,
28
- CREATE_ARTIFACT_COLLECTION_TAG_ASSIGNMENTS_GQL,
29
- DELETE_ARTIFACT_COLLECTION_TAG_ASSIGNMENTS_GQL,
40
+ DELETE_ARTIFACT_COLLECTION_TAGS_GQL,
30
41
  DELETE_ARTIFACT_PORTFOLIO_GQL,
31
42
  DELETE_ARTIFACT_SEQUENCE_GQL,
32
- MOVE_ARTIFACT_COLLECTION_GQL,
33
43
  PROJECT_ARTIFACT_COLLECTION_GQL,
34
44
  PROJECT_ARTIFACT_COLLECTIONS_GQL,
35
45
  PROJECT_ARTIFACT_TYPE_GQL,
@@ -37,36 +47,42 @@ from wandb.sdk.artifacts._generated import (
37
47
  PROJECT_ARTIFACTS_GQL,
38
48
  RUN_INPUT_ARTIFACTS_GQL,
39
49
  RUN_OUTPUT_ARTIFACTS_GQL,
50
+ UPDATE_ARTIFACT_COLLECTION_TYPE_GQL,
40
51
  UPDATE_ARTIFACT_PORTFOLIO_GQL,
41
52
  UPDATE_ARTIFACT_SEQUENCE_GQL,
53
+ ArtifactCollectionFragment,
42
54
  ArtifactCollectionMembershipFiles,
43
- ArtifactCollectionsFragment,
44
- ArtifactsFragment,
55
+ ArtifactFragment,
45
56
  ArtifactTypeFragment,
46
- ArtifactTypesFragment,
47
57
  ArtifactVersionFiles,
48
- FilesFragment,
58
+ CreateArtifactCollectionTagAssignmentsInput,
59
+ DeleteArtifactCollectionTagAssignmentsInput,
60
+ MoveArtifactSequenceInput,
49
61
  ProjectArtifactCollection,
50
62
  ProjectArtifactCollections,
51
63
  ProjectArtifacts,
52
64
  ProjectArtifactType,
53
65
  ProjectArtifactTypes,
54
- RunInputArtifactConnectionFragment,
55
- RunOutputArtifactConnectionFragment,
66
+ UpdateArtifactPortfolioInput,
67
+ UpdateArtifactSequenceInput,
56
68
  )
57
69
  from wandb.sdk.artifacts._gqlutils import omit_artifact_fields
58
- from wandb.sdk.artifacts._validators import (
59
- SOURCE_ARTIFACT_COLLECTION_TYPE,
60
- FullArtifactPath,
61
- validate_artifact_name,
62
- validate_artifact_type,
70
+ from wandb.sdk.artifacts._models import ArtifactCollectionData
71
+ from wandb.sdk.artifacts._models.pagination import (
72
+ ArtifactCollectionConnection,
73
+ ArtifactFileConnection,
74
+ ArtifactTypeConnection,
75
+ RunArtifactConnection,
63
76
  )
77
+ from wandb.sdk.artifacts._validators import FullArtifactPath, validate_artifact_type
64
78
  from wandb.sdk.internal.internal_api import Api as InternalApi
65
79
  from wandb.sdk.lib import deprecate
66
80
 
67
81
  from .utils import gql_compat
68
82
 
69
83
  if TYPE_CHECKING:
84
+ from wandb_gql import Client
85
+
70
86
  from wandb.sdk.artifacts.artifact import Artifact
71
87
 
72
88
  from . import RetryingClient, Run
@@ -80,7 +96,7 @@ class ArtifactTypes(Paginator["ArtifactType"]):
80
96
 
81
97
  QUERY = gql(PROJECT_ARTIFACT_TYPES_GQL)
82
98
 
83
- last_response: ArtifactTypesFragment | None
99
+ last_response: ArtifactTypeConnection | None
84
100
 
85
101
  def __init__(
86
102
  self,
@@ -108,7 +124,7 @@ class ArtifactTypes(Paginator["ArtifactType"]):
108
124
  if not ((proj := result.project) and (conn := proj.artifact_types)):
109
125
  raise ValueError(f"Unable to parse {nameof(type(self))!r} response data")
110
126
 
111
- self.last_response = ArtifactTypesFragment.model_validate(conn)
127
+ self.last_response = ArtifactTypeConnection.model_validate(conn)
112
128
 
113
129
  @property
114
130
  def _length(self) -> None:
@@ -125,9 +141,7 @@ class ArtifactTypes(Paginator["ArtifactType"]):
125
141
 
126
142
  <!-- lazydoc-ignore: internal -->
127
143
  """
128
- if self.last_response is None:
129
- return True
130
- return self.last_response.page_info.has_next_page
144
+ return (conn := self.last_response) is None or conn.has_next
131
145
 
132
146
  @property
133
147
  def cursor(self) -> str | None:
@@ -135,9 +149,7 @@ class ArtifactTypes(Paginator["ArtifactType"]):
135
149
 
136
150
  <!-- lazydoc-ignore: internal -->
137
151
  """
138
- if self.last_response is None:
139
- return None
140
- return self.last_response.edges[-1].cursor
152
+ return conn.next_cursor if (conn := self.last_response) else None
141
153
 
142
154
  def update_variables(self) -> None:
143
155
  """Update the cursor variable for pagination.
@@ -160,10 +172,9 @@ class ArtifactTypes(Paginator["ArtifactType"]):
160
172
  entity=self.entity,
161
173
  project=self.project,
162
174
  type_name=node.name,
163
- attrs=node.model_dump(exclude_unset=True),
175
+ attrs=node,
164
176
  )
165
- for edge in self.last_response.edges
166
- if edge.node and (node := ArtifactTypeFragment.model_validate(edge.node))
177
+ for node in self.last_response.nodes()
167
178
  ]
168
179
 
169
180
 
@@ -181,51 +192,50 @@ class ArtifactType:
181
192
  <!-- lazydoc-ignore-init: internal -->
182
193
  """
183
194
 
195
+ _attrs: ArtifactTypeFragment
196
+
184
197
  def __init__(
185
198
  self,
186
199
  client: Client,
187
200
  entity: str,
188
201
  project: str,
189
202
  type_name: str,
190
- attrs: Mapping[str, Any] | None = None,
203
+ attrs: ArtifactTypeFragment | None = None,
191
204
  ):
192
205
  self.client = client
193
206
  self.entity = entity
194
207
  self.project = project
195
208
  self.type = type_name
196
- self._attrs = attrs
197
- if self._attrs is None:
198
- self.load()
199
209
 
200
- def load(self) -> Mapping[str, Any]:
210
+ # FIXME: Make this lazy, so we don't (re-)fetch the attributes until they are needed
211
+ self._attrs = ArtifactTypeFragment.model_validate(attrs or self.load())
212
+
213
+ def load(self) -> ArtifactTypeFragment:
201
214
  """Load the artifact type attributes from W&B.
202
215
 
203
216
  <!-- lazydoc-ignore: internal -->
204
217
  """
205
- data: Mapping[str, Any] | None = self.client.execute(
206
- gql(PROJECT_ARTIFACT_TYPE_GQL),
207
- variable_values={
208
- "entityName": self.entity,
209
- "projectName": self.project,
210
- "artifactTypeName": self.type,
211
- },
212
- )
218
+ gql_op = gql(PROJECT_ARTIFACT_TYPE_GQL)
219
+ gql_vars = {
220
+ "entityName": self.entity,
221
+ "projectName": self.project,
222
+ "artifactTypeName": self.type,
223
+ }
224
+ data = self.client.execute(gql_op, variable_values=gql_vars)
213
225
  result = ProjectArtifactType.model_validate(data)
214
226
  if not ((proj := result.project) and (artifact_type := proj.artifact_type)):
215
- raise ValueError(f"Could not find artifact type {self.type}")
216
-
217
- self._attrs = artifact_type.model_dump(exclude_unset=True)
218
- return self._attrs
227
+ raise ValueError(f"Could not find artifact type {self.type!r}")
228
+ return ArtifactTypeFragment.model_validate(artifact_type)
219
229
 
220
230
  @property
221
231
  def id(self) -> str:
222
232
  """The unique identifier of the artifact type."""
223
- return self._attrs["id"]
233
+ return self._attrs.id
224
234
 
225
235
  @property
226
236
  def name(self) -> str:
227
237
  """The name of the artifact type."""
228
- return self._attrs["name"]
238
+ return self._attrs.name
229
239
 
230
240
  @normalize_exceptions
231
241
  def collections(self, per_page: int = 50) -> ArtifactCollections:
@@ -235,7 +245,12 @@ class ArtifactType:
235
245
  per_page (int): The number of artifact collections to fetch per page.
236
246
  Default is 50.
237
247
  """
238
- return ArtifactCollections(self.client, self.entity, self.project, self.type)
248
+ return ArtifactCollections(
249
+ self.client,
250
+ entity=self.entity,
251
+ project=self.project,
252
+ type_name=self.type,
253
+ )
239
254
 
240
255
  def collection(self, name: str) -> ArtifactCollection:
241
256
  """Get a specific artifact collection by name.
@@ -244,7 +259,11 @@ class ArtifactType:
244
259
  name (str): The name of the artifact collection to retrieve.
245
260
  """
246
261
  return ArtifactCollection(
247
- self.client, self.entity, self.project, name, self.type
262
+ self.client,
263
+ entity=self.entity,
264
+ project=self.project,
265
+ name=name,
266
+ type=self.type,
248
267
  )
249
268
 
250
269
  def __repr__(self) -> str:
@@ -264,7 +283,7 @@ class ArtifactCollections(SizedPaginator["ArtifactCollection"]):
264
283
  <!-- lazydoc-ignore-init: internal -->
265
284
  """
266
285
 
267
- last_response: ArtifactCollectionsFragment | None
286
+ last_response: ArtifactCollectionConnection | None
268
287
 
269
288
  def __init__(
270
289
  self,
@@ -278,11 +297,7 @@ class ArtifactCollections(SizedPaginator["ArtifactCollection"]):
278
297
  self.project = project
279
298
  self.type_name = type_name
280
299
 
281
- variable_values = {
282
- "entityName": entity,
283
- "projectName": project,
284
- "artifactTypeName": type_name,
285
- }
300
+ variables = {"entity": entity, "project": project, "artifactType": type_name}
286
301
 
287
302
  if server_supports_artifact_collections_gql_edges(client):
288
303
  rename_fields = None
@@ -293,7 +308,7 @@ class ArtifactCollections(SizedPaginator["ArtifactCollection"]):
293
308
  PROJECT_ARTIFACT_COLLECTIONS_GQL, rename_fields=rename_fields
294
309
  )
295
310
 
296
- super().__init__(client, variable_values, per_page)
311
+ super().__init__(client, variables, per_page)
297
312
 
298
313
  @override
299
314
  def _update_response(self) -> None:
@@ -304,12 +319,12 @@ class ArtifactCollections(SizedPaginator["ArtifactCollection"]):
304
319
  # Extract the inner `*Connection` result for faster/easier access.
305
320
  if not (
306
321
  (proj := result.project)
307
- and (type_ := proj.artifact_type)
308
- and (conn := type_.artifact_collections)
322
+ and (artifact_type := proj.artifact_type)
323
+ and (conn := artifact_type.artifact_collections)
309
324
  ):
310
325
  raise ValueError(f"Unable to parse {nameof(type(self))!r} response data")
311
326
 
312
- self.last_response = ArtifactCollectionsFragment.model_validate(conn)
327
+ self.last_response = ArtifactCollectionConnection.model_validate(conn)
313
328
 
314
329
  @property
315
330
  def _length(self) -> int:
@@ -322,24 +337,20 @@ class ArtifactCollections(SizedPaginator["ArtifactCollection"]):
322
337
  return self.last_response.total_count
323
338
 
324
339
  @property
325
- def more(self):
340
+ def more(self) -> bool:
326
341
  """Returns whether there are more artifacts to fetch.
327
342
 
328
343
  <!-- lazydoc-ignore: internal -->
329
344
  """
330
- if self.last_response is None:
331
- return True
332
- return self.last_response.page_info.has_next_page
345
+ return (conn := self.last_response) is None or conn.has_next
333
346
 
334
347
  @property
335
- def cursor(self):
348
+ def cursor(self) -> str | None:
336
349
  """Returns the cursor for the next page of results.
337
350
 
338
351
  <!-- lazydoc-ignore: internal -->
339
352
  """
340
- if self.last_response is None:
341
- return None
342
- return self.last_response.edges[-1].cursor
353
+ return conn.next_cursor if (conn := self.last_response) else None
343
354
 
344
355
  def update_variables(self) -> None:
345
356
  """Update the cursor variable for pagination.
@@ -358,13 +369,14 @@ class ArtifactCollections(SizedPaginator["ArtifactCollection"]):
358
369
  return [
359
370
  ArtifactCollection(
360
371
  client=self.client,
361
- entity=self.entity,
362
- project=self.project,
372
+ entity=node.project.entity.name,
373
+ project=node.project.name,
363
374
  name=node.name,
364
- type=self.type_name,
375
+ type=node.type.name,
376
+ attrs=node,
365
377
  )
366
- for edge in self.last_response.edges
367
- if (node := edge.node)
378
+ for node in self.last_response.nodes()
379
+ if node.project
368
380
  ]
369
381
 
370
382
 
@@ -385,6 +397,12 @@ class ArtifactCollection:
385
397
  <!-- lazydoc-ignore-init: internal -->
386
398
  """
387
399
 
400
+ _saved: ArtifactCollectionData
401
+ """The saved artifact collection data as last fetched from the W&B server."""
402
+
403
+ _current: ArtifactCollectionData
404
+ """The local, editable artifact collection data."""
405
+
388
406
  def __init__(
389
407
  self,
390
408
  client: Client,
@@ -393,32 +411,34 @@ class ArtifactCollection:
393
411
  name: str,
394
412
  type: str,
395
413
  organization: str | None = None,
396
- attrs: Mapping[str, Any] | None = None,
397
- is_sequence: bool | None = None,
414
+ attrs: ArtifactCollectionFragment | None = None,
398
415
  ):
399
416
  self.client = client
400
- self.entity = entity
401
- self.project = project
402
- self._name = validate_artifact_name(name)
403
- self._saved_name = name
404
- self._type = type
405
- self._saved_type = type
406
- self._attrs = attrs
407
- if is_sequence is not None:
408
- self._is_sequence = is_sequence
409
- if (attrs is None) or (is_sequence is None):
410
- self.load()
411
- self._aliases = [a["node"]["alias"] for a in self._attrs["aliases"]["edges"]]
412
- self._description = self._attrs["description"]
413
- self._created_at = self._attrs["createdAt"]
414
- self._tags = [a["node"]["name"] for a in self._attrs["tags"]["edges"]]
415
- self._saved_tags = copy(self._tags)
417
+
418
+ # FIXME: Make this lazy, so we don't (re-)fetch the attributes until they are needed
419
+ fragment = attrs or self.load(entity, project, type, name)
420
+
421
+ # Separate "saved" vs "current" copies of the artifact collection data
422
+ validated = ArtifactCollectionData.from_fragment(fragment)
423
+ self._saved = validated
424
+ self._current = validated.model_copy(deep=True)
425
+
416
426
  self.organization = organization
417
427
 
418
428
  @property
419
429
  def id(self) -> str:
420
430
  """The unique identifier of the artifact collection."""
421
- return self._attrs["id"]
431
+ return self._current.id
432
+
433
+ @property
434
+ def entity(self) -> str:
435
+ """The entity (user or team) that owns the project."""
436
+ return self._current.entity
437
+
438
+ @property
439
+ def project(self) -> str:
440
+ """The project that contains the artifact collection."""
441
+ return self._current.project
422
442
 
423
443
  @normalize_exceptions
424
444
  def artifacts(self, per_page: int = 50) -> Artifacts:
@@ -427,23 +447,26 @@ class ArtifactCollection:
427
447
  client=self.client,
428
448
  entity=self.entity,
429
449
  project=self.project,
430
- collection_name=self._saved_name,
431
- type=self._saved_type,
450
+ # Use the saved name and type, since they're mutable and may have been edited locally.
451
+ collection_name=self._saved.name,
452
+ type=self._saved.type,
432
453
  per_page=per_page,
433
454
  )
434
455
 
435
456
  @property
436
457
  def aliases(self) -> list[str]:
437
458
  """Artifact Collection Aliases."""
438
- return self._aliases
459
+ return list(self._saved.aliases)
439
460
 
440
461
  @property
441
462
  def created_at(self) -> str:
442
463
  """The creation date of the artifact collection."""
443
- return self._created_at
464
+ return self._saved.created_at
444
465
 
445
- def load(self):
446
- """Load the artifact collection attributes from W&B.
466
+ def load(
467
+ self, entity: str, project: str, artifact_type: str, name: str
468
+ ) -> ArtifactCollectionFragment:
469
+ """Fetch and return the validated artifact collection data from W&B.
447
470
 
448
471
  <!-- lazydoc-ignore: internal -->
449
472
  """
@@ -452,34 +475,25 @@ class ArtifactCollection:
452
475
  else:
453
476
  rename_fields = {"artifactCollection": "artifactSequence"}
454
477
 
455
- response = self.client.execute(
456
- gql_compat(PROJECT_ARTIFACT_COLLECTION_GQL, rename_fields=rename_fields),
457
- variable_values={
458
- "entityName": self.entity,
459
- "projectName": self.project,
460
- "artifactTypeName": self._saved_type,
461
- "artifactCollectionName": self._saved_name,
462
- },
478
+ gql_op = gql_compat(
479
+ PROJECT_ARTIFACT_COLLECTION_GQL, rename_fields=rename_fields
463
480
  )
464
-
465
- result = ProjectArtifactCollection.model_validate(response)
466
-
481
+ gql_vars = {
482
+ "entity": entity,
483
+ "project": project,
484
+ "artifactType": artifact_type,
485
+ "name": name,
486
+ }
487
+ data = self.client.execute(gql_op, variable_values=gql_vars)
488
+ result = ProjectArtifactCollection.model_validate(data)
467
489
  if not (
468
490
  result.project
469
491
  and (proj := result.project)
470
492
  and (type_ := proj.artifact_type)
471
493
  and (collection := type_.artifact_collection)
472
494
  ):
473
- raise ValueError(f"Could not find artifact type {self._saved_type}")
474
-
475
- sequence = type_.artifact_sequence
476
- self._is_sequence = (
477
- sequence is not None
478
- ) and sequence.typename__ == SOURCE_ARTIFACT_COLLECTION_TYPE
479
-
480
- if self._attrs is None:
481
- self._attrs = collection.model_dump(exclude_unset=True)
482
- return self._attrs
495
+ raise ValueError(f"Could not find artifact type {artifact_type!s}")
496
+ return collection
483
497
 
484
498
  @normalize_exceptions
485
499
  def change_type(self, new_type: str) -> None:
@@ -489,174 +503,178 @@ class ArtifactCollection:
489
503
  warning_message="ArtifactCollection.change_type(type) is deprecated, use ArtifactCollection.save() instead.",
490
504
  )
491
505
 
492
- if self._saved_type != new_type:
506
+ if (old_type := self._saved.type) != new_type:
493
507
  try:
494
- validate_artifact_type(self._saved_type, self.name)
508
+ validate_artifact_type(old_type, self.name)
495
509
  except ValueError as e:
496
510
  raise ValueError(
497
- f"The current type '{self._saved_type!r}' is an internal type and cannot be changed."
511
+ f"The current type {old_type!r} is an internal type and cannot be changed."
498
512
  ) from e
499
513
 
500
514
  # Check that the new type is not going to conflict with internal types
501
- validate_artifact_type(new_type, self.name)
515
+ new_type = validate_artifact_type(new_type, self.name)
502
516
 
503
517
  if not self.is_sequence():
504
518
  raise ValueError("Artifact collection needs to be a sequence")
505
- termlog(
506
- f"Changing artifact collection type of {self._saved_type} to {new_type}"
507
- )
508
- self.client.execute(
509
- gql(MOVE_ARTIFACT_COLLECTION_GQL),
510
- variable_values={
511
- "artifactSequenceID": self.id,
512
- "destinationArtifactTypeName": new_type,
513
- },
519
+
520
+ termlog(f"Changing artifact collection type of {old_type!r} to {new_type!r}")
521
+
522
+ gql_op = gql(UPDATE_ARTIFACT_COLLECTION_TYPE_GQL)
523
+ gql_input = MoveArtifactSequenceInput(
524
+ artifact_sequence_id=self.id,
525
+ destination_artifact_type_name=new_type,
514
526
  )
515
- self._saved_type = new_type
516
- self._type = new_type
527
+ self.client.execute(gql_op, variable_values={"input": gql_input.model_dump()})
528
+ self._saved.type = new_type
529
+ self._current.type = new_type
517
530
 
518
531
  def is_sequence(self) -> bool:
519
532
  """Return whether the artifact collection is a sequence."""
520
- return self._is_sequence
533
+ return self._saved.is_sequence
521
534
 
522
535
  @normalize_exceptions
523
536
  def delete(self) -> None:
524
537
  """Delete the entire artifact collection."""
525
- self.client.execute(
526
- gql(
527
- DELETE_ARTIFACT_SEQUENCE_GQL
528
- if self.is_sequence()
529
- else DELETE_ARTIFACT_PORTFOLIO_GQL
530
- ),
531
- variable_values={"id": self.id},
538
+ gql_op = gql(
539
+ DELETE_ARTIFACT_SEQUENCE_GQL
540
+ if self.is_sequence()
541
+ else DELETE_ARTIFACT_PORTFOLIO_GQL
532
542
  )
543
+ self.client.execute(gql_op, variable_values={"id": self.id})
533
544
 
534
545
  @property
535
- def description(self) -> str:
546
+ def description(self) -> str | None:
536
547
  """A description of the artifact collection."""
537
- return self._description
548
+ return self._current.description
538
549
 
539
550
  @description.setter
540
551
  def description(self, description: str | None) -> None:
541
552
  """Set the description of the artifact collection."""
542
- self._description = description
553
+ self._current.description = description
543
554
 
544
555
  @property
545
556
  def tags(self) -> list[str]:
546
557
  """The tags associated with the artifact collection."""
547
- return self._tags
558
+ return self._current.tags
548
559
 
549
560
  @tags.setter
550
- def tags(self, tags: list[str]) -> None:
561
+ def tags(self, tags: Collection[str]) -> None:
551
562
  """Set the tags associated with the artifact collection."""
552
- if any(not re.match(r"^[-\w]+([ ]+[-\w]+)*$", tag) for tag in tags):
553
- raise ValueError(
554
- "Tags must only contain alphanumeric characters or underscores separated by spaces or hyphens"
555
- )
556
- self._tags = tags
563
+ self._current.tags = tags
557
564
 
558
565
  @property
559
566
  def name(self) -> str:
560
567
  """The name of the artifact collection."""
561
- return self._name
568
+ return self._current.name
562
569
 
563
570
  @name.setter
564
571
  def name(self, name: str) -> None:
565
572
  """Set the name of the artifact collection."""
566
- self._name = validate_artifact_name(name)
573
+ self._current.name = name
567
574
 
568
575
  @property
569
576
  def type(self):
570
577
  """Returns the type of the artifact collection."""
571
- return self._type
578
+ return self._current.type
572
579
 
573
580
  @type.setter
574
- def type(self, type: list[str]) -> None:
581
+ def type(self, type: str) -> None:
575
582
  """Set the type of the artifact collection."""
576
583
  if not self.is_sequence():
577
584
  raise ValueError(
578
585
  "Type can only be changed if the artifact collection is a sequence."
579
586
  )
580
- self._type = type
587
+ self._current.type = type
581
588
 
582
589
  def _update_collection(self) -> None:
583
- self.client.execute(
584
- gql(
585
- UPDATE_ARTIFACT_SEQUENCE_GQL
586
- if self.is_sequence()
587
- else UPDATE_ARTIFACT_PORTFOLIO_GQL
588
- ),
589
- variable_values={
590
- "id": self.id,
591
- "name": self.name,
592
- "description": self.description,
593
- },
594
- )
595
- self._saved_name = self._name
590
+ if self.is_sequence():
591
+ gql_op = gql(UPDATE_ARTIFACT_SEQUENCE_GQL)
592
+ gql_input = UpdateArtifactSequenceInput(
593
+ artifact_sequence_id=self.id,
594
+ name=self.name,
595
+ description=self.description,
596
+ )
597
+ else:
598
+ gql_op = gql(UPDATE_ARTIFACT_PORTFOLIO_GQL)
599
+ gql_input = UpdateArtifactPortfolioInput(
600
+ artifact_portfolio_id=self.id,
601
+ name=self.name,
602
+ description=self.description,
603
+ )
604
+ self.client.execute(gql_op, variable_values={"input": gql_input.model_dump()})
605
+ self._saved.name = self._current.name
596
606
 
597
607
  def _update_collection_type(self) -> None:
598
- self.client.execute(
599
- gql(MOVE_ARTIFACT_COLLECTION_GQL),
600
- variable_values={
601
- "artifactSequenceID": self.id,
602
- "destinationArtifactTypeName": self.type,
603
- },
608
+ gql_op = gql(UPDATE_ARTIFACT_COLLECTION_TYPE_GQL)
609
+ gql_input = MoveArtifactSequenceInput(
610
+ artifact_sequence_id=self.id,
611
+ destination_artifact_type_name=self.type,
604
612
  )
605
- self._saved_type = self._type
606
-
607
- def _add_tags(self, tags_to_add: Iterable[str]) -> None:
608
- self.client.execute(
609
- gql(CREATE_ARTIFACT_COLLECTION_TAG_ASSIGNMENTS_GQL),
610
- variable_values={
611
- "entityName": self.entity,
612
- "projectName": self.project,
613
- "artifactCollectionName": self._saved_name,
614
- "tags": [{"tagName": tag} for tag in tags_to_add],
615
- },
613
+ self.client.execute(gql_op, variable_values={"input": gql_input.model_dump()})
614
+ self._saved.type = self._current.type
615
+
616
+ def _add_tags(self, tag_names: Iterable[str]) -> None:
617
+ gql_op = gql(ADD_ARTIFACT_COLLECTION_TAGS_GQL)
618
+ gql_input = CreateArtifactCollectionTagAssignmentsInput(
619
+ entity_name=self.entity,
620
+ project_name=self.project,
621
+ artifact_collection_name=self._saved.name,
622
+ tags=[{"tagName": tag} for tag in tag_names],
616
623
  )
617
-
618
- def _delete_tags(self, tags_to_delete: Iterable[str]) -> None:
619
- self.client.execute(
620
- gql(DELETE_ARTIFACT_COLLECTION_TAG_ASSIGNMENTS_GQL),
621
- variable_values={
622
- "entityName": self.entity,
623
- "projectName": self.project,
624
- "artifactCollectionName": self._saved_name,
625
- "tags": [{"tagName": tag} for tag in tags_to_delete],
626
- },
624
+ self.client.execute(gql_op, variable_values={"input": gql_input.model_dump()})
625
+
626
+ def _delete_tags(self, tag_names: Iterable[str]) -> None:
627
+ gql_op = gql(DELETE_ARTIFACT_COLLECTION_TAGS_GQL)
628
+ gql_input = DeleteArtifactCollectionTagAssignmentsInput(
629
+ entity_name=self.entity,
630
+ project_name=self.project,
631
+ artifact_collection_name=self._saved.name,
632
+ tags=[{"tagName": tag} for tag in tag_names],
627
633
  )
634
+ self.client.execute(gql_op, variable_values={"input": gql_input.model_dump()})
628
635
 
629
636
  @normalize_exceptions
630
637
  def save(self) -> None:
631
638
  """Persist any changes made to the artifact collection."""
632
- if self._saved_type != self.type:
639
+ if (old_type := self._saved.type) != (new_type := self.type):
633
640
  try:
634
- validate_artifact_type(self.type, self._name)
641
+ validate_artifact_type(new_type, self.name)
635
642
  except ValueError as e:
636
- raise ValueError(f"Failed to save artifact collection: {e}") from e
643
+ reason = str(e)
644
+ raise ValueError(
645
+ f"Failed to save artifact collection {self.name!r}: {reason}"
646
+ ) from e
637
647
  try:
638
- validate_artifact_type(self._saved_type, self._name)
648
+ validate_artifact_type(old_type, self.name)
639
649
  except ValueError as e:
650
+ reason = f"The current type {old_type!r} is an internal type and cannot be changed."
640
651
  raise ValueError(
641
- f"Failed to save artifact collection '{self._name}': "
642
- f"The current type '{self._saved_type!r}' is an internal type and cannot be changed."
652
+ f"Failed to save artifact collection {self.name!r}: {reason}"
643
653
  ) from e
644
654
 
655
+ # FIXME: Consider consolidating the multiple GQL mutations into a single call.
645
656
  self._update_collection()
646
657
 
647
- if self.is_sequence() and (self._saved_type != self._type):
658
+ if self.is_sequence() and (old_type != new_type):
648
659
  self._update_collection_type()
649
660
 
650
- current_tags = set(self._tags)
651
- saved_tags = set(self._saved_tags)
652
- if tags_to_add := (current_tags - saved_tags):
653
- self._add_tags(tags_to_add)
654
- if tags_to_delete := (saved_tags - current_tags):
655
- self._delete_tags(tags_to_delete)
656
- self._saved_tags = copy(self._tags)
661
+ if (new_tags := set(self._current.tags)) != (old_tags := set(self._saved.tags)):
662
+ if added_tags := (new_tags - old_tags):
663
+ self._add_tags(added_tags)
664
+ if deleted_tags := (old_tags - new_tags):
665
+ self._delete_tags(deleted_tags)
666
+ self._saved.tags = copy(new_tags)
657
667
 
658
668
  def __repr__(self) -> str:
659
- return f"<ArtifactCollection {self._name} ({self._type})>"
669
+ return f"<ArtifactCollection {self.name} ({self.type})>"
670
+
671
+
672
+ class _ArtifactEdge(Edge[ArtifactFragment]):
673
+ version: str # Extra field defined only on VersionedArtifactEdge
674
+
675
+
676
+ class _ArtifactConnection(ConnectionWithTotal[ArtifactFragment]):
677
+ edges: List[_ArtifactEdge] # noqa: UP006
660
678
 
661
679
 
662
680
  class Artifacts(SizedPaginator["Artifact"]):
@@ -679,7 +697,7 @@ class Artifacts(SizedPaginator["Artifact"]):
679
697
  <!-- lazydoc-ignore-init: internal -->
680
698
  """
681
699
 
682
- last_response: ArtifactsFragment | None
700
+ last_response: _ArtifactConnection | None
683
701
 
684
702
  def __init__(
685
703
  self,
@@ -698,7 +716,7 @@ class Artifacts(SizedPaginator["Artifact"]):
698
716
  self.type = type
699
717
  self.project = project
700
718
  self.filters = {"state": "COMMITTED"} if filters is None else filters
701
- self.tags = [tags] if isinstance(tags, str) else tags
719
+ self.tags = always_list(tags or [])
702
720
  self.order = order
703
721
  variables = {
704
722
  "project": self.project,
@@ -736,7 +754,7 @@ class Artifacts(SizedPaginator["Artifact"]):
736
754
  ):
737
755
  raise ValueError(f"Unable to parse {nameof(type(self))!r} response data")
738
756
 
739
- self.last_response = ArtifactsFragment.model_validate(conn)
757
+ self.last_response = _ArtifactConnection.model_validate(conn)
740
758
 
741
759
  @property
742
760
  def _length(self) -> int:
@@ -754,9 +772,7 @@ class Artifacts(SizedPaginator["Artifact"]):
754
772
 
755
773
  <!-- lazydoc-ignore: internal -->
756
774
  """
757
- if self.last_response is None:
758
- return True
759
- return self.last_response.page_info.has_next_page
775
+ return (conn := self.last_response) is None or conn.has_next
760
776
 
761
777
  @property
762
778
  def cursor(self) -> str | None:
@@ -764,9 +780,7 @@ class Artifacts(SizedPaginator["Artifact"]):
764
780
 
765
781
  <!-- lazydoc-ignore: internal -->
766
782
  """
767
- if self.last_response is None:
768
- return None
769
- return self.last_response.edges[-1].cursor
783
+ return conn.next_cursor if (conn := self.last_response) else None
770
784
 
771
785
  def convert_objects(self) -> list[Artifact]:
772
786
  """Convert the raw response data into a list of wandb.Artifact objects.
@@ -799,14 +813,13 @@ class RunArtifacts(SizedPaginator["Artifact"]):
799
813
  <!-- lazydoc-ignore-init: internal -->
800
814
  """
801
815
 
802
- last_response: (
803
- RunOutputArtifactConnectionFragment | RunInputArtifactConnectionFragment
804
- )
816
+ last_response: RunArtifactConnection | None
805
817
 
806
- #: The pydantic model used to parse the (inner part of the) raw response.
807
- _response_cls: type[
808
- RunOutputArtifactConnectionFragment | RunInputArtifactConnectionFragment
809
- ]
818
+ _mode2gqlstr: ClassVar[dict[Literal["logged", "used"], str]] = {
819
+ "logged": RUN_OUTPUT_ARTIFACTS_GQL,
820
+ "used": RUN_INPUT_ARTIFACTS_GQL,
821
+ }
822
+ """Maps the mode ("logged" or "used") to the corresponding GraphQL query string."""
810
823
 
811
824
  def __init__(
812
825
  self,
@@ -817,20 +830,12 @@ class RunArtifacts(SizedPaginator["Artifact"]):
817
830
  ):
818
831
  self.run = run
819
832
 
820
- if mode == "logged":
821
- self.run_key = "outputArtifacts"
822
- self.QUERY = gql_compat(
823
- RUN_OUTPUT_ARTIFACTS_GQL, omit_fields=omit_artifact_fields(client)
824
- )
825
- self._response_cls = RunOutputArtifactConnectionFragment
826
- elif mode == "used":
827
- self.run_key = "inputArtifacts"
828
- self.QUERY = gql_compat(
829
- RUN_INPUT_ARTIFACTS_GQL, omit_fields=omit_artifact_fields(client)
830
- )
831
- self._response_cls = RunInputArtifactConnectionFragment
832
- else:
833
+ try:
834
+ query_str = self._mode2gqlstr[mode]
835
+ except LookupError:
833
836
  raise ValueError("mode must be logged or used")
837
+ else:
838
+ self.QUERY = gql_compat(query_str, omit_fields=omit_artifact_fields(client))
834
839
 
835
840
  variable_values = {
836
841
  "entity": run.entity,
@@ -844,8 +849,8 @@ class RunArtifacts(SizedPaginator["Artifact"]):
844
849
  data = self.client.execute(self.QUERY, variable_values=self.variables)
845
850
 
846
851
  # Extract the inner `*Connection` result for faster/easier access.
847
- inner_data = data["project"]["run"][self.run_key]
848
- self.last_response = self._response_cls.model_validate(inner_data)
852
+ inner_data = data["project"]["run"]["artifacts"]
853
+ self.last_response = RunArtifactConnection.model_validate(inner_data)
849
854
 
850
855
  @property
851
856
  def _length(self) -> int:
@@ -863,9 +868,7 @@ class RunArtifacts(SizedPaginator["Artifact"]):
863
868
 
864
869
  <!-- lazydoc-ignore: internal -->
865
870
  """
866
- if self.last_response is None:
867
- return True
868
- return self.last_response.page_info.has_next_page
871
+ return (conn := self.last_response) is None or conn.has_next
869
872
 
870
873
  @property
871
874
  def cursor(self) -> str | None:
@@ -873,9 +876,7 @@ class RunArtifacts(SizedPaginator["Artifact"]):
873
876
 
874
877
  <!-- lazydoc-ignore: internal -->
875
878
  """
876
- if self.last_response is None:
877
- return None
878
- return self.last_response.edges[-1].cursor
879
+ return conn.next_cursor if (conn := self.last_response) else None
879
880
 
880
881
  def convert_objects(self) -> list[Artifact]:
881
882
  """Convert the raw response data into a list of wandb.Artifact objects.
@@ -888,16 +889,15 @@ class RunArtifacts(SizedPaginator["Artifact"]):
888
889
  return [
889
890
  wandb.Artifact._from_attrs(
890
891
  path=FullArtifactPath(
891
- prefix=proj.entity_name,
892
+ prefix=proj.entity.name,
892
893
  project=proj.name,
893
894
  name=f"{artifact_seq.name}:v{node.version_index}",
894
895
  ),
895
896
  attrs=node,
896
897
  client=self.client,
897
898
  )
898
- for edge in self.last_response.edges
899
- if (node := edge.node)
900
- and (artifact_seq := node.artifact_sequence)
899
+ for node in self.last_response.nodes()
900
+ if (artifact_seq := node.artifact_sequence)
901
901
  and (proj := artifact_seq.project)
902
902
  ]
903
903
 
@@ -908,7 +908,7 @@ class ArtifactFiles(SizedPaginator["public.File"]):
908
908
  <!-- lazydoc-ignore-init: internal -->
909
909
  """
910
910
 
911
- last_response: FilesFragment | None
911
+ last_response: ArtifactFileConnection | None
912
912
 
913
913
  def __init__(
914
914
  self,
@@ -965,7 +965,7 @@ class ArtifactFiles(SizedPaginator["public.File"]):
965
965
  if conn is None:
966
966
  raise ValueError(f"Unable to parse {nameof(type(self))!r} response data")
967
967
 
968
- self.last_response = FilesFragment.model_validate(conn)
968
+ self.last_response = ArtifactFileConnection.model_validate(conn)
969
969
 
970
970
  @property
971
971
  def path(self) -> list[str]:
@@ -974,12 +974,12 @@ class ArtifactFiles(SizedPaginator["public.File"]):
974
974
 
975
975
  @property
976
976
  def _length(self) -> int:
977
- if self.last_response is None:
978
- self._load_page()
979
977
  """Returns the total number of files in the artifact.
980
978
 
981
979
  <!-- lazydoc-ignore: internal -->
982
980
  """
981
+ if self.last_response is None:
982
+ self._load_page()
983
983
  return self.artifact.file_count
984
984
 
985
985
  @property
@@ -988,9 +988,7 @@ class ArtifactFiles(SizedPaginator["public.File"]):
988
988
 
989
989
  <!-- lazydoc-ignore: internal -->
990
990
  """
991
- if self.last_response is None:
992
- return True
993
- return self.last_response.page_info.has_next_page
991
+ return (conn := self.last_response) is None or conn.has_next
994
992
 
995
993
  @property
996
994
  def cursor(self) -> str | None:
@@ -998,19 +996,10 @@ class ArtifactFiles(SizedPaginator["public.File"]):
998
996
 
999
997
  <!-- lazydoc-ignore: internal -->
1000
998
  """
1001
- if self.last_response is None:
1002
- return None
1003
- return self.last_response.edges[-1].cursor
1004
-
1005
- def update_variables(self) -> None:
1006
- """Update the variables dictionary with the cursor.
1007
-
1008
- <!-- lazydoc-ignore: internal -->
1009
- """
1010
- self.variables.update({"fileLimit": self.per_page, "fileCursor": self.cursor})
999
+ return conn.next_cursor if (conn := self.last_response) else None
1011
1000
 
1012
1001
  def convert_objects(self) -> list[public.File]:
1013
- """Convert the raw response data into a list of public.File objects.
1002
+ """Convert the raw response data into a list of File objects.
1014
1003
 
1015
1004
  <!-- lazydoc-ignore: internal -->
1016
1005
  """