wandb 0.22.0__py3-none-macosx_12_0_arm64.whl → 0.22.2__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 (114) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +8 -5
  3. wandb/_pydantic/__init__.py +12 -11
  4. wandb/_pydantic/base.py +49 -19
  5. wandb/apis/__init__.py +2 -0
  6. wandb/apis/attrs.py +2 -0
  7. wandb/apis/importers/internals/internal.py +16 -23
  8. wandb/apis/internal.py +2 -0
  9. wandb/apis/normalize.py +2 -0
  10. wandb/apis/public/__init__.py +3 -2
  11. wandb/apis/public/api.py +215 -164
  12. wandb/apis/public/artifacts.py +23 -20
  13. wandb/apis/public/const.py +2 -0
  14. wandb/apis/public/files.py +33 -24
  15. wandb/apis/public/history.py +2 -0
  16. wandb/apis/public/jobs.py +20 -18
  17. wandb/apis/public/projects.py +4 -2
  18. wandb/apis/public/query_generator.py +3 -0
  19. wandb/apis/public/registries/__init__.py +7 -0
  20. wandb/apis/public/registries/_freezable_list.py +9 -12
  21. wandb/apis/public/registries/registries_search.py +8 -6
  22. wandb/apis/public/registries/registry.py +22 -17
  23. wandb/apis/public/reports.py +2 -0
  24. wandb/apis/public/runs.py +261 -57
  25. wandb/apis/public/sweeps.py +10 -9
  26. wandb/apis/public/teams.py +2 -0
  27. wandb/apis/public/users.py +2 -0
  28. wandb/apis/public/utils.py +16 -15
  29. wandb/automations/_generated/__init__.py +54 -127
  30. wandb/automations/_generated/create_generic_webhook_integration.py +1 -7
  31. wandb/automations/_generated/fragments.py +26 -91
  32. wandb/bin/gpu_stats +0 -0
  33. wandb/bin/wandb-core +0 -0
  34. wandb/cli/beta.py +16 -2
  35. wandb/cli/beta_leet.py +74 -0
  36. wandb/cli/beta_sync.py +9 -11
  37. wandb/cli/cli.py +34 -7
  38. wandb/errors/errors.py +3 -3
  39. wandb/proto/v3/wandb_api_pb2.py +86 -0
  40. wandb/proto/v3/wandb_internal_pb2.py +352 -351
  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_internal_pb2.py +352 -351
  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_internal_pb2.py +352 -351
  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_internal_pb2.py +352 -351
  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/_factories.py +7 -2
  58. wandb/sdk/artifacts/_generated/__init__.py +112 -412
  59. wandb/sdk/artifacts/_generated/fragments.py +65 -0
  60. wandb/sdk/artifacts/_generated/operations.py +52 -22
  61. wandb/sdk/artifacts/_generated/run_input_artifacts.py +3 -23
  62. wandb/sdk/artifacts/_generated/run_output_artifacts.py +3 -23
  63. wandb/sdk/artifacts/_generated/type_info.py +19 -0
  64. wandb/sdk/artifacts/_gqlutils.py +47 -0
  65. wandb/sdk/artifacts/_models/__init__.py +4 -0
  66. wandb/sdk/artifacts/_models/base_model.py +20 -0
  67. wandb/sdk/artifacts/_validators.py +40 -12
  68. wandb/sdk/artifacts/artifact.py +99 -118
  69. wandb/sdk/artifacts/artifact_file_cache.py +6 -1
  70. wandb/sdk/artifacts/artifact_manifest_entry.py +67 -14
  71. wandb/sdk/artifacts/storage_handler.py +18 -12
  72. wandb/sdk/artifacts/storage_handlers/azure_handler.py +11 -6
  73. wandb/sdk/artifacts/storage_handlers/gcs_handler.py +9 -6
  74. wandb/sdk/artifacts/storage_handlers/http_handler.py +9 -4
  75. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +10 -6
  76. wandb/sdk/artifacts/storage_handlers/multi_handler.py +5 -4
  77. wandb/sdk/artifacts/storage_handlers/s3_handler.py +10 -8
  78. wandb/sdk/artifacts/storage_handlers/tracking_handler.py +6 -4
  79. wandb/sdk/artifacts/storage_handlers/wb_artifact_handler.py +24 -21
  80. wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +4 -2
  81. wandb/sdk/artifacts/storage_policies/_multipart.py +187 -0
  82. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +71 -242
  83. wandb/sdk/artifacts/storage_policy.py +25 -12
  84. wandb/sdk/data_types/bokeh.py +5 -1
  85. wandb/sdk/data_types/image.py +17 -6
  86. wandb/sdk/data_types/object_3d.py +67 -2
  87. wandb/sdk/interface/interface.py +31 -4
  88. wandb/sdk/interface/interface_queue.py +10 -0
  89. wandb/sdk/interface/interface_shared.py +0 -7
  90. wandb/sdk/interface/interface_sock.py +9 -3
  91. wandb/sdk/internal/_generated/__init__.py +2 -12
  92. wandb/sdk/internal/job_builder.py +27 -10
  93. wandb/sdk/internal/sender.py +5 -2
  94. wandb/sdk/internal/settings_static.py +2 -82
  95. wandb/sdk/launch/create_job.py +2 -1
  96. wandb/sdk/launch/runner/kubernetes_runner.py +25 -20
  97. wandb/sdk/launch/utils.py +82 -1
  98. wandb/sdk/lib/progress.py +8 -74
  99. wandb/sdk/lib/service/service_client.py +5 -9
  100. wandb/sdk/lib/service/service_connection.py +39 -23
  101. wandb/sdk/mailbox/mailbox_handle.py +2 -0
  102. wandb/sdk/projects/_generated/__init__.py +12 -33
  103. wandb/sdk/wandb_init.py +23 -3
  104. wandb/sdk/wandb_login.py +53 -27
  105. wandb/sdk/wandb_run.py +10 -5
  106. wandb/sdk/wandb_settings.py +63 -25
  107. wandb/sync/sync.py +7 -2
  108. wandb/util.py +1 -1
  109. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/METADATA +1 -1
  110. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/RECORD +113 -103
  111. wandb/sdk/artifacts/_graphql_fragments.py +0 -19
  112. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/WHEEL +0 -0
  113. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/entry_points.txt +0 -0
  114. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/launch/utils.py CHANGED
