wandb 0.22.1__py3-none-macosx_12_0_arm64.whl → 0.22.3__py3-none-macosx_12_0_arm64.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 (166) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +7 -4
  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 +0 -0
  31. wandb/bin/wandb-core +0 -0
  32. wandb/cli/beta.py +23 -3
  33. wandb/cli/beta_leet.py +75 -0
  34. wandb/cli/beta_sync.py +1 -1
  35. wandb/cli/cli.py +34 -7
  36. wandb/errors/term.py +8 -8
  37. wandb/jupyter.py +0 -51
  38. wandb/old/settings.py +6 -6
  39. wandb/proto/v3/wandb_api_pb2.py +86 -0
  40. wandb/proto/v3/wandb_server_pb2.py +38 -37
  41. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  42. wandb/proto/v3/wandb_sync_pb2.py +19 -6
  43. wandb/proto/v4/wandb_api_pb2.py +37 -0
  44. wandb/proto/v4/wandb_server_pb2.py +38 -37
  45. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  46. wandb/proto/v4/wandb_sync_pb2.py +10 -6
  47. wandb/proto/v5/wandb_api_pb2.py +38 -0
  48. wandb/proto/v5/wandb_server_pb2.py +38 -37
  49. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  50. wandb/proto/v5/wandb_sync_pb2.py +10 -6
  51. wandb/proto/v6/wandb_api_pb2.py +48 -0
  52. wandb/proto/v6/wandb_server_pb2.py +38 -37
  53. wandb/proto/v6/wandb_settings_pb2.py +2 -2
  54. wandb/proto/v6/wandb_sync_pb2.py +10 -6
  55. wandb/proto/wandb_api_pb2.py +18 -0
  56. wandb/proto/wandb_generate_proto.py +1 -0
  57. wandb/sdk/artifacts/_generated/__init__.py +96 -40
  58. wandb/sdk/artifacts/_generated/add_aliases.py +3 -3
  59. wandb/sdk/artifacts/_generated/add_artifact_collection_tags.py +26 -0
  60. wandb/sdk/artifacts/_generated/artifact_by_id.py +2 -2
  61. wandb/sdk/artifacts/_generated/artifact_by_name.py +3 -3
  62. wandb/sdk/artifacts/_generated/artifact_collection_membership_file_urls.py +27 -8
  63. wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +27 -8
  64. wandb/sdk/artifacts/_generated/artifact_created_by.py +7 -20
  65. wandb/sdk/artifacts/_generated/artifact_file_urls.py +19 -6
  66. wandb/sdk/artifacts/_generated/artifact_membership_by_name.py +26 -0
  67. wandb/sdk/artifacts/_generated/artifact_type.py +5 -5
  68. wandb/sdk/artifacts/_generated/artifact_used_by.py +8 -17
  69. wandb/sdk/artifacts/_generated/artifact_version_files.py +19 -8
  70. wandb/sdk/artifacts/_generated/delete_aliases.py +3 -3
  71. wandb/sdk/artifacts/_generated/delete_artifact.py +4 -4
  72. wandb/sdk/artifacts/_generated/delete_artifact_collection_tags.py +23 -0
  73. wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +4 -4
  74. wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +4 -4
  75. wandb/sdk/artifacts/_generated/delete_registry.py +21 -0
  76. wandb/sdk/artifacts/_generated/fetch_artifact_manifest.py +8 -20
  77. wandb/sdk/artifacts/_generated/fetch_linked_artifacts.py +13 -35
  78. wandb/sdk/artifacts/_generated/fetch_org_info_from_entity.py +28 -0
  79. wandb/sdk/artifacts/_generated/fetch_registries.py +18 -8
  80. wandb/sdk/{projects → artifacts}/_generated/fetch_registry.py +4 -4
  81. wandb/sdk/artifacts/_generated/fragments.py +183 -333
  82. wandb/sdk/artifacts/_generated/input_types.py +133 -7
  83. wandb/sdk/artifacts/_generated/link_artifact.py +5 -5
  84. wandb/sdk/artifacts/_generated/operations.py +1053 -548
  85. wandb/sdk/artifacts/_generated/project_artifact_collection.py +9 -77
  86. wandb/sdk/artifacts/_generated/project_artifact_collections.py +21 -9
  87. wandb/sdk/artifacts/_generated/project_artifact_type.py +3 -3
  88. wandb/sdk/artifacts/_generated/project_artifact_types.py +19 -6
  89. wandb/sdk/artifacts/_generated/project_artifacts.py +7 -8
  90. wandb/sdk/artifacts/_generated/registry_collections.py +21 -9
  91. wandb/sdk/artifacts/_generated/registry_versions.py +20 -9
  92. wandb/sdk/artifacts/_generated/rename_registry.py +25 -0
  93. wandb/sdk/artifacts/_generated/run_input_artifacts.py +5 -9
  94. wandb/sdk/artifacts/_generated/run_output_artifacts.py +5 -9
  95. wandb/sdk/artifacts/_generated/type_info.py +2 -2
  96. wandb/sdk/artifacts/_generated/unlink_artifact.py +3 -5
  97. wandb/sdk/artifacts/_generated/update_artifact.py +3 -3
  98. wandb/sdk/artifacts/_generated/update_artifact_collection_type.py +28 -0
  99. wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +7 -16
  100. wandb/sdk/artifacts/_generated/update_artifact_sequence.py +7 -16
  101. wandb/sdk/artifacts/_generated/upsert_registry.py +25 -0
  102. wandb/sdk/artifacts/_gqlutils.py +170 -6
  103. wandb/sdk/artifacts/_models/__init__.py +9 -0
  104. wandb/sdk/artifacts/_models/artifact_collection.py +109 -0
  105. wandb/sdk/artifacts/_models/manifest.py +26 -0
  106. wandb/sdk/artifacts/_models/pagination.py +26 -0
  107. wandb/sdk/artifacts/_models/registry.py +100 -0
  108. wandb/sdk/artifacts/_validators.py +45 -27
  109. wandb/sdk/artifacts/artifact.py +249 -244
  110. wandb/sdk/artifacts/artifact_file_cache.py +1 -1
  111. wandb/sdk/artifacts/artifact_manifest.py +37 -32
  112. wandb/sdk/artifacts/artifact_manifest_entry.py +82 -133
  113. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +43 -61
  114. wandb/sdk/artifacts/storage_handler.py +18 -12
  115. wandb/sdk/artifacts/storage_handlers/azure_handler.py +11 -6
  116. wandb/sdk/artifacts/storage_handlers/gcs_handler.py +17 -12
  117. wandb/sdk/artifacts/storage_handlers/http_handler.py +9 -4
  118. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +10 -6
  119. wandb/sdk/artifacts/storage_handlers/multi_handler.py +5 -4
  120. wandb/sdk/artifacts/storage_handlers/s3_handler.py +10 -8
  121. wandb/sdk/artifacts/storage_handlers/tracking_handler.py +6 -4
  122. wandb/sdk/artifacts/storage_handlers/wb_artifact_handler.py +24 -21
  123. wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +4 -2
  124. wandb/sdk/artifacts/storage_policies/_multipart.py +187 -0
  125. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +61 -242
  126. wandb/sdk/artifacts/storage_policy.py +25 -12
  127. wandb/sdk/data_types/image.py +2 -2
  128. wandb/sdk/data_types/object_3d.py +67 -2
  129. wandb/sdk/interface/interface.py +72 -64
  130. wandb/sdk/interface/interface_queue.py +27 -18
  131. wandb/sdk/interface/interface_shared.py +61 -23
  132. wandb/sdk/interface/interface_sock.py +9 -5
  133. wandb/sdk/internal/_generated/server_features_query.py +4 -4
  134. wandb/sdk/internal/job_builder.py +27 -10
  135. wandb/sdk/internal/sender.py +4 -1
  136. wandb/sdk/launch/create_job.py +2 -1
  137. wandb/sdk/launch/inputs/schema.py +13 -10
  138. wandb/sdk/lib/apikey.py +8 -12
  139. wandb/sdk/lib/asyncio_compat.py +1 -1
  140. wandb/sdk/lib/asyncio_manager.py +5 -5
  141. wandb/sdk/lib/console_capture.py +38 -30
  142. wandb/sdk/lib/progress.py +151 -125
  143. wandb/sdk/lib/retry.py +3 -2
  144. wandb/sdk/lib/service/service_connection.py +2 -2
  145. wandb/sdk/lib/wb_logging.py +2 -1
  146. wandb/sdk/mailbox/mailbox.py +1 -1
  147. wandb/sdk/wandb_init.py +11 -14
  148. wandb/sdk/wandb_run.py +14 -48
  149. wandb/sdk/wandb_settings.py +114 -30
  150. {wandb-0.22.1.dist-info → wandb-0.22.3.dist-info}/METADATA +2 -1
  151. {wandb-0.22.1.dist-info → wandb-0.22.3.dist-info}/RECORD +154 -146
  152. wandb/sdk/artifacts/_generated/artifact_via_membership_by_name.py +0 -26
  153. wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +0 -36
  154. wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +0 -25
  155. wandb/sdk/artifacts/_generated/move_artifact_collection.py +0 -35
  156. wandb/sdk/projects/_generated/__init__.py +0 -26
  157. wandb/sdk/projects/_generated/delete_project.py +0 -22
  158. wandb/sdk/projects/_generated/enums.py +0 -4
  159. wandb/sdk/projects/_generated/fragments.py +0 -41
  160. wandb/sdk/projects/_generated/input_types.py +0 -13
  161. wandb/sdk/projects/_generated/operations.py +0 -88
  162. wandb/sdk/projects/_generated/rename_project.py +0 -27
  163. wandb/sdk/projects/_generated/upsert_registry_project.py +0 -27
  164. {wandb-0.22.1.dist-info → wandb-0.22.3.dist-info}/WHEEL +0 -0
  165. {wandb-0.22.1.dist-info → wandb-0.22.3.dist-info}/entry_points.txt +0 -0
  166. {wandb-0.22.1.dist-info → wandb-0.22.3.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/lib/progress.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import contextlib
7
7
  import time
8
- from typing import Iterable, Iterator, NoReturn
8
+ from typing import Iterator, NoReturn
9
9
 
10
10
  from wandb.proto import wandb_internal_pb2 as pb
11
11
  from wandb.sdk.interface import interface
@@ -13,6 +13,10 @@ from wandb.sdk.lib import asyncio_compat
13
13
 
14
14
  from . import printer as p
15
15
 
16
+ _INDENT = " "
17
+ _MAX_LINES_TO_PRINT = 6
18
+ _MAX_OPS_TO_PRINT = 5
19
+
16
20
 
17
21
  async def loop_printing_operation_stats(
18
22
  progress: ProgressPrinter,
@@ -93,168 +97,192 @@ class ProgressPrinter:
93
97
  progress_text_area: p.DynamicText | None,
94
98
  default_text: str,
95
99
  ) -> None:
96
- self._show_operation_stats = True
97
100
  self._printer = printer
98
101
  self._progress_text_area = progress_text_area
99
102
  self._default_text = default_text
100
- self._tick = 0
103
+ self._tick = -1
101
104
  self._last_printed_line = ""
102
105
 
103
106
  def update(
104
107
  self,
105
- progress: list[pb.PollExitResponse] | pb.OperationStats,
108
+ stats_or_groups: pb.OperationStats | dict[str, pb.OperationStats],
106
109
  ) -> None:
107
- """Update the displayed information."""
108
- if not progress:
110
+ """Update the displayed information.
111
+
112
+ Args:
113
+ stats_or_groups: A single group of operations, or zero or more
114
+ labeled operation groups.
115
+ """
116
+ self._tick += 1
117
+
118
+ if not self._progress_text_area:
119
+ line = self._to_static_text(stats_or_groups)
120
+ if line and line != self._last_printed_line:
121
+ self._printer.display(line)
122
+ self._last_printed_line = line
109
123
  return
110
124
 
111
- if isinstance(progress, pb.OperationStats):
112
- self._update_operation_stats([progress])
113
- elif self._show_operation_stats:
114
- self._update_operation_stats(
115
- list(response.operation_stats for response in progress)
116
- )
117
- elif len(progress) == 1:
118
- self._update_single_run(progress[0])
119
- else:
120
- self._update_multiple_runs(progress)
125
+ lines = self._to_dynamic_text(stats_or_groups)
126
+ if not lines:
127
+ loading_symbol = self._printer.loading_symbol(self._tick)
128
+ if loading_symbol:
129
+ lines = [f"{loading_symbol} {self._default_text}"]
130
+ else:
131
+ lines = [self._default_text]
121
132
 
122
- self._tick += 1
133
+ self._progress_text_area.set_text("\n".join(lines))
123
134
 
124
- def _update_operation_stats(self, stats_list: list[pb.OperationStats]) -> None:
125
- if self._progress_text_area:
126
- _DynamicOperationStatsPrinter(
135
+ def _to_dynamic_text(
136
+ self,
137
+ stats_or_groups: pb.OperationStats | dict[str, pb.OperationStats],
138
+ ) -> list[str]:
139
+ """Returns text to show in a dynamic text area."""
140
+ loading_symbol = self._printer.loading_symbol(self._tick)
141
+
142
+ if isinstance(stats_or_groups, dict):
143
+ return _GroupedOperationStatsPrinter(
127
144
  self._printer,
128
- self._progress_text_area,
129
- max_lines=6,
130
- loading_symbol=self._printer.loading_symbol(self._tick),
131
- default_text=self._default_text,
132
- ).display(stats_list)
145
+ _MAX_LINES_TO_PRINT,
146
+ loading_symbol,
147
+ ).render(stats_or_groups)
133
148
 
134
149
  else:
135
- top_level_operations: list[str] = []
136
- extra_operations = 0
137
- for stats in stats_list:
138
- for op in stats.operations:
139
- if len(top_level_operations) < 5:
140
- top_level_operations.append(op.desc)
141
- else:
142
- extra_operations += 1
143
-
144
- line = "; ".join(top_level_operations)
145
- if extra_operations > 0:
146
- line += f" (+ {extra_operations} more)"
147
-
148
- if line and line != self._last_printed_line:
149
- self._printer.display(line)
150
- self._last_printed_line = line
150
+ return _OperationStatsPrinter(
151
+ self._printer,
152
+ _MAX_LINES_TO_PRINT,
153
+ loading_symbol,
154
+ ).render(stats_or_groups)
151
155
 
152
- def _update_single_run(
156
+ def _to_static_text(
153
157
  self,
154
- progress: pb.PollExitResponse,
155
- ) -> None:
156
- stats = progress.pusher_stats
157
- line = (
158
- f"{_megabytes(stats.uploaded_bytes):.3f} MB"
159
- f" of {_megabytes(stats.total_bytes):.3f} MB uploaded"
160
- )
161
-
162
- if stats.deduped_bytes > 0:
163
- line += f" ({_megabytes(stats.deduped_bytes):.3f} MB deduped)"
164
-
165
- if stats.total_bytes > 0:
166
- self._update_progress_text(
167
- line,
168
- stats.uploaded_bytes / stats.total_bytes,
158
+ stats_or_groups: pb.OperationStats | dict[str, pb.OperationStats],
159
+ ) -> str:
160
+ """Returns a single line of text to print out."""
161
+ if isinstance(stats_or_groups, dict):
162
+ sorted_prefixed_stats = list(
163
+ (f"[{group}] ", stats) #
164
+ for group, stats in sorted(stats_or_groups.items())
169
165
  )
170
166
  else:
171
- self._update_progress_text(line, 1.0)
167
+ sorted_prefixed_stats = [("", stats_or_groups)]
168
+
169
+ group_strs: list[str] = []
170
+ total_operations = 0
171
+ total_printed = 0
172
+
173
+ for prefix, stats in sorted_prefixed_stats:
174
+ total_operations += stats.total_operations
175
+ if not stats.operations:
176
+ continue
177
+
178
+ group_ops: list[str] = []
179
+ i = 0
180
+ while total_printed < _MAX_OPS_TO_PRINT and i < len(stats.operations):
181
+ group_ops.append(stats.operations[i].desc)
182
+ total_printed += 1
183
+ i += 1
184
+
185
+ if group_ops:
186
+ group_strs.append(prefix + "; ".join(group_ops))
187
+
188
+ line = "; ".join(group_strs)
189
+ remaining = total_operations - total_printed
190
+ if total_printed > 0 and remaining > 0:
191
+ line += f" (+ {remaining} more)"
192
+
193
+ return line
194
+
195
+
196
+ class _GroupedOperationStatsPrinter:
197
+ """Renders a list of labeled operation stats groups into lines of text."""
172
198
 
173
- def _update_multiple_runs(
199
+ def __init__(
174
200
  self,
175
- progress_list: list[pb.PollExitResponse],
201
+ printer: p.Printer,
202
+ max_lines: int,
203
+ loading_symbol: str,
176
204
  ) -> None:
177
- total_files = 0
178
- uploaded_bytes = 0
179
- total_bytes = 0
180
-
181
- for progress in progress_list:
182
- total_files += progress.file_counts.wandb_count
183
- total_files += progress.file_counts.media_count
184
- total_files += progress.file_counts.artifact_count
185
- total_files += progress.file_counts.other_count
186
-
187
- uploaded_bytes += progress.pusher_stats.uploaded_bytes
188
- total_bytes += progress.pusher_stats.total_bytes
189
-
190
- line = (
191
- f"Processing {len(progress_list)} runs with {total_files} files"
192
- f" ({_megabytes(uploaded_bytes):.2f} MB"
193
- f" / {_megabytes(total_bytes):.2f} MB)"
194
- )
195
-
196
- if total_bytes > 0:
197
- self._update_progress_text(line, uploaded_bytes / total_bytes)
198
- else:
199
- self._update_progress_text(line, 1.0)
205
+ self._printer = printer
206
+ self._max_lines = max_lines
207
+ self._loading_symbol = loading_symbol
200
208
 
201
- def _update_progress_text(self, text: str, progress: float) -> None:
202
- if text == self._last_printed_line:
203
- return
204
- self._last_printed_line = text
209
+ def render(self, groups: dict[str, pb.OperationStats]) -> list[str]:
210
+ """Convert labeled operation stats groups into text to display.
205
211
 
206
- if self._progress_text_area:
207
- self._progress_text_area.set_text(text)
208
- else:
209
- self._printer.progress_update(text + "\r", progress)
212
+ Args:
213
+ groups: A mapping from group labels to stats for that group.
214
+
215
+ Returns:
216
+ The lines of text to print. The lines do not end with the newline
217
+ character. Returns an empty list if there are no operations.
218
+ """
219
+ lines: list[str] = []
220
+
221
+ for key, stats in sorted(groups.items()):
222
+ # Don't display empty groups.
223
+ if not stats.operations:
224
+ continue
225
+
226
+ # Ensure enough space left for the group header and at least
227
+ # one line of content.
228
+ remaining_lines = self._max_lines - len(lines)
229
+ if remaining_lines < 2:
230
+ break
231
+
232
+ # Group header.
233
+ lines.append(key)
210
234
 
235
+ # Group content.
236
+ stats_lines = _OperationStatsPrinter(
237
+ printer=self._printer,
238
+ max_lines=remaining_lines - 1, # minus one for the header
239
+ loading_symbol=self._loading_symbol,
240
+ ).render(stats)
241
+ for line in stats_lines:
242
+ lines.append(f"{_INDENT}{line}")
211
243
 
212
- class _DynamicOperationStatsPrinter:
213
- """Single-use object that writes operation stats into a text area."""
244
+ return lines
245
+
246
+
247
+ class _OperationStatsPrinter:
248
+ """Renders operation stats into lines of text."""
214
249
 
215
250
  def __init__(
216
251
  self,
217
252
  printer: p.Printer,
218
- text_area: p.DynamicText,
219
253
  max_lines: int,
220
254
  loading_symbol: str,
221
- default_text: str,
222
255
  ) -> None:
223
256
  self._printer = printer
224
- self._text_area = text_area
225
257
  self._max_lines = max_lines
226
258
  self._loading_symbol = loading_symbol
227
- self._default_text = default_text
228
259
 
229
260
  self._lines: list[str] = []
230
261
  self._ops_shown = 0
231
262
 
232
- def display(
233
- self,
234
- stats_list: Iterable[pb.OperationStats],
235
- ) -> None:
236
- """Show the given stats in the text area."""
237
- total_operations = 0
238
- for stats in stats_list:
239
- for op in stats.operations:
240
- self._add_operation(op, is_subtask=False, indent="")
241
- total_operations += stats.total_operations
263
+ def render(self, stats: pb.OperationStats) -> list[str]:
264
+ """Convert the stats into a list of lines to display.
265
+
266
+ Args:
267
+ stats: Collection of operations to display.
242
268
 
243
- if self._ops_shown < total_operations:
269
+ Returns:
270
+ The lines of text to print. The lines do not end with the newline
271
+ character. Returns an empty list if there are no operations.
272
+ """
273
+ for op in stats.operations:
274
+ self._add_operation(op, is_subtask=False, indent="")
275
+
276
+ if self._ops_shown < stats.total_operations:
244
277
  if 1 <= self._max_lines <= len(self._lines):
278
+ self._ops_shown -= 1
245
279
  self._lines.pop()
246
280
 
247
- remaining = total_operations - self._ops_shown
281
+ remaining = stats.total_operations - self._ops_shown
248
282
 
249
283
  self._lines.append(f"+ {remaining} more task(s)")
250
284
 
251
- if len(self._lines) == 0:
252
- if self._loading_symbol:
253
- self._text_area.set_text(f"{self._loading_symbol} {self._default_text}")
254
- else:
255
- self._text_area.set_text(self._default_text)
256
- else:
257
- self._text_area.set_text("\n".join(self._lines))
285
+ return self._lines
258
286
 
259
287
  def _add_operation(self, op: pb.Operation, is_subtask: bool, indent: str) -> None:
260
288
  """Add the operation to `self._lines`."""
@@ -264,14 +292,17 @@ class _DynamicOperationStatsPrinter:
264
292
  if not is_subtask:
265
293
  self._ops_shown += 1
266
294
 
267
- parts = []
295
+ status_indent_level = 0 # alignment for the status message, if any
296
+ parts: list[str] = []
268
297
 
269
298
  # Subtask indicator.
270
299
  if is_subtask and self._printer.supports_unicode:
300
+ status_indent_level += 2 # +1 for space
271
301
  parts.append("↳")
272
302
 
273
303
  # Loading symbol.
274
304
  if self._loading_symbol:
305
+ status_indent_level += 2 # +1 for space
275
306
  parts.append(self._loading_symbol)
276
307
 
277
308
  # Task name.
@@ -289,14 +320,14 @@ class _DynamicOperationStatsPrinter:
289
320
  if op.error_status:
290
321
  error_word = self._printer.error("ERROR")
291
322
  error_desc = self._printer.secondary_text(op.error_status)
292
- subtask_indent = " " if is_subtask else ""
323
+ status_indent = " " * status_indent_level
293
324
  self._lines.append(
294
- f"{indent}{subtask_indent} {error_word} {error_desc}",
325
+ f"{indent}{status_indent}{error_word} {error_desc}",
295
326
  )
296
327
 
297
328
  # Subtasks.
298
329
  if op.subtasks:
299
- subtask_indent = indent + " "
330
+ subtask_indent = indent + _INDENT
300
331
  for task in op.subtasks:
301
332
  self._add_operation(
302
333
  task,
@@ -318,8 +349,3 @@ def _time_to_string(seconds: float) -> str:
318
349
  hours = int(seconds / (60 * 60))
319
350
  minutes = int((seconds / 60) % 60)
320
351
  return f"{hours}h{minutes}m"
321
-
322
-
323
- def _megabytes(bytes: int) -> float:
324
- """Returns the number of megabytes in `bytes`."""
325
- return bytes / (1 << 20)
wandb/sdk/lib/retry.py CHANGED
@@ -77,9 +77,10 @@ class Retry(Generic[_R]):
77
77
  self._retryable_exceptions = retryable_exceptions
78
78
  else:
79
79
  self._retryable_exceptions = (TransientError,)
80
- self._index = 0
81
80
  self.retry_callback = retry_callback
82
81
 
82
+ self._num_iter = 0
83
+
83
84
  def _sleep_check_cancelled(
84
85
  self, wait_seconds: float, cancel_event: Optional[threading.Event]
85
86
  ) -> bool:
@@ -194,7 +195,7 @@ class Retry(Generic[_R]):
194
195
  else:
195
196
  wandb.termlog(
196
197
  f"{self._error_prefix}"
197
- f" ({exception.__class__.__name__}), entering retry loop."
198
+ + f" ({exception.__class__.__name__}), entering retry loop."
198
199
  )
199
200
 
200
201
  def _print_recovered(self, start_time: datetime.datetime) -> None:
@@ -190,8 +190,8 @@ class ServiceConnection:
190
190
  except TimeoutError:
191
191
  raise WandbAttachFailedError(
192
192
  "Failed to attach because the run does not belong to"
193
- " the current service process, or because the service"
194
- " process is busy (unlikely)."
193
+ + " the current service process, or because the service"
194
+ + " process is busy (unlikely)."
195
195
  ) from None
196
196
 
197
197
  else:
@@ -136,7 +136,7 @@ def add_file_handler(run_id: str, filepath: pathlib.Path) -> logging.Handler:
136
136
  return handler
137
137
 
138
138
 
139
- class _RunIDFilter(logging.Filter):
139
+ class _RunIDFilter:
140
140
  """Filters out messages logged for a different run."""
141
141
 
142
142
  def __init__(self, run_id: str) -> None:
@@ -148,6 +148,7 @@ class _RunIDFilter(logging.Filter):
148
148
  self._run_id = run_id
149
149
 
150
150
  def filter(self, record: logging.LogRecord) -> bool:
151
+ """Modify a log record and return whether it matches the run."""
151
152
  run_id = _run_id.get()
152
153
 
153
154
  if run_id is None:
@@ -89,7 +89,7 @@ class Mailbox:
89
89
  def generate():
90
90
  return "".join(
91
91
  secrets.choice(string.ascii_lowercase + string.digits)
92
- for i in range(12)
92
+ for _ in range(12)
93
93
  )
94
94
 
95
95
  address = generate()
wandb/sdk/wandb_init.py CHANGED
@@ -12,6 +12,7 @@ from __future__ import annotations
12
12
 
13
13
  import contextlib
14
14
  import dataclasses
15
+ import functools
15
16
  import json
16
17
  import logging
17
18
  import os
@@ -727,7 +728,7 @@ class _WandbInit:
727
728
  drun = Run(
728
729
  settings=Settings(
729
730
  mode="disabled",
730
- x_files_dir=tempfile.gettempdir(),
731
+ root_dir=tempfile.gettempdir(),
731
732
  run_id=run_id,
732
733
  run_tags=tuple(),
733
734
  run_notes=None,
@@ -988,25 +989,21 @@ class _WandbInit:
988
989
 
989
990
  run_init_handle = backend.interface.deliver_run(run)
990
991
 
991
- async def display_init_message() -> None:
992
- assert backend.interface
993
-
992
+ try:
994
993
  with progress.progress_printer(
995
994
  run_printer,
996
995
  default_text="Waiting for wandb.init()...",
997
996
  ) as progress_printer:
998
- await progress.loop_printing_operation_stats(
999
- progress_printer,
1000
- backend.interface,
997
+ result = wait_with_progress(
998
+ run_init_handle,
999
+ timeout=timeout,
1000
+ display_progress=functools.partial(
1001
+ progress.loop_printing_operation_stats,
1002
+ progress_printer,
1003
+ backend.interface,
1004
+ ),
1001
1005
  )
1002
1006
 
1003
- try:
1004
- result = wait_with_progress(
1005
- run_init_handle,
1006
- timeout=timeout,
1007
- display_progress=display_init_message,
1008
- )
1009
-
1010
1007
  except TimeoutError:
1011
1008
  run_init_handle.cancel(backend.interface)
1012
1009
 
wandb/sdk/wandb_run.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
4
3
  import functools
5
4
  import glob
6
5
  import json
@@ -33,7 +32,6 @@ from wandb.errors import CommError, UsageError
33
32
  from wandb.errors.links import url_registry
34
33
  from wandb.integration.torch import wandb_torch
35
34
  from wandb.plot import CustomChart, Visualize
36
- from wandb.proto import wandb_internal_pb2 as pb
37
35
  from wandb.proto.wandb_deprecated import Deprecated
38
36
  from wandb.proto.wandb_internal_pb2 import (
39
37
  MetricRecord,
@@ -44,7 +42,7 @@ from wandb.proto.wandb_internal_pb2 import (
44
42
  from wandb.sdk.artifacts._internal_artifact import InternalArtifact
45
43
  from wandb.sdk.artifacts.artifact import Artifact
46
44
  from wandb.sdk.internal import job_builder
47
- from wandb.sdk.lib import asyncio_compat, wb_logging
45
+ from wandb.sdk.lib import wb_logging
48
46
  from wandb.sdk.lib.import_hooks import (
49
47
  register_post_import_hook,
50
48
  unregister_post_import_hook,
@@ -489,7 +487,7 @@ class Run:
489
487
 
490
488
  You can log data to a run with `wandb.Run.log()`. Anything you log using
491
489
  `wandb.Run.log()` is sent to that run. See
492
- [Create an experiment](https://docs.wandb.ai/guides/track/launch) or
490
+ [Create an experiment](https://docs.wandb.ai/guides/track/create-an-experiment/) or
493
491
  [`wandb.init`](https://docs.wandb.ai/ref/python/init/) API reference page
494
492
  or more information.
495
493
 
@@ -1574,15 +1572,13 @@ class Run:
1574
1572
 
1575
1573
  @_log_to_run
1576
1574
  def _console_callback(self, name: str, data: str) -> None:
1577
- # logger.info("console callback: %s, %s", name, data)
1578
1575
  if self._backend and self._backend.interface:
1579
- self._backend.interface.publish_output(name, data)
1576
+ # nowait=True so that this can be called from an asyncio context.
1577
+ self._backend.interface.publish_output(name, data, nowait=True)
1580
1578
 
1581
1579
  @_log_to_run
1582
1580
  @_raise_if_finished
1583
1581
  def _console_raw_callback(self, name: str, data: str) -> None:
1584
- # logger.info("console callback: %s, %s", name, data)
1585
-
1586
1582
  # NOTE: console output is only allowed on the process which installed the callback
1587
1583
  # this will prevent potential corruption in the socket to the service. Other methods
1588
1584
  # are protected by the _attach run decorator, but this callback was installed on the
@@ -1592,7 +1588,8 @@ class Run:
1592
1588
  return
1593
1589
 
1594
1590
  if self._backend and self._backend.interface:
1595
- self._backend.interface.publish_output_raw(name, data)
1591
+ # nowait=True so that this can be called from an asyncio context.
1592
+ self._backend.interface.publish_output_raw(name, data, nowait=True)
1596
1593
 
1597
1594
  @_log_to_run
1598
1595
  def _tensorboard_callback(
@@ -1861,7 +1858,7 @@ class Run:
1861
1858
  run.log({"accuracy": 0.8}, step=current_step)
1862
1859
  current_step += 1
1863
1860
  run.log({"train-loss": 0.4}, step=current_step)
1864
- run.log({"accuracy": 0.9}, step=current_step)
1861
+ run.log({"accuracy": 0.9}, step=current_step, commit=True)
1865
1862
  ```
1866
1863
 
1867
1864
  Args:
@@ -2051,6 +2048,9 @@ class Run:
2051
2048
  When given an absolute path or glob and no `base_path`, one
2052
2049
  directory level is preserved as in the example above.
2053
2050
 
2051
+ Files are automatically deduplicated: calling `save()` multiple times
2052
+ on the same file without modifications will not re-upload it.
2053
+
2054
2054
  Args:
2055
2055
  glob_str: A relative or absolute path or Unix glob.
2056
2056
  base_path: A path to use to infer a directory structure; see examples.
@@ -2075,10 +2075,10 @@ class Run:
2075
2075
  run.save("these/are/myfiles/*", base_path="these")
2076
2076
  # => Saves files in an "are/myfiles/" folder in the run.
2077
2077
 
2078
- run.save("/User/username/Documents/run123/*.txt")
2078
+ run.save("/Users/username/Documents/run123/*.txt")
2079
2079
  # => Saves files in a "run123/" folder in the run. See note below.
2080
2080
 
2081
- run.save("/User/username/Documents/run123/*.txt", base_path="/User")
2081
+ run.save("/Users/username/Documents/run123/*.txt", base_path="/Users")
2082
2082
  # => Saves files in a "username/Documents/run123/" folder in the run.
2083
2083
 
2084
2084
  run.save("files/*/saveme.txt")
@@ -2679,41 +2679,6 @@ class Run:
2679
2679
  else:
2680
2680
  return artifact
2681
2681
 
2682
- async def _display_finish_stats(
2683
- self,
2684
- progress_printer: progress.ProgressPrinter,
2685
- ) -> None:
2686
- last_result: Result | None = None
2687
-
2688
- async def loop_update_printer() -> None:
2689
- while True:
2690
- if last_result:
2691
- progress_printer.update(
2692
- [last_result.response.poll_exit_response],
2693
- )
2694
- await asyncio.sleep(0.1)
2695
-
2696
- async def loop_poll_exit() -> None:
2697
- nonlocal last_result
2698
- assert self._backend and self._backend.interface
2699
-
2700
- while True:
2701
- handle = await self._backend.interface.deliver_async(
2702
- pb.Record(request=pb.Request(poll_exit=pb.PollExitRequest()))
2703
- )
2704
-
2705
- time_start = time.monotonic()
2706
- last_result = await handle.wait_async(timeout=None)
2707
-
2708
- # Update at most once a second.
2709
- time_elapsed = time.monotonic() - time_start
2710
- if time_elapsed < 1:
2711
- await asyncio.sleep(1 - time_elapsed)
2712
-
2713
- async with asyncio_compat.open_task_group() as task_group:
2714
- task_group.start_soon(loop_update_printer())
2715
- task_group.start_soon(loop_poll_exit())
2716
-
2717
2682
  def _on_finish(self) -> None:
2718
2683
  trigger.call("on_finished")
2719
2684
 
@@ -2738,8 +2703,9 @@ class Run:
2738
2703
  exit_handle,
2739
2704
  timeout=None,
2740
2705
  display_progress=functools.partial(
2741
- self._display_finish_stats,
2706
+ progress.loop_printing_operation_stats,
2742
2707
  progress_printer,
2708
+ self._backend.interface,
2743
2709
  ),
2744
2710
  )
2745
2711