wandb 0.21.3__py3-none-win_amd64.whl → 0.22.0__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.
- wandb/__init__.py +1 -1
- wandb/__init__.pyi +1 -1
- wandb/_analytics.py +65 -0
- wandb/_iterutils.py +8 -0
- wandb/_pydantic/__init__.py +10 -11
- wandb/_pydantic/base.py +3 -53
- wandb/_pydantic/field_types.py +29 -0
- wandb/_pydantic/v1_compat.py +47 -30
- wandb/_strutils.py +40 -0
- wandb/apis/public/__init__.py +42 -0
- wandb/apis/public/api.py +17 -4
- wandb/apis/public/artifacts.py +5 -4
- wandb/apis/public/automations.py +2 -1
- wandb/apis/public/registries/_freezable_list.py +6 -6
- wandb/apis/public/registries/_utils.py +2 -1
- wandb/apis/public/registries/registries_search.py +4 -0
- wandb/apis/public/registries/registry.py +7 -0
- wandb/apis/public/runs.py +24 -6
- wandb/automations/_filters/expressions.py +3 -2
- wandb/automations/_filters/operators.py +2 -1
- wandb/automations/_validators.py +20 -0
- wandb/automations/actions.py +4 -2
- wandb/automations/events.py +4 -5
- wandb/bin/gpu_stats.exe +0 -0
- wandb/bin/wandb-core +0 -0
- wandb/cli/beta.py +48 -130
- wandb/cli/beta_sync.py +226 -0
- wandb/integration/dspy/__init__.py +5 -0
- wandb/integration/dspy/dspy.py +422 -0
- wandb/integration/weave/weave.py +55 -0
- wandb/proto/v3/wandb_internal_pb2.py +234 -224
- wandb/proto/v3/wandb_server_pb2.py +38 -57
- wandb/proto/v3/wandb_sync_pb2.py +87 -0
- wandb/proto/v3/wandb_telemetry_pb2.py +12 -12
- wandb/proto/v4/wandb_internal_pb2.py +226 -224
- wandb/proto/v4/wandb_server_pb2.py +38 -41
- wandb/proto/v4/wandb_sync_pb2.py +38 -0
- wandb/proto/v4/wandb_telemetry_pb2.py +12 -12
- wandb/proto/v5/wandb_internal_pb2.py +226 -224
- wandb/proto/v5/wandb_server_pb2.py +38 -41
- wandb/proto/v5/wandb_sync_pb2.py +39 -0
- wandb/proto/v5/wandb_telemetry_pb2.py +12 -12
- wandb/proto/v6/wandb_base_pb2.py +3 -3
- wandb/proto/v6/wandb_internal_pb2.py +229 -227
- wandb/proto/v6/wandb_server_pb2.py +41 -44
- wandb/proto/v6/wandb_settings_pb2.py +3 -3
- wandb/proto/v6/wandb_sync_pb2.py +49 -0
- wandb/proto/v6/wandb_telemetry_pb2.py +15 -15
- wandb/proto/wandb_generate_proto.py +1 -0
- wandb/proto/wandb_sync_pb2.py +12 -0
- wandb/sdk/artifacts/_validators.py +50 -49
- wandb/sdk/artifacts/artifact.py +7 -7
- wandb/sdk/artifacts/exceptions.py +2 -1
- wandb/sdk/artifacts/storage_handlers/gcs_handler.py +1 -1
- wandb/sdk/artifacts/storage_handlers/http_handler.py +1 -3
- wandb/sdk/artifacts/storage_handlers/local_file_handler.py +1 -1
- wandb/sdk/artifacts/storage_handlers/s3_handler.py +3 -2
- wandb/sdk/artifacts/storage_policies/_factories.py +63 -0
- wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +59 -124
- wandb/sdk/interface/interface.py +10 -0
- wandb/sdk/interface/interface_shared.py +9 -0
- wandb/sdk/lib/asyncio_compat.py +88 -23
- wandb/sdk/lib/gql_request.py +18 -7
- wandb/sdk/lib/printer.py +9 -13
- wandb/sdk/lib/progress.py +8 -6
- wandb/sdk/lib/service/service_connection.py +42 -12
- wandb/sdk/mailbox/wait_with_progress.py +1 -1
- wandb/sdk/wandb_init.py +9 -9
- wandb/sdk/wandb_run.py +13 -1
- wandb/sdk/wandb_settings.py +55 -0
- wandb/wandb_agent.py +35 -4
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/METADATA +1 -1
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/RECORD +76 -64
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/WHEEL +0 -0
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/entry_points.txt +0 -0
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/lib/printer.py
CHANGED
@@ -156,14 +156,10 @@ class Printer(abc.ABC):
|
|
156
156
|
"""
|
157
157
|
|
158
158
|
@abc.abstractmethod
|
159
|
-
def progress_close(self
|
159
|
+
def progress_close(self) -> None:
|
160
160
|
"""Close the progress indicator.
|
161
161
|
|
162
162
|
After this, `progress_update` should not be used.
|
163
|
-
|
164
|
-
Args:
|
165
|
-
text: The final text to set on the progress indicator.
|
166
|
-
Ignored in Jupyter notebooks.
|
167
163
|
"""
|
168
164
|
|
169
165
|
@staticmethod
|
@@ -342,13 +338,10 @@ class _PrinterTerm(Printer):
|
|
342
338
|
wandb.termlog(f"{next(self._progress)} {text}", newline=False)
|
343
339
|
|
344
340
|
@override
|
345
|
-
def progress_close(self
|
341
|
+
def progress_close(self) -> None:
|
346
342
|
if self._settings and self._settings.silent:
|
347
343
|
return
|
348
344
|
|
349
|
-
text = text or " " * 79
|
350
|
-
wandb.termlog(text)
|
351
|
-
|
352
345
|
@property
|
353
346
|
@override
|
354
347
|
def supports_html(self) -> bool:
|
@@ -462,11 +455,14 @@ class _PrinterJupyter(Printer):
|
|
462
455
|
display_id=True,
|
463
456
|
)
|
464
457
|
|
465
|
-
if handle:
|
458
|
+
if not handle:
|
459
|
+
yield None
|
460
|
+
return
|
461
|
+
|
462
|
+
try:
|
466
463
|
yield _DynamicJupyterText(handle)
|
464
|
+
finally:
|
467
465
|
handle.update(self._ipython_display.HTML(""))
|
468
|
-
else:
|
469
|
-
yield None
|
470
466
|
|
471
467
|
@override
|
472
468
|
def display(
|
@@ -539,7 +535,7 @@ class _PrinterJupyter(Printer):
|
|
539
535
|
self._progress.update(percent_done, text)
|
540
536
|
|
541
537
|
@override
|
542
|
-
def progress_close(self
|
538
|
+
def progress_close(self) -> None:
|
543
539
|
if self._progress:
|
544
540
|
self._progress.close()
|
545
541
|
|
wandb/sdk/lib/progress.py
CHANGED
@@ -70,12 +70,14 @@ def progress_printer(
|
|
70
70
|
default_text: The text to show if no information is available.
|
71
71
|
"""
|
72
72
|
with printer.dynamic_text() as text_area:
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
try:
|
74
|
+
yield ProgressPrinter(
|
75
|
+
printer,
|
76
|
+
text_area,
|
77
|
+
default_text=default_text,
|
78
|
+
)
|
79
|
+
finally:
|
80
|
+
printer.progress_close()
|
79
81
|
|
80
82
|
|
81
83
|
class ProgressPrinter:
|
@@ -1,10 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import atexit
|
4
|
+
import pathlib
|
4
5
|
from typing import Callable
|
5
6
|
|
6
7
|
from wandb.proto import wandb_server_pb2 as spb
|
7
|
-
from wandb.proto import wandb_settings_pb2
|
8
|
+
from wandb.proto import wandb_settings_pb2, wandb_sync_pb2
|
8
9
|
from wandb.sdk import wandb_settings
|
9
10
|
from wandb.sdk.interface.interface import InterfaceBase
|
10
11
|
from wandb.sdk.interface.interface_sock import InterfaceSock
|
@@ -12,6 +13,7 @@ from wandb.sdk.lib import asyncio_manager
|
|
12
13
|
from wandb.sdk.lib.exit_hooks import ExitHooks
|
13
14
|
from wandb.sdk.lib.service.service_client import ServiceClient
|
14
15
|
from wandb.sdk.mailbox import HandleAbandonedError, MailboxClosedError
|
16
|
+
from wandb.sdk.mailbox.mailbox_handle import MailboxHandle
|
15
17
|
|
16
18
|
from . import service_process, service_token
|
17
19
|
|
@@ -96,6 +98,45 @@ class ServiceConnection:
|
|
96
98
|
"""Returns an interface for communicating with the service."""
|
97
99
|
return InterfaceSock(self._client, stream_id=stream_id)
|
98
100
|
|
101
|
+
def init_sync(
|
102
|
+
self,
|
103
|
+
paths: set[pathlib.Path],
|
104
|
+
settings: wandb_settings.Settings,
|
105
|
+
) -> MailboxHandle[wandb_sync_pb2.ServerInitSyncResponse]:
|
106
|
+
"""Send a ServerInitSyncRequest."""
|
107
|
+
init_sync = wandb_sync_pb2.ServerInitSyncRequest(
|
108
|
+
path=(str(path) for path in paths),
|
109
|
+
settings=settings.to_proto(),
|
110
|
+
)
|
111
|
+
request = spb.ServerRequest(init_sync=init_sync)
|
112
|
+
|
113
|
+
handle = self._client.deliver(request)
|
114
|
+
return handle.map(lambda r: r.init_sync_response)
|
115
|
+
|
116
|
+
def sync(
|
117
|
+
self,
|
118
|
+
id: str,
|
119
|
+
*,
|
120
|
+
parallelism: int,
|
121
|
+
) -> MailboxHandle[wandb_sync_pb2.ServerSyncResponse]:
|
122
|
+
"""Send a ServerSyncRequest."""
|
123
|
+
sync = wandb_sync_pb2.ServerSyncRequest(id=id, parallelism=parallelism)
|
124
|
+
request = spb.ServerRequest(sync=sync)
|
125
|
+
|
126
|
+
handle = self._client.deliver(request)
|
127
|
+
return handle.map(lambda r: r.sync_response)
|
128
|
+
|
129
|
+
def sync_status(
|
130
|
+
self,
|
131
|
+
id: str,
|
132
|
+
) -> MailboxHandle[wandb_sync_pb2.ServerSyncStatusResponse]:
|
133
|
+
"""Send a ServerSyncStatusRequest."""
|
134
|
+
sync_status = wandb_sync_pb2.ServerSyncStatusRequest(id=id)
|
135
|
+
request = spb.ServerRequest(sync_status=sync_status)
|
136
|
+
|
137
|
+
handle = self._client.deliver(request)
|
138
|
+
return handle.map(lambda r: r.sync_status_response)
|
139
|
+
|
99
140
|
def inform_init(
|
100
141
|
self,
|
101
142
|
settings: wandb_settings_pb2.Settings,
|
@@ -143,17 +184,6 @@ class ServiceConnection:
|
|
143
184
|
else:
|
144
185
|
return response.inform_attach_response.settings
|
145
186
|
|
146
|
-
def inform_start(
|
147
|
-
self,
|
148
|
-
settings: wandb_settings_pb2.Settings,
|
149
|
-
run_id: str,
|
150
|
-
) -> None:
|
151
|
-
"""Send a start request to the service."""
|
152
|
-
request = spb.ServerInformStartRequest()
|
153
|
-
request.settings.CopyFrom(settings)
|
154
|
-
request._info.stream_id = run_id
|
155
|
-
self._client.publish(spb.ServerRequest(inform_start=request))
|
156
|
-
|
157
187
|
def teardown(self, exit_code: int) -> int | None:
|
158
188
|
"""Close the connection.
|
159
189
|
|
@@ -62,7 +62,7 @@ def wait_all_with_progress(
|
|
62
62
|
start_time = time.monotonic()
|
63
63
|
|
64
64
|
async def progress_loop_with_timeout() -> list[_T]:
|
65
|
-
with asyncio_compat.cancel_on_exit(display_progress()):
|
65
|
+
async with asyncio_compat.cancel_on_exit(display_progress()):
|
66
66
|
if timeout is not None:
|
67
67
|
elapsed_time = time.monotonic() - start_time
|
68
68
|
remaining_timeout = timeout - elapsed_time
|
wandb/sdk/wandb_init.py
CHANGED
@@ -839,6 +839,13 @@ class _WandbInit:
|
|
839
839
|
" and reinit is set to 'create_new', so continuing"
|
840
840
|
)
|
841
841
|
|
842
|
+
elif settings.resume == "must":
|
843
|
+
raise wandb.Error(
|
844
|
+
"Cannot resume a run while another run is active."
|
845
|
+
" You must either finish it using run.finish(),"
|
846
|
+
" or use reinit='create_new' when calling wandb.init()."
|
847
|
+
)
|
848
|
+
|
842
849
|
else:
|
843
850
|
run_printer.display(
|
844
851
|
"wandb.init() called while a run is active and reinit is"
|
@@ -864,7 +871,6 @@ class _WandbInit:
|
|
864
871
|
backend.ensure_launched()
|
865
872
|
self._logger.info("backend started and connected")
|
866
873
|
|
867
|
-
# resuming needs access to the server, check server_status()?
|
868
874
|
run = Run(
|
869
875
|
config=config.base_no_artifacts,
|
870
876
|
settings=settings,
|
@@ -1009,14 +1015,6 @@ class _WandbInit:
|
|
1009
1015
|
run._set_run_obj(result.run_result.run)
|
1010
1016
|
|
1011
1017
|
self._logger.info("starting run threads in backend")
|
1012
|
-
# initiate run (stats and metadata probing)
|
1013
|
-
|
1014
|
-
if service:
|
1015
|
-
assert settings.run_id
|
1016
|
-
service.inform_start(
|
1017
|
-
settings=settings.to_proto(),
|
1018
|
-
run_id=settings.run_id,
|
1019
|
-
)
|
1020
1018
|
|
1021
1019
|
assert backend.interface
|
1022
1020
|
|
@@ -1027,6 +1025,8 @@ class _WandbInit:
|
|
1027
1025
|
except TimeoutError:
|
1028
1026
|
pass
|
1029
1027
|
|
1028
|
+
backend.interface.publish_probe_system_info()
|
1029
|
+
|
1030
1030
|
assert self._wl is not None
|
1031
1031
|
self.run = run
|
1032
1032
|
|
wandb/sdk/wandb_run.py
CHANGED
@@ -892,7 +892,19 @@ class Run:
|
|
892
892
|
def tags(self, tags: Sequence) -> None:
|
893
893
|
with telemetry.context(run=self) as tel:
|
894
894
|
tel.feature.set_run_tags = True
|
895
|
-
|
895
|
+
|
896
|
+
try:
|
897
|
+
self._settings.run_tags = tuple(tags)
|
898
|
+
except ValueError as e:
|
899
|
+
# For runtime tag setting, warn instead of crash
|
900
|
+
# Extract the core error message without the pydantic wrapper
|
901
|
+
error_msg = str(e)
|
902
|
+
if "Value error," in error_msg:
|
903
|
+
# Extract the actual error message after "Value error, "
|
904
|
+
error_msg = error_msg.split("Value error, ")[1].split(" [type=")[0]
|
905
|
+
wandb.termwarn(f"Invalid tag detected: {error_msg} Tags not updated.")
|
906
|
+
return
|
907
|
+
|
896
908
|
if self._backend and self._backend.interface:
|
897
909
|
self._backend.interface.publish_run(self)
|
898
910
|
|
wandb/sdk/wandb_settings.py
CHANGED
@@ -1435,6 +1435,61 @@ class Settings(BaseModel, validate_assignment=True):
|
|
1435
1435
|
raise UsageError("Sweep ID cannot contain only whitespace")
|
1436
1436
|
return value
|
1437
1437
|
|
1438
|
+
@field_validator("run_tags", mode="before")
|
1439
|
+
@classmethod
|
1440
|
+
def validate_run_tags(cls, value):
|
1441
|
+
"""Validate run tags.
|
1442
|
+
|
1443
|
+
Validates that each tag:
|
1444
|
+
- Is between 1 and 64 characters in length (inclusive)
|
1445
|
+
- Converts single string values to tuple format
|
1446
|
+
- Preserves None values
|
1447
|
+
|
1448
|
+
Args:
|
1449
|
+
value: A string, list, tuple, or None representing tags
|
1450
|
+
|
1451
|
+
Returns:
|
1452
|
+
tuple: A tuple of validated tags, or None
|
1453
|
+
|
1454
|
+
Raises:
|
1455
|
+
ValueError: If any tag is empty or exceeds 64 characters
|
1456
|
+
|
1457
|
+
<!-- lazydoc-ignore-classmethod: internal -->
|
1458
|
+
"""
|
1459
|
+
if value is None:
|
1460
|
+
return None
|
1461
|
+
|
1462
|
+
# Convert to tuple if needed
|
1463
|
+
if isinstance(value, str):
|
1464
|
+
tags = (value,)
|
1465
|
+
else:
|
1466
|
+
tags = tuple(value)
|
1467
|
+
|
1468
|
+
# Validate each tag and accumulate errors
|
1469
|
+
errors = []
|
1470
|
+
for i, tag in enumerate(tags):
|
1471
|
+
tag_str = str(tag)
|
1472
|
+
if len(tag_str) == 0:
|
1473
|
+
errors.append(
|
1474
|
+
f"Tag at index {i} is empty. Tags must be between 1 and 64 characters"
|
1475
|
+
)
|
1476
|
+
elif len(tag_str) > 64:
|
1477
|
+
# Truncate long tags for display
|
1478
|
+
display_tag = (
|
1479
|
+
f"{tag_str[:20]}...{tag_str[-20:]}"
|
1480
|
+
if len(tag_str) > 43
|
1481
|
+
else tag_str
|
1482
|
+
)
|
1483
|
+
errors.append(
|
1484
|
+
f"Tag '{display_tag}' is {len(tag_str)} characters. Tags must be between 1 and 64 characters"
|
1485
|
+
)
|
1486
|
+
|
1487
|
+
# Raise combined error if any validation issues were found
|
1488
|
+
if errors:
|
1489
|
+
raise ValueError("; ".join(errors))
|
1490
|
+
|
1491
|
+
return tags
|
1492
|
+
|
1438
1493
|
@field_validator("sweep_param_path", mode="before")
|
1439
1494
|
@classmethod
|
1440
1495
|
def validate_sweep_param_path(cls, value):
|
wandb/wandb_agent.py
CHANGED
@@ -42,11 +42,42 @@ class AgentProcess:
|
|
42
42
|
if command:
|
43
43
|
if platform.system() == "Windows":
|
44
44
|
kwargs = dict(creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
45
|
+
env.pop(wandb.env.SERVICE, None)
|
46
|
+
# TODO: Determine if we need the same stdin workaround as POSIX case below.
|
47
|
+
self._popen = subprocess.Popen(command, env=env, **kwargs)
|
45
48
|
else:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
if sys.version_info >= (3, 11):
|
50
|
+
# preexec_fn=os.setpgrp is not thread-safe; process_group was introduced in
|
51
|
+
# python 3.11 to replace it, so use that when possible
|
52
|
+
kwargs = dict(process_group=0)
|
53
|
+
else:
|
54
|
+
kwargs = dict(preexec_fn=os.setpgrp)
|
55
|
+
env.pop(wandb.env.SERVICE, None)
|
56
|
+
# Upon spawning the subprocess in a new process group, the child's process group is
|
57
|
+
# not connected to the controlling terminal's stdin. If it tries to access stdin,
|
58
|
+
# it gets a SIGTTIN and blocks until we give it the terminal, which we don't want
|
59
|
+
# to do.
|
60
|
+
#
|
61
|
+
# By using subprocess.PIPE, we give it an independent stdin. However, it will still
|
62
|
+
# block if it tries to read from stdin, because we're not writing anything to it.
|
63
|
+
# We immediately close the subprocess's stdin here so it can fail fast and get an
|
64
|
+
# EOF.
|
65
|
+
#
|
66
|
+
# (One situation that makes this relevant is that importing `readline` even
|
67
|
+
# indirectly can cause the child to attempt to access stdin, which can trigger the
|
68
|
+
# deadlock. In Python 3.13, `import torch` indirectly imports `readline` via `pdb`,
|
69
|
+
# meaning `import torch` in a run script can deadlock unless we override stdin.
|
70
|
+
# See https://github.com/wandb/wandb/pull/10489 description for more details.)
|
71
|
+
#
|
72
|
+
# Also, we avoid spawning a new session because that breaks preempted child process
|
73
|
+
# handling.
|
74
|
+
self._popen = subprocess.Popen(
|
75
|
+
command,
|
76
|
+
env=env,
|
77
|
+
stdin=subprocess.PIPE,
|
78
|
+
**kwargs,
|
79
|
+
)
|
80
|
+
self._popen.stdin.close()
|
50
81
|
elif function:
|
51
82
|
self._proc = multiprocessing.Process(
|
52
83
|
target=self._start,
|