@@ -7,7 +7,17 @@ import re
7
7
  import subprocess
8
8
  import sys
9
9
  from collections import defaultdict
10
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast
10
+ from typing import (
11
+ TYPE_CHECKING,
12
+ Any,
13
+ Dict,
14
+ Iterator,
15
+ List,
16
+ Optional,
17
+ Tuple,
18
+ Union,
19
+ cast,
20
+ )
11
21
 
12
22
  import click
13
23
 
@@ -599,6 +609,32 @@ def make_name_dns_safe(name: str) -> str:
599
609
  return resp
600
610
 
601
611
 
612
+ def make_k8s_label_safe(value: str) -> str:
613
+ """Return a Kubernetes label/identifier safe string (DNS-1123 label).
614
+
615
+ See:
616
+ https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
617
+
618
+ Rules:
619
+ - lowercase alphanumeric and '-'
620
+ - must start and end with an alphanumeric
621
+ - max length 63
622
+ """
623
+ # Normalize common separators first
624
+ safe = value.replace("_", "-").lower()
625
+ # Remove any invalid characters
626
+ safe = re.sub(r"[^a-z0-9\-]", "", safe)
627
+ # Collapse consecutive '-'
628
+ safe = re.sub(r"-+", "-", safe)
629
+ # Trim to 63 and strip leading/trailing '-'
630
+ safe = safe[:63].strip("-")
631
+
632
+ if not safe:
633
+ raise LaunchError(f"Invalid value for Kubernetes label: {value}")
634
+
635
+ return safe
636
+
637
+
602
638
  def warn_failed_packages_from_build_logs(
603
639
  log: str, image_uri: str, api: Api, job_tracker: Optional["JobAndRunStatusTracker"]
604
640
  ) -> None:
@@ -744,3 +780,48 @@ def get_current_python_version() -> Tuple[str, str]:
744
780
  major = full_version[0]
745
781
  version = ".".join(full_version[:2]) if len(full_version) >= 2 else major + ".0"
