wandb 0.22.0__py3-none-musllinux_1_2_aarch64.whl → 0.22.2__py3-none-musllinux_1_2_aarch64.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.
- wandb/__init__.py +1 -1
- wandb/__init__.pyi +8 -5
- wandb/_pydantic/__init__.py +12 -11
- wandb/_pydantic/base.py +49 -19
- wandb/apis/__init__.py +2 -0
- wandb/apis/attrs.py +2 -0
- wandb/apis/importers/internals/internal.py +16 -23
- wandb/apis/internal.py +2 -0
- wandb/apis/normalize.py +2 -0
- wandb/apis/public/__init__.py +3 -2
- wandb/apis/public/api.py +215 -164
- wandb/apis/public/artifacts.py +23 -20
- wandb/apis/public/const.py +2 -0
- wandb/apis/public/files.py +33 -24
- wandb/apis/public/history.py +2 -0
- wandb/apis/public/jobs.py +20 -18
- wandb/apis/public/projects.py +4 -2
- wandb/apis/public/query_generator.py +3 -0
- wandb/apis/public/registries/__init__.py +7 -0
- wandb/apis/public/registries/_freezable_list.py +9 -12
- wandb/apis/public/registries/registries_search.py +8 -6
- wandb/apis/public/registries/registry.py +22 -17
- wandb/apis/public/reports.py +2 -0
- wandb/apis/public/runs.py +261 -57
- wandb/apis/public/sweeps.py +10 -9
- wandb/apis/public/teams.py +2 -0
- wandb/apis/public/users.py +2 -0
- wandb/apis/public/utils.py +16 -15
- wandb/automations/_generated/__init__.py +54 -127
- wandb/automations/_generated/create_generic_webhook_integration.py +1 -7
- wandb/automations/_generated/fragments.py +26 -91
- wandb/bin/gpu_stats +0 -0
- wandb/bin/wandb-core +0 -0
- wandb/cli/beta.py +16 -2
- wandb/cli/beta_leet.py +74 -0
- wandb/cli/beta_sync.py +9 -11
- wandb/cli/cli.py +34 -7
- wandb/errors/errors.py +3 -3
- wandb/proto/v3/wandb_api_pb2.py +86 -0
- wandb/proto/v3/wandb_internal_pb2.py +352 -351
- wandb/proto/v3/wandb_settings_pb2.py +2 -2
- wandb/proto/v3/wandb_sync_pb2.py +19 -6
- wandb/proto/v4/wandb_api_pb2.py +37 -0
- wandb/proto/v4/wandb_internal_pb2.py +352 -351
- wandb/proto/v4/wandb_settings_pb2.py +2 -2
- wandb/proto/v4/wandb_sync_pb2.py +10 -6
- wandb/proto/v5/wandb_api_pb2.py +38 -0
- wandb/proto/v5/wandb_internal_pb2.py +352 -351
- wandb/proto/v5/wandb_settings_pb2.py +2 -2
- wandb/proto/v5/wandb_sync_pb2.py +10 -6
- wandb/proto/v6/wandb_api_pb2.py +48 -0
- wandb/proto/v6/wandb_internal_pb2.py +352 -351
- wandb/proto/v6/wandb_settings_pb2.py +2 -2
- wandb/proto/v6/wandb_sync_pb2.py +10 -6
- wandb/proto/wandb_api_pb2.py +18 -0
- wandb/proto/wandb_generate_proto.py +1 -0
- wandb/sdk/artifacts/_factories.py +7 -2
- wandb/sdk/artifacts/_generated/__init__.py +112 -412
- wandb/sdk/artifacts/_generated/fragments.py +65 -0
- wandb/sdk/artifacts/_generated/operations.py +52 -22
- wandb/sdk/artifacts/_generated/run_input_artifacts.py +3 -23
- wandb/sdk/artifacts/_generated/run_output_artifacts.py +3 -23
- wandb/sdk/artifacts/_generated/type_info.py +19 -0
- wandb/sdk/artifacts/_gqlutils.py +47 -0
- wandb/sdk/artifacts/_models/__init__.py +4 -0
- wandb/sdk/artifacts/_models/base_model.py +20 -0
- wandb/sdk/artifacts/_validators.py +40 -12
- wandb/sdk/artifacts/artifact.py +99 -118
- wandb/sdk/artifacts/artifact_file_cache.py +6 -1
- wandb/sdk/artifacts/artifact_manifest_entry.py +67 -14
- wandb/sdk/artifacts/storage_handler.py +18 -12
- wandb/sdk/artifacts/storage_handlers/azure_handler.py +11 -6
- wandb/sdk/artifacts/storage_handlers/gcs_handler.py +9 -6
- wandb/sdk/artifacts/storage_handlers/http_handler.py +9 -4
- wandb/sdk/artifacts/storage_handlers/local_file_handler.py +10 -6
- wandb/sdk/artifacts/storage_handlers/multi_handler.py +5 -4
- wandb/sdk/artifacts/storage_handlers/s3_handler.py +10 -8
- wandb/sdk/artifacts/storage_handlers/tracking_handler.py +6 -4
- wandb/sdk/artifacts/storage_handlers/wb_artifact_handler.py +24 -21
- wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +4 -2
- wandb/sdk/artifacts/storage_policies/_multipart.py +187 -0
- wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +71 -242
- wandb/sdk/artifacts/storage_policy.py +25 -12
- wandb/sdk/data_types/bokeh.py +5 -1
- wandb/sdk/data_types/image.py +17 -6
- wandb/sdk/data_types/object_3d.py +67 -2
- wandb/sdk/interface/interface.py +31 -4
- wandb/sdk/interface/interface_queue.py +10 -0
- wandb/sdk/interface/interface_shared.py +0 -7
- wandb/sdk/interface/interface_sock.py +9 -3
- wandb/sdk/internal/_generated/__init__.py +2 -12
- wandb/sdk/internal/job_builder.py +27 -10
- wandb/sdk/internal/sender.py +5 -2
- wandb/sdk/internal/settings_static.py +2 -82
- wandb/sdk/launch/create_job.py +2 -1
- wandb/sdk/launch/runner/kubernetes_runner.py +25 -20
- wandb/sdk/launch/utils.py +82 -1
- wandb/sdk/lib/progress.py +8 -74
- wandb/sdk/lib/service/service_client.py +5 -9
- wandb/sdk/lib/service/service_connection.py +39 -23
- wandb/sdk/mailbox/mailbox_handle.py +2 -0
- wandb/sdk/projects/_generated/__init__.py +12 -33
- wandb/sdk/wandb_init.py +23 -3
- wandb/sdk/wandb_login.py +53 -27
- wandb/sdk/wandb_run.py +10 -5
- wandb/sdk/wandb_settings.py +63 -25
- wandb/sync/sync.py +7 -2
- wandb/util.py +1 -1
- {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/METADATA +1 -1
- {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/RECORD +784 -774
- wandb/sdk/artifacts/_graphql_fragments.py +0 -19
- {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/WHEEL +0 -0
- {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/entry_points.txt +0 -0
- {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
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
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(
|
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.
|
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.
|
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
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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
|
-
|
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
|
1411
|
-
|
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
|
-
|
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
|
-
|
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
|
+
)
|