746
782
  return version, major
783
+
784
+
785
+ def yield_containers(root: Union[dict, list]) -> Iterator[dict]:
786
+ """Yield all container specs in a manifest.
787
+
788
+ Recursively traverses the manifest and yields all container specs. Container
789
+ specs are identified by the presence of a "containers" key in the value.
790
+ """
791
+ if isinstance(root, dict):
792
+ for k, v in root.items():
793
+ if k == "containers":
794
+ if isinstance(v, list):
795
+ yield from v
796
+ elif isinstance(v, (dict, list)):
797
+ yield from yield_containers(v)
798
+ elif isinstance(root, list):
799
+ for item in root:
800
+ yield from yield_containers(item)
801
+
802
+
803
+ def sanitize_identifiers_for_k8s(root: Any) -> None:
804
+ if isinstance(root, list):
805
+ for item in root:
806
+ sanitize_identifiers_for_k8s(item)
807
+ return
808
+
809
+ # Only dicts have metadata and nested structures we need to sanitize.
810
+ if not isinstance(root, dict):
811
+ return
812
+
813
+ metadata = root.get("metadata")
814
+ if isinstance(metadata, dict):
815
+ if name := metadata.get("name"):
816
+ metadata["name"] = make_k8s_label_safe(str(name))
817
+
818
+ for container in yield_containers(root):
819
+ if name := container.get("name"):
820
+ container["name"] = make_k8s_label_safe(str(name))
821
+
822
+ # nested names
823
+ for key, value in root.items():
824
+ if isinstance(value, (dict, list)):
825
+ sanitize_identifiers_for_k8s(value)
826
+ elif key == "name" and isinstance(value, str):
827
+ root[key] = make_k8s_label_safe(value)
wandb/sdk/lib/progress.py CHANGED
@@ -45,7 +45,11 @@ async def loop_printing_operation_stats(
45
45
  while True:
46
46
  start_time = time.monotonic()
47
47
 
48
- handle = interface.deliver_operation_stats()
48
+ handle = await interface.deliver_async(
49
+ pb.Record(
50
+ request=pb.Request(operations=pb.OperationStatsRequest()),
51
+ )
52
+ )
49
53
  result = await handle.wait_async(timeout=None)
50
54
  stats = result.response.operations_response.operation_stats
51
55
 
@@ -89,7 +93,6 @@ class ProgressPrinter:
89
93
  progress_text_area: p.DynamicText | None,
90
94
  default_text: str,
91
95
  ) -> None:
92
- self._show_operation_stats = True
93
96
  self._printer = printer
94
97
  self._progress_text_area = progress_text_area
95
98
  self._default_text = default_text
@@ -106,14 +109,10 @@ class ProgressPrinter:
106
109
 
107
110
  if isinstance(progress, pb.OperationStats):
108
111
  self._update_operation_stats([progress])
109
- elif self._show_operation_stats:
112
+ else:
110
113
  self._update_operation_stats(
111
114
  list(response.operation_stats for response in progress)
112
115
  )
113
- elif len(progress) == 1:
114
- self._update_single_run(progress[0])
115
- else:
116
- self._update_multiple_runs(progress)
117
116
 
118
117
  self._tick += 1
119
118
 
@@ -141,69 +140,9 @@ class ProgressPrinter:
141
140
  if extra_operations > 0:
142
141
  line += f" (+ {extra_operations} more)"
143
142
 
144
- if line != self._last_printed_line:
143
+ if line and line != self._last_printed_line:
145
144
  self._printer.display(line)
146
-
147
- self._last_printed_line = line
148
-
149
- def _update_single_run(
150
- self,
151
- progress: pb.PollExitResponse,
152
- ) -> None:
153
- stats = progress.pusher_stats
154
- line = (
155
- f"{_megabytes(stats.uploaded_bytes):.3f} MB"
156
- f" of {_megabytes(stats.total_bytes):.3f} MB uploaded"
157
- )
158
-
159
- if stats.deduped_bytes > 0:
160
- line += f" ({_megabytes(stats.deduped_bytes):.3f} MB deduped)"
161
-
162
- if stats.total_bytes > 0:
163
- self._update_progress_text(
164
- line,
165
- stats.uploaded_bytes / stats.total_bytes,
166
- )
167
- else:
168
- self._update_progress_text(line, 1.0)
169
-
170
- def _update_multiple_runs(
171
- self,
172
- progress_list: list[pb.PollExitResponse],
173
- ) -> None:
174
- total_files = 0
175
- uploaded_bytes = 0
176
- total_bytes = 0
177
-
178
- for progress in progress_list:
179
- total_files += progress.file_counts.wandb_count
180
- total_files += progress.file_counts.media_count
181
- total_files += progress.file_counts.artifact_count
182
- total_files += progress.file_counts.other_count
183
-
184
- uploaded_bytes += progress.pusher_stats.uploaded_bytes
185
- total_bytes += progress.pusher_stats.total_bytes
186
-
187
- line = (
188
- f"Processing {len(progress_list)} runs with {total_files} files"
189
- f" ({_megabytes(uploaded_bytes):.2f} MB"
190
- f" / {_megabytes(total_bytes):.2f} MB)"
191
- )
192
-
193
- if total_bytes > 0:
194
- self._update_progress_text(line, uploaded_bytes / total_bytes)
195
- else:
196
- self._update_progress_text(line, 1.0)
197
-
198
- def _update_progress_text(self, text: str, progress: float) -> None:
199
- if text == self._last_printed_line:
200
- return
201
- self._last_printed_line = text
202
-
203
- if self._progress_text_area:
204
- self._progress_text_area.set_text(text)
205
- else:
206
- self._printer.progress_update(text + "\r", progress)
145
+ self._last_printed_line = line
207
146
 
208
147
 
209
148
  class _DynamicOperationStatsPrinter:
@@ -315,8 +254,3 @@ def _time_to_string(seconds: float) -> str:
315
254
  hours = int(seconds / (60 * 60))
316
255
  minutes = int((seconds / 60) % 60)
317
256
  return f"{hours}h{minutes}m"
318
-
319
-
320
- def _megabytes(bytes: int) -> float:
321
- """Returns the number of megabytes in `bytes`."""
322
- return bytes / (1 << 20)
@@ -24,7 +24,6 @@ class ServiceClient:
24
24
  reader: asyncio.StreamReader,
25
25
  writer: asyncio.StreamWriter,
26
26
  ) -> None:
27
- self._asyncer = asyncer
28
27
  self._reader = reader
29
28
  self._writer = writer
30
29
  self._mailbox = Mailbox(asyncer)
@@ -34,11 +33,11 @@ class ServiceClient:
34
33
  name="ServiceClient._forward_responses",
35
34
  )
36
35
 
37
- def publish(self, request: spb.ServerRequest) -> None:
36
+ async def publish(self, request: spb.ServerRequest) -> None:
38
37
  """Send a request without waiting for a response."""
39
- self._asyncer.run_soon(lambda: self._send_server_request(request))
38
+ await self._send_server_request(request)
40
39
 
41
- def deliver(
40
+ async def deliver(
42
41
  self,
43
42
  request: spb.ServerRequest,
44
43
  ) -> MailboxHandle[spb.ServerResponse]:
@@ -52,7 +51,7 @@ class ServiceClient:
52
51
  stopped due to an error.
53
52
  """
54
53
  handle = self._mailbox.require_response(request)
55
- self._asyncer.run_soon(lambda: self._send_server_request(request))
54
+ await self._send_server_request(request)
56
55
  return handle
57
56
 
58
57
  async def _send_server_request(self, request: spb.ServerRequest) -> None:
@@ -64,11 +63,8 @@ class ServiceClient:
64
63
 
65
64
  await self._writer.drain()
66
65
 
67
- def close(self) -> None:
66
+ async def close(self) -> None:
68
67
  """Flush and close the socket."""
69
- self._asyncer.run_soon(self._close)
70
-
71
- async def _close(self) -> None:
72
68
  self._writer.close()
73
69
  await self._writer.wait_closed()
74
70
 
@@ -31,6 +31,7 @@ def connect_to_service(
31
31
 
32
32
  if token:
33
33
  return ServiceConnection(
34
+ asyncer=asyncer,
34
35
  client=token.connect(asyncer=asyncer),
35
36
  proc=None,
36
37
  )
@@ -60,6 +61,7 @@ def _start_and_connect_service(
60
61
  conn.teardown(hooks.exit_code)
61
62
 
62
63
  conn = ServiceConnection(
64
+ asyncer=asyncer,
63
65
  client=client,
64
66
  proc=proc,
65
67
  cleanup=lambda: atexit.unregister(teardown_atexit),
@@ -71,10 +73,14 @@ def _start_and_connect_service(
71
73
 
72
74
 
73
75
  class ServiceConnection:
74
- """A connection to the W&B internal service process."""
76
+ """A connection to the W&B internal service process.
77
+
78
+ None of the synchronous methods may be called in an asyncio context.
79
+ """
75
80
 
76
81
  def __init__(
77
82
  self,
83
+ asyncer: asyncio_manager.AsyncioManager,
78
84
  client: ServiceClient,
79
85
  proc: service_process.ServiceProcess | None,
80
86
  cleanup: Callable[[], None] | None = None,
@@ -82,13 +88,12 @@ class ServiceConnection:
82
88
  """Returns a new ServiceConnection.
83
89
 
84
90
  Args:
85
- mailbox: The mailbox to use for all communication over the socket.
86
- router: A handle to the thread that reads from the socket and
87
- updates the mailbox.
88
- client: A socket that's connected to the service.
91
+ asyncer: An asyncio runner.
92
+ client: A client for communicating with the service over a socket.
89
93
  proc: The service process if we own it, or None otherwise.
90
94
  cleanup: A callback to run on teardown before doing anything.
91
95
  """
96
+ self._asyncer = asyncer
92
97
  self._client = client
93
98
  self._proc = proc
94
99
  self._torn_down = False
@@ -96,9 +101,13 @@ class ServiceConnection:
96
101
 
97
102
  def make_interface(self, stream_id: str) -> InterfaceBase:
98
103
  """Returns an interface for communicating with the service."""
99
- return InterfaceSock(self._client, stream_id=stream_id)
104
+ return InterfaceSock(
105
+ self._asyncer,
106
+ self._client,
107
+ stream_id=stream_id,
108
+ )
100
109
 
101
- def init_sync(
110
+ async def init_sync(
102
111
  self,
103
112
  paths: set[pathlib.Path],
104
113
  settings: wandb_settings.Settings,
@@ -110,10 +119,10 @@ class ServiceConnection:
110
119
  )
111
120
  request = spb.ServerRequest(init_sync=init_sync)
112
121
 
113
- handle = self._client.deliver(request)
122
+ handle = await self._client.deliver(request)
114
123
  return handle.map(lambda r: r.init_sync_response)
115
124
 
116
- def sync(
125
+ async def sync(
117
126
  self,
118
127
  id: str,
119
128
  *,
@@ -123,10 +132,10 @@ class ServiceConnection:
123
132
  sync = wandb_sync_pb2.ServerSyncRequest(id=id, parallelism=parallelism)
124
133
  request = spb.ServerRequest(sync=sync)
125
134
 
126
- handle = self._client.deliver(request)
135
+ handle = await self._client.deliver(request)
127
136
  return handle.map(lambda r: r.sync_response)
128
137
 
129
- def sync_status(
138
+ async def sync_status(
130
139
  self,
131
140
  id: str,
132
141
  ) -> MailboxHandle[wandb_sync_pb2.ServerSyncStatusResponse]:
@@ -134,7 +143,7 @@ class ServiceConnection:
134
143
  sync_status = wandb_sync_pb2.ServerSyncStatusRequest(id=id)
135
144
  request = spb.ServerRequest(sync_status=sync_status)
136
145
 
137
- handle = self._client.deliver(request)
146
+ handle = await self._client.deliver(request)
138
147
  return handle.map(lambda r: r.sync_status_response)
139
148
 
140
149
  def inform_init(
@@ -146,13 +155,17 @@ class ServiceConnection:
146
155
  request = spb.ServerInformInitRequest()
147
156
  request.settings.CopyFrom(settings)
148
157
  request._info.stream_id = run_id
149
- self._client.publish(spb.ServerRequest(inform_init=request))
158
+ self._asyncer.run(
159
+ lambda: self._client.publish(spb.ServerRequest(inform_init=request))
160
+ )
150
161
 
151
162
  def inform_finish(self, run_id: str) -> None:
152
163
  """Send an finish request to the service."""
153
164
  request = spb.ServerInformFinishRequest()
154
165
  request._info.stream_id = run_id
155
- self._client.publish(spb.ServerRequest(inform_finish=request))
166
+ self._asyncer.run(
167
+ lambda: self._client.publish(spb.ServerRequest(inform_finish=request))
168
+ )
156
169
 
157
170
  def inform_attach(
158
171
  self,
@@ -166,7 +179,7 @@ class ServiceConnection:
166
179
  request.inform_attach._info.stream_id = attach_id
167
180
 
168
181
  try:
169
- handle = self._client.deliver(request)
182
+ handle = self._asyncer.run(lambda: self._client.deliver(request))
170
183
  response = handle.wait_or(timeout=10)
171
184
 
172
185
  except (MailboxClosedError, HandleAbandonedError):
@@ -210,13 +223,16 @@ class ServiceConnection:
210
223
  # Clear the service token to prevent new connections to the process.
211
224
  service_token.clear_service_in_env()
212
225
 
213
- self._client.publish(
214
- spb.ServerRequest(
215
- inform_teardown=spb.ServerInformTeardownRequest(
216
- exit_code=exit_code,
217
- )
218
- ),
219
- )
220
- self._client.close()
226
+ async def publish_teardown_and_close() -> None:
227
+ await self._client.publish(
228
+ spb.ServerRequest(
229
+ inform_teardown=spb.ServerInformTeardownRequest(
230
+ exit_code=exit_code,
231
+ )
232
+ ),
233
+ )
234
+ await self._client.close()
235
+
236
+ self._asyncer.run(publish_teardown_and_close)
221
237
 
222
238
  return self._proc.join()
@@ -58,6 +58,8 @@ class MailboxHandle(abc.ABC, Generic[_T]):
58
58
  def cancel(self, iface: interface.InterfaceBase) -> None:
59
59
  """Cancel the handle, requesting any associated work to not complete.
60
60
 
61
+ It is an error to call this from an async function.
62
+
61
63
  This automatically abandons the handle, as a response is no longer
62
64
  guaranteed.
63
65
 
@@ -1,47 +1,26 @@
1
1
  # Generated by ariadne-codegen
2
2
 
3
- from .delete_project import DeleteProject, DeleteProjectDeleteModel
4
- from .fetch_registry import FetchRegistry, FetchRegistryEntity
5
- from .fragments import (
6
- RegistryFragment,
7
- RegistryFragmentArtifactTypes,
8
- RegistryFragmentArtifactTypesEdges,
9
- RegistryFragmentArtifactTypesEdgesNode,
10
- )
11
- from .input_types import ArtifactTypeInput
12
- from .operations import (
13
- DELETE_PROJECT_GQL,
14
- FETCH_REGISTRY_GQL,
15
- RENAME_PROJECT_GQL,
16
- UPSERT_REGISTRY_PROJECT_GQL,
17
- )
18
- from .rename_project import (
19
- RenameProject,
20
- RenameProjectRenameProject,
21
- RenameProjectRenameProjectProject,
22
- )
23
- from .upsert_registry_project import (
24
- UpsertRegistryProject,
25
- UpsertRegistryProjectUpsertModel,
26
- )
27
-
28
3
  __all__ = [
29
4
  "DELETE_PROJECT_GQL",
30
5
  "FETCH_REGISTRY_GQL",
31
6
  "RENAME_PROJECT_GQL",
32
7
  "UPSERT_REGISTRY_PROJECT_GQL",
33
8
  "FetchRegistry",
34
- "FetchRegistryEntity",
35
9
  "RenameProject",
36
- "RenameProjectRenameProject",
37
- "RenameProjectRenameProjectProject",
38
10
  "UpsertRegistryProject",
39
- "UpsertRegistryProjectUpsertModel",
40
11
  "DeleteProject",
41
- "DeleteProjectDeleteModel",
42
12
  "ArtifactTypeInput",
43
13
  "RegistryFragment",
44
- "RegistryFragmentArtifactTypes",
45
- "RegistryFragmentArtifactTypesEdges",
46
- "RegistryFragmentArtifactTypesEdgesNode",
47
14
  ]
15
+ from .delete_project import DeleteProject
16
+ from .fetch_registry import FetchRegistry
17
+ from .fragments import RegistryFragment
18
+ from .input_types import ArtifactTypeInput
19
+ from .operations import (
20
+ DELETE_PROJECT_GQL,
21
+ FETCH_REGISTRY_GQL,
22
+ RENAME_PROJECT_GQL,
23
+ UPSERT_REGISTRY_PROJECT_GQL,
24
+ )
25
+ from .rename_project import RenameProject
26
+ from .upsert_registry_project import UpsertRegistryProject
wandb/sdk/wandb_init.py CHANGED
@@ -406,6 +406,25 @@ class _WandbInit:
406
406
  run_id=settings.run_id,
407
407
  )
408
408
 
409
+ def set_sync_dir_suffix(self, settings: Settings) -> None:
410
+ """Add a suffix to sync_dir if it already exists.
411
+
412
+ The sync_dir uses a timestamp with second-level precision which can
413
+ result in conflicts if a run with the same ID is initialized within the
414
+ same second. This is most likely to happen in tests.
415
+
416
+ This can't prevent conflicts from multiple processes attempting
417
+ to create a wandb run simultaneously.
418
+
419
+ Args:
420
+ settings: Fully initialized settings other than the
421
+ x_sync_dir_suffix setting which will be modified.
422
+ """
423
+ index = 1
424
+ while pathlib.Path(settings.sync_dir).exists():
425
+ settings.x_sync_dir_suffix = f"{index}"
426
+ index += 1
427
+
409
428
  def make_run_config(
410
429
  self,
411
430
  settings: Settings,
@@ -708,7 +727,7 @@ class _WandbInit:
708
727
  drun = Run(
709
728
  settings=Settings(
710
729
  mode="disabled",
711
- x_files_dir=tempfile.gettempdir(),
730
+ root_dir=tempfile.gettempdir(),
712
731
  run_id=run_id,
713
732
  run_tags=tuple(),
714
733
  run_notes=None,
@@ -1407,8 +1426,8 @@ def init( # noqa: C901
1407
1426
  enable on your settings page.
1408
1427
  tensorboard: Deprecated. Use `sync_tensorboard` instead.
1409
1428
  sync_tensorboard: Enables automatic syncing of W&B logs from TensorBoard
1410
- or TensorBoardX, saving relevant event files for viewing in the W&B UI.
1411
- saving relevant event files for viewing in the W&B UI. (Default: `False`)
1429
+ or TensorBoardX, saving relevant event files for viewing in
1430
+ the W&B UI.
1412
1431
  monitor_gym: Enables automatic logging of videos of the environment when
1413
1432
  using OpenAI Gym.
1414
1433
  settings: Specifies a dictionary or `wandb.Settings` object with advanced
@@ -1524,6 +1543,7 @@ def init( # noqa: C901
1524
1543
  init_telemetry.feature.rewind_mode = True
1525
1544
 
1526
1545
  wi.set_run_id(run_settings)
1546
+ wi.set_sync_dir_suffix(run_settings)
1527
1547
  run_printer = printer.new_printer(run_settings)
1528
1548
  show_warnings(run_printer)
1529
1549
 
wandb/sdk/wandb_login.py CHANGED
@@ -77,7 +77,7 @@ def login(
77
77
  UsageError: If `api_key` cannot be configured and no tty.
78
78
  """
79
79
  _handle_host_wandb_setting(host)
80
- return _login(
80
+ logged_in, _ = _login(
81
81
  anonymous=anonymous,
82
82
  key=key,
83
83
  relogin=relogin,
@@ -87,6 +87,7 @@ def login(
87
87
  verify=verify,
88
88
  referrer=referrer,
89
89
  )
90
+ return logged_in
90
91
 
91
92
 
92
93
  class ApiKeyStatus(enum.Enum):
@@ -245,24 +246,6 @@ class _WandbLogin:
245
246
 
246
247
  return key, status
247
248
 
248
- def _verify_login(self, key: str) -> None:
249
- api = InternalApi(api_key=key)
250
-
251
- try:
252
- is_api_key_valid = api.validate_api_key()
253
- except ConnectionError:
254
- raise AuthenticationError(
255
- "Unable to connect to server to verify API token."
256
- )
257
- except Exception:
258
- raise AuthenticationError("An error occurred while verifying the API key.")
259
-
260
- if not is_api_key_valid:
261
- raise AuthenticationError(
262
- f"API key verification failed for host {self._settings.base_url}."
263
- " Make sure your API key is valid."
264
- )
265
-
266
249
 
267
250
  def _login(
268
251
  *,
@@ -277,11 +260,30 @@ def _login(
277
260
  update_api_key: bool = True,
278
261
  _silent: Optional[bool] = None,
279
262
  _disable_warning: Optional[bool] = None,
280
- ) -> bool:
263
+ ) -> (bool, Optional[str]):
264
+ """Logs in to W&B.
265
+
266
+ This is the internal implementation of wandb.login(),
267
+ with many of the same arguments as wandb.login().
268
+ Additional arguments are documented below.
269
+
270
+ Args:
271
+ update_api_key: If true, the api key will be saved or updated
272
+ in the users .netrc file.
273
+ _silent: If true, will not print any messages to the console.
274
+ _disable_warning: If true, no warning will be displayed
275
+ when calling wandb.login() after wandb.init().
276
+
277
+ Returns:
278
+ bool: If the login was successful
279
+ or the user is assumed to be already be logged in.
280
+ str: The API key used to log in,
281
+ or None if the api key was not verified during the login process.
282
+ """
281
283
  if wandb.run is not None:
282
284
  if not _disable_warning:
283
285
  wandb.termwarn("Calling wandb.login() after wandb.init() has no effect.")
284
- return True
286
+ return True, None
285
287
 
286
288
  wlogin = _WandbLogin(
287
289
  anonymous=anonymous,
@@ -293,19 +295,19 @@ def _login(
293
295
  )
294
296
 
295
297
  if wlogin._settings._noop:
296
- return True
298
+ return True, None
297
299
 
298
300
  if wlogin._settings._offline and not wlogin._settings.x_cli_only_mode:
299
301
  wandb.termwarn("Unable to verify login in offline mode.")
300
- return False
302
+ return False, None
301
303
  elif wandb.util._is_kaggle() and not wandb.util._has_internet():
302
304
  wandb.termerror(
303
305
  "To use W&B in kaggle you must enable internet in the settings panel on the right."
304
306
  )
305
- return False
307
+ return False, None
306
308
 
307
309
  if wlogin._settings.identity_token_file:
308
- return True
310
+ return True, None
309
311
 
310
312
  key_is_pre_configured = False
311
313
  key_status = None
@@ -318,7 +320,7 @@ def _login(
318
320
  key, key_status = wlogin.prompt_api_key(referrer=referrer)
319
321
 
320
322
  if verify:
321
- wlogin._verify_login(key)
323
+ _verify_login(key, wlogin._settings.base_url)
322
324
 
323
325
  if not key_is_pre_configured:
324
326
  if update_api_key:
@@ -329,4 +331,28 @@ def _login(
329
331
  if key and not _silent:
330
332
  wlogin._print_logged_in_message()
331
333
 
332
- return key is not None
334
+ return key is not None, key
335
+
336
+
337
+ def _verify_login(key: str, base_url: str) -> None:
338
+ api = InternalApi(
339
+ api_key=key,
340
+ default_settings={"base_url": base_url},
341
+ )
342
+
343
+ try:
344
+ is_api_key_valid = api.validate_api_key()
345
+ except ConnectionError:
346
+ raise AuthenticationError(
347
+ "Unable to connect to server to verify API token."
348
+ ) from None
349
+ except Exception as e:
350
+ raise AuthenticationError(
351
+ "An error occurred while verifying the API key."
352
+ ) from e
353
+
354
+ if not is_api_key_valid:
355
+ raise AuthenticationError(
356
+ f"API key verification failed for host {base_url}."
357
+ " Make sure your API key is valid."
358
+ )