wandb 0.19.9__py3-none-any.whl → 0.19.10__py3-none-any.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 +4 -1
- wandb/_pydantic/__init__.py +14 -7
- wandb/_pydantic/base.py +44 -9
- wandb/_pydantic/utils.py +66 -0
- wandb/_pydantic/v1_compat.py +78 -56
- wandb/apis/public/__init__.py +2 -2
- wandb/apis/public/api.py +114 -2
- wandb/apis/public/artifacts.py +365 -673
- wandb/apis/public/automations.py +69 -0
- wandb/apis/public/integrations.py +168 -0
- wandb/apis/public/projects.py +29 -0
- wandb/apis/public/utils.py +107 -1
- wandb/automations/__init__.py +81 -0
- wandb/automations/_filters/__init__.py +40 -0
- wandb/automations/_filters/expressions.py +179 -0
- wandb/automations/_filters/operators.py +267 -0
- wandb/automations/_filters/run_metrics.py +183 -0
- wandb/automations/_generated/__init__.py +184 -0
- wandb/automations/_generated/create_filter_trigger.py +21 -0
- wandb/automations/_generated/create_generic_webhook_integration.py +43 -0
- wandb/automations/_generated/delete_trigger.py +19 -0
- wandb/automations/_generated/enums.py +33 -0
- wandb/automations/_generated/fragments.py +343 -0
- wandb/automations/_generated/generic_webhook_integrations_by_entity.py +22 -0
- wandb/automations/_generated/get_triggers.py +24 -0
- wandb/automations/_generated/get_triggers_by_entity.py +24 -0
- wandb/automations/_generated/input_types.py +104 -0
- wandb/automations/_generated/integrations_by_entity.py +22 -0
- wandb/automations/_generated/operations.py +710 -0
- wandb/automations/_generated/slack_integrations_by_entity.py +22 -0
- wandb/automations/_generated/update_filter_trigger.py +21 -0
- wandb/automations/_utils.py +123 -0
- wandb/automations/_validators.py +73 -0
- wandb/automations/actions.py +205 -0
- wandb/automations/automations.py +109 -0
- wandb/automations/events.py +235 -0
- wandb/automations/integrations.py +26 -0
- wandb/automations/scopes.py +76 -0
- wandb/beta/workflows.py +9 -10
- wandb/bin/gpu_stats +0 -0
- wandb/cli/cli.py +3 -3
- wandb/integration/keras/keras.py +2 -1
- wandb/integration/langchain/wandb_tracer.py +2 -1
- wandb/jupyter.py +137 -118
- wandb/old/summary.py +0 -2
- wandb/proto/v3/wandb_internal_pb2.py +293 -292
- wandb/proto/v3/wandb_settings_pb2.py +2 -2
- wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v4/wandb_internal_pb2.py +292 -292
- wandb/proto/v4/wandb_settings_pb2.py +2 -2
- wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v5/wandb_internal_pb2.py +292 -292
- wandb/proto/v5/wandb_settings_pb2.py +2 -2
- wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v6/wandb_base_pb2.py +41 -0
- wandb/proto/v6/wandb_internal_pb2.py +393 -0
- wandb/proto/v6/wandb_server_pb2.py +78 -0
- wandb/proto/v6/wandb_settings_pb2.py +58 -0
- wandb/proto/v6/wandb_telemetry_pb2.py +52 -0
- wandb/proto/wandb_base_pb2.py +2 -0
- wandb/proto/wandb_deprecated.py +8 -0
- wandb/proto/wandb_internal_pb2.py +3 -1
- wandb/proto/wandb_server_pb2.py +2 -0
- wandb/proto/wandb_settings_pb2.py +2 -0
- wandb/proto/wandb_telemetry_pb2.py +2 -0
- wandb/sdk/artifacts/_generated/__init__.py +248 -0
- wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +43 -0
- wandb/sdk/artifacts/_generated/artifact_version_files.py +36 -0
- wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +36 -0
- wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +25 -0
- wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +35 -0
- wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +35 -0
- wandb/sdk/artifacts/_generated/enums.py +17 -0
- wandb/sdk/artifacts/_generated/fragments.py +186 -0
- wandb/sdk/artifacts/_generated/input_types.py +16 -0
- wandb/sdk/artifacts/_generated/move_artifact_collection.py +35 -0
- wandb/sdk/artifacts/_generated/operations.py +510 -0
- wandb/sdk/artifacts/_generated/project_artifact_collection.py +101 -0
- wandb/sdk/artifacts/_generated/project_artifact_collections.py +33 -0
- wandb/sdk/artifacts/_generated/project_artifact_type.py +24 -0
- wandb/sdk/artifacts/_generated/project_artifact_types.py +24 -0
- wandb/sdk/artifacts/_generated/project_artifacts.py +42 -0
- wandb/sdk/artifacts/_generated/run_input_artifacts.py +51 -0
- wandb/sdk/artifacts/_generated/run_output_artifacts.py +51 -0
- wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +35 -0
- wandb/sdk/artifacts/_generated/update_artifact_sequence.py +35 -0
- wandb/sdk/artifacts/_graphql_fragments.py +56 -79
- wandb/sdk/artifacts/artifact.py +40 -13
- wandb/sdk/artifacts/artifact_manifest_entry.py +2 -1
- wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
- wandb/sdk/data_types/base_types/media.py +2 -3
- wandb/sdk/data_types/base_types/wb_value.py +34 -11
- wandb/sdk/data_types/html.py +36 -9
- wandb/sdk/data_types/image.py +12 -12
- wandb/sdk/data_types/table.py +5 -0
- wandb/sdk/data_types/trace_tree.py +2 -0
- wandb/sdk/data_types/utils.py +1 -1
- wandb/sdk/data_types/video.py +14 -26
- wandb/sdk/interface/interface.py +2 -0
- wandb/sdk/internal/profiler.py +6 -5
- wandb/sdk/internal/run.py +13 -6
- wandb/sdk/lib/apikey.py +25 -4
- wandb/sdk/lib/asyncio_compat.py +1 -1
- wandb/sdk/lib/deprecate.py +13 -22
- wandb/sdk/lib/disabled.py +2 -1
- wandb/sdk/lib/printer.py +37 -8
- wandb/sdk/lib/printer_asyncio.py +46 -0
- wandb/sdk/lib/redirect.py +10 -5
- wandb/sdk/service/server_sock.py +19 -14
- wandb/sdk/service/service.py +9 -7
- wandb/sdk/service/streams.py +5 -0
- wandb/sdk/verify/verify.py +6 -3
- wandb/sdk/wandb_init.py +185 -65
- wandb/sdk/wandb_login.py +13 -4
- wandb/sdk/wandb_run.py +382 -286
- wandb/sdk/wandb_settings.py +21 -3
- wandb/sdk/wandb_setup.py +49 -0
- wandb/util.py +29 -29
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/METADATA +5 -5
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/RECORD +124 -71
- wandb/_globals.py +0 -19
- wandb/sdk/internal/_generated/base.py +0 -226
- wandb/sdk/internal/_generated/typing_compat.py +0 -14
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/WHEEL +0 -0
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/entry_points.txt +0 -0
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/data_types/html.py
CHANGED
@@ -16,19 +16,46 @@ if TYPE_CHECKING: # pragma: no cover
|
|
16
16
|
|
17
17
|
|
18
18
|
class Html(BatchableMedia):
|
19
|
-
"""
|
20
|
-
|
21
|
-
Args:
|
22
|
-
data: (string or io object) HTML to display in wandb
|
23
|
-
inject: (boolean) Add a stylesheet to the HTML object. If set
|
24
|
-
to False the HTML will pass through unchanged.
|
25
|
-
"""
|
19
|
+
"""A class for logging HTML content to W&B."""
|
26
20
|
|
27
21
|
_log_type = "html-file"
|
28
22
|
|
29
|
-
def __init__(
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
data: Union[str, "TextIO"],
|
26
|
+
inject: bool = True,
|
27
|
+
data_is_not_path: bool = False,
|
28
|
+
) -> None:
|
29
|
+
"""Creates a W&B HTML object.
|
30
|
+
|
31
|
+
It can be initialized by providing a path to a file:
|
32
|
+
```
|
33
|
+
with wandb.init() as run:
|
34
|
+
run.log({"html": wandb.Html("./index.html")})
|
35
|
+
```
|
36
|
+
|
37
|
+
Alternatively, it can be initialized by providing literal HTML,
|
38
|
+
in either a string or IO object:
|
39
|
+
```
|
40
|
+
with wandb.init() as run:
|
41
|
+
run.log({"html": wandb.Html("<h1>Hello, world!</h1>")})
|
42
|
+
```
|
43
|
+
|
44
|
+
Args:
|
45
|
+
data:
|
46
|
+
A string that is a path to a file with the extension ".html",
|
47
|
+
or a string or IO object containing literal HTML.
|
48
|
+
inject: Add a stylesheet to the HTML object. If set
|
49
|
+
to False the HTML will pass through unchanged.
|
50
|
+
data_is_not_path: If set to False, the data will be
|
51
|
+
treated as a path to a file.
|
52
|
+
"""
|
30
53
|
super().__init__()
|
31
|
-
data_is_path =
|
54
|
+
data_is_path = (
|
55
|
+
isinstance(data, str)
|
56
|
+
and os.path.isfile(data)
|
57
|
+
and os.path.splitext(data)[1] == ".html"
|
58
|
+
) and not data_is_not_path
|
32
59
|
data_path = ""
|
33
60
|
if data_is_path:
|
34
61
|
assert isinstance(data, str)
|
wandb/sdk/data_types/image.py
CHANGED
@@ -34,8 +34,8 @@ if TYPE_CHECKING: # pragma: no cover
|
|
34
34
|
TorchTensorType = Union["torch.Tensor", "torch.Variable"]
|
35
35
|
|
36
36
|
|
37
|
-
def _server_accepts_image_filenames() -> bool:
|
38
|
-
if
|
37
|
+
def _server_accepts_image_filenames(run: "LocalRun") -> bool:
|
38
|
+
if run.offline:
|
39
39
|
return True
|
40
40
|
|
41
41
|
# Newer versions of wandb accept large image filenames arrays
|
@@ -51,15 +51,15 @@ def _server_accepts_image_filenames() -> bool:
|
|
51
51
|
return accepts_image_filenames
|
52
52
|
|
53
53
|
|
54
|
-
def _server_accepts_artifact_path() -> bool:
|
55
|
-
|
54
|
+
def _server_accepts_artifact_path(run: "LocalRun") -> bool:
|
55
|
+
if run.offline:
|
56
|
+
return False
|
57
|
+
|
58
|
+
max_cli_version = util._get_max_cli_version()
|
59
|
+
if max_cli_version is None:
|
60
|
+
return False
|
56
61
|
|
57
|
-
|
58
|
-
max_cli_version = util._get_max_cli_version() if not util._is_offline() else None
|
59
|
-
accepts_artifact_path: bool = max_cli_version is not None and parse_version(
|
60
|
-
target_version
|
61
|
-
) <= parse_version(max_cli_version)
|
62
|
-
return accepts_artifact_path
|
62
|
+
return util.parse_version("0.12.14") <= util.parse_version(max_cli_version)
|
63
63
|
|
64
64
|
|
65
65
|
class Image(BatchableMedia):
|
@@ -401,7 +401,7 @@ class Image(BatchableMedia):
|
|
401
401
|
)
|
402
402
|
|
403
403
|
if (
|
404
|
-
not _server_accepts_artifact_path()
|
404
|
+
not _server_accepts_artifact_path(run)
|
405
405
|
or self._get_artifact_entry_ref_url() is None
|
406
406
|
):
|
407
407
|
super().bind_to_run(run, key, step, id_, ignore_copy_err=ignore_copy_err)
|
@@ -575,7 +575,7 @@ class Image(BatchableMedia):
|
|
575
575
|
"format": format,
|
576
576
|
"count": num_images_to_log,
|
577
577
|
}
|
578
|
-
if _server_accepts_image_filenames():
|
578
|
+
if _server_accepts_image_filenames(run):
|
579
579
|
meta["filenames"] = [
|
580
580
|
obj.get("path", obj.get("artifact_path")) for obj in jsons
|
581
581
|
]
|
wandb/sdk/data_types/table.py
CHANGED
@@ -480,6 +480,11 @@ class Table(Media):
|
|
480
480
|
max_rows = Table.MAX_ROWS
|
481
481
|
n_rows = len(self.data)
|
482
482
|
if n_rows > max_rows and warn:
|
483
|
+
# NOTE: Never raises for reinit="create_new" runs.
|
484
|
+
# Since this is called by bind_to_run(), this can be fixed by
|
485
|
+
# propagating the run. It cannot be fixed for to_json() calls
|
486
|
+
# that are given an artifact, other than by deferring to singleton
|
487
|
+
# settings.
|
483
488
|
if wandb.run and (
|
484
489
|
wandb.run.settings.table_raise_on_max_row_limit_exceeded
|
485
490
|
or wandb.run.settings.strict
|
@@ -431,6 +431,8 @@ class Trace:
|
|
431
431
|
name: The name of the trace to be logged
|
432
432
|
"""
|
433
433
|
trace_tree = WBTraceTree(self._span, self._model_dict)
|
434
|
+
# NOTE: Does not work for reinit="create_new" runs.
|
435
|
+
# This method should be deprecated and users should call run.log().
|
434
436
|
assert (
|
435
437
|
wandb.run is not None
|
436
438
|
), "You must call wandb.init() before logging a trace"
|
wandb/sdk/data_types/utils.py
CHANGED
wandb/sdk/data_types/video.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
+
import functools
|
1
2
|
import logging
|
2
3
|
import os
|
3
4
|
from io import BytesIO
|
4
|
-
from typing import TYPE_CHECKING, Any,
|
5
|
+
from typing import TYPE_CHECKING, Any, Optional, Sequence, Type, Union
|
5
6
|
|
6
7
|
import wandb
|
7
8
|
from wandb import util
|
8
|
-
from wandb.sdk.lib import filesystem, runid
|
9
|
+
from wandb.sdk.lib import filesystem, printer, printer_asyncio, runid
|
9
10
|
|
10
11
|
from . import _dtypes
|
11
12
|
from ._private import MEDIA_TMP
|
@@ -134,7 +135,11 @@ class Video(BatchableMedia):
|
|
134
135
|
"wandb.Video accepts a file path or numpy like data as input"
|
135
136
|
)
|
136
137
|
fps = fps or 4
|
137
|
-
|
138
|
+
printer_asyncio.run_async_with_spinner(
|
139
|
+
printer.new_printer(),
|
140
|
+
"Encoding video...",
|
141
|
+
functools.partial(self.encode, fps=fps),
|
142
|
+
)
|
138
143
|
|
139
144
|
def encode(self, fps: int = 4) -> None:
|
140
145
|
# import ImageSequenceClip from the appropriate MoviePy module
|
@@ -152,29 +157,12 @@ class Video(BatchableMedia):
|
|
152
157
|
filename = os.path.join(
|
153
158
|
MEDIA_TMP.name, runid.generate_id() + "." + self._format
|
154
159
|
)
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
else:
|
162
|
-
clip.write_videofile(filename, **kwargs)
|
163
|
-
except TypeError:
|
164
|
-
try: # even older versions of moviepy do not support progress_bar argument
|
165
|
-
kwargs = {"verbose": False, "progress_bar": False}
|
166
|
-
if self._format == "gif":
|
167
|
-
clip.write_gif(filename, **kwargs)
|
168
|
-
else:
|
169
|
-
clip.write_videofile(filename, **kwargs)
|
170
|
-
except TypeError:
|
171
|
-
kwargs = {
|
172
|
-
"verbose": False,
|
173
|
-
}
|
174
|
-
if self._format == "gif":
|
175
|
-
clip.write_gif(filename, **kwargs)
|
176
|
-
else:
|
177
|
-
clip.write_videofile(filename, **kwargs)
|
160
|
+
|
161
|
+
if self._format == "gif":
|
162
|
+
write_gif_with_image_io(clip, filename)
|
163
|
+
else:
|
164
|
+
clip.write_videofile(filename, logger=None)
|
165
|
+
|
178
166
|
self._set_file(filename, is_tmp=True)
|
179
167
|
|
180
168
|
@classmethod
|
wandb/sdk/interface/interface.py
CHANGED
@@ -173,6 +173,8 @@ class InterfaceBase:
|
|
173
173
|
self._make_config(data=config_dict, obj=proto_run.config)
|
174
174
|
if run._telemetry_obj:
|
175
175
|
proto_run.telemetry.MergeFrom(run._telemetry_obj)
|
176
|
+
if run._start_runtime:
|
177
|
+
proto_run.runtime = run._start_runtime
|
176
178
|
return proto_run
|
177
179
|
|
178
180
|
def publish_run(self, run: "Run") -> None:
|
wandb/sdk/internal/profiler.py
CHANGED
@@ -18,12 +18,13 @@ def torch_trace_handler():
|
|
18
18
|
torch.profiler.profile(..., on_trace_ready=wandb.profiler.torch_trace_handler())
|
19
19
|
```
|
20
20
|
|
21
|
-
Calling this function ensures that profiler charts & tables can be viewed in
|
22
|
-
on wandb.ai.
|
21
|
+
Calling this function ensures that profiler charts & tables can be viewed in
|
22
|
+
your run dashboard on wandb.ai.
|
23
23
|
|
24
|
-
Please note that `wandb.init()` must be called before this function is
|
25
|
-
|
26
|
-
|
24
|
+
Please note that `wandb.init()` must be called before this function is
|
25
|
+
invoked, and the reinit setting must not be set to "create_new". The PyTorch
|
26
|
+
(torch) version must also be at least 1.9, in order to ensure stability of
|
27
|
+
their Profiler API.
|
27
28
|
|
28
29
|
Args:
|
29
30
|
None
|
wandb/sdk/internal/run.py
CHANGED
@@ -5,21 +5,28 @@ Semi-stubbed run for internal process use.
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
-
|
8
|
+
import sys
|
9
9
|
|
10
|
-
|
10
|
+
if sys.version_info >= (3, 12):
|
11
|
+
from typing import override
|
12
|
+
else:
|
13
|
+
from typing_extensions import override
|
14
|
+
|
15
|
+
from wandb.sdk import wandb_run
|
11
16
|
|
12
17
|
|
13
18
|
class InternalRun(wandb_run.Run):
|
14
19
|
def __init__(self, run_obj, settings, datatypes_cb):
|
15
20
|
super().__init__(settings=settings)
|
16
21
|
self._run_obj = run_obj
|
22
|
+
self._datatypes_cb = datatypes_cb
|
17
23
|
|
18
|
-
|
19
|
-
# We really want a common interface for wandb_run.Run and InternalRun.
|
20
|
-
_datatypes_set_callback(datatypes_cb)
|
21
|
-
|
24
|
+
@override
|
22
25
|
def _set_backend(self, backend):
|
23
26
|
# This type of run object can't have a backend
|
24
27
|
# or do any writes.
|
25
28
|
pass
|
29
|
+
|
30
|
+
@override
|
31
|
+
def _publish_file(self, fname: str) -> None:
|
32
|
+
self._datatypes_cb(fname)
|
wandb/sdk/lib/apikey.py
CHANGED
@@ -83,6 +83,25 @@ def get_netrc_file_path() -> str:
|
|
83
83
|
return os.path.join(os.path.expanduser("~"), netrc_file)
|
84
84
|
|
85
85
|
|
86
|
+
def _api_key_prompt_str(app_url: str, referrer: str | None = None) -> str:
|
87
|
+
"""Generate a prompt string for API key authorization.
|
88
|
+
|
89
|
+
Creates a URL string that directs users to the authorization page where they
|
90
|
+
can find their API key.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
app_url: The base URL of the W&B application.
|
94
|
+
referrer: Optional referrer parameter to include in the URL.
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
A formatted string with instructions and the authorization URL.
|
98
|
+
"""
|
99
|
+
ref = ""
|
100
|
+
if referrer:
|
101
|
+
ref = f"?ref={referrer}"
|
102
|
+
return f"You can find your API key in your browser here: {app_url}/authorize{ref}"
|
103
|
+
|
104
|
+
|
86
105
|
def prompt_api_key( # noqa: C901
|
87
106
|
settings: Settings,
|
88
107
|
api: InternalApi | None = None,
|
@@ -91,6 +110,7 @@ def prompt_api_key( # noqa: C901
|
|
91
110
|
no_offline: bool = False,
|
92
111
|
no_create: bool = False,
|
93
112
|
local: bool = False,
|
113
|
+
referrer: str | None = None,
|
94
114
|
) -> str | bool | None:
|
95
115
|
"""Prompt for api key.
|
96
116
|
|
@@ -149,7 +169,10 @@ def prompt_api_key( # noqa: C901
|
|
149
169
|
key = browser_callback(signup=True) if browser_callback else None
|
150
170
|
|
151
171
|
if not key:
|
152
|
-
|
172
|
+
ref = f"&ref={referrer}" if referrer else ""
|
173
|
+
wandb.termlog(
|
174
|
+
f"Create an account here: {app_url}/authorize?signup=true{ref}"
|
175
|
+
)
|
153
176
|
key = input_callback(api_ask).strip()
|
154
177
|
elif result == LOGIN_CHOICE_EXISTS:
|
155
178
|
key = browser_callback() if browser_callback else None
|
@@ -164,9 +187,7 @@ def prompt_api_key( # noqa: C901
|
|
164
187
|
f"Logging into {host}. (Learn how to deploy a W&B server "
|
165
188
|
f"locally: {url_registry.url('wandb-server')})"
|
166
189
|
)
|
167
|
-
wandb.termlog(
|
168
|
-
f"You can find your API key in your browser here: {app_url}/authorize"
|
169
|
-
)
|
190
|
+
wandb.termlog(_api_key_prompt_str(app_url, referrer))
|
170
191
|
key = input_callback(api_ask).strip()
|
171
192
|
elif result == LOGIN_CHOICE_NOTTY:
|
172
193
|
# TODO: Needs refactor as this needs to be handled by caller
|
wandb/sdk/lib/asyncio_compat.py
CHANGED
wandb/sdk/lib/deprecate.py
CHANGED
@@ -1,42 +1,33 @@
|
|
1
|
-
|
1
|
+
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import TYPE_CHECKING
|
3
|
+
from typing import TYPE_CHECKING
|
4
4
|
|
5
5
|
import wandb
|
6
|
-
from wandb.proto.wandb_deprecated import DEPRECATED_FEATURES
|
7
|
-
from wandb.
|
6
|
+
from wandb.proto.wandb_deprecated import DEPRECATED_FEATURES
|
7
|
+
from wandb.sdk.lib import telemetry
|
8
8
|
|
9
|
-
#
|
9
|
+
# Necessary to break import cycle.
|
10
10
|
if TYPE_CHECKING:
|
11
|
-
from
|
12
|
-
|
13
|
-
|
14
|
-
deprecated_field_names: Tuple[str, ...] = tuple(
|
15
|
-
str(v) for k, v in Deprecated.__dict__.items() if not k.startswith("_")
|
16
|
-
)
|
11
|
+
from wandb import wandb_run
|
17
12
|
|
18
13
|
|
19
14
|
def deprecate(
|
20
15
|
field_name: DEPRECATED_FEATURES,
|
21
16
|
warning_message: str,
|
22
|
-
run:
|
17
|
+
run: wandb_run.Run | None = None,
|
23
18
|
) -> None:
|
24
19
|
"""Warn the user that a feature has been deprecated.
|
25
20
|
|
26
|
-
|
21
|
+
If a run is provided, the given field on its telemetry is updated.
|
22
|
+
Otherwise, the global run is used.
|
27
23
|
|
28
24
|
Args:
|
29
|
-
field_name: The
|
30
|
-
Defined in wandb/proto/wandb_telemetry.proto::Deprecated
|
25
|
+
field_name: The field on the Deprecated proto for this deprecation.
|
31
26
|
warning_message: The message to display to the user.
|
32
|
-
run: The run
|
27
|
+
run: The run whose telemetry to update.
|
33
28
|
"""
|
34
|
-
known_fields = TelemetryDeprecated.DESCRIPTOR.fields_by_name.keys()
|
35
|
-
if field_name not in known_fields:
|
36
|
-
raise ValueError(
|
37
|
-
f"Unknown field name: {field_name}. Known fields: {known_fields}"
|
38
|
-
)
|
39
29
|
_run = run or wandb.run
|
40
|
-
with
|
30
|
+
with telemetry.context(run=_run) as tel:
|
41
31
|
setattr(tel.deprecated, field_name, True)
|
32
|
+
|
42
33
|
wandb.termwarn(warning_message, repeat=False)
|
wandb/sdk/lib/disabled.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
|
+
from wandb.proto.wandb_deprecated import Deprecated
|
3
4
|
from wandb.sdk.lib import deprecate
|
4
5
|
|
5
6
|
|
@@ -23,7 +24,7 @@ class RunDisabled:
|
|
23
24
|
|
24
25
|
def __getattr__(self, name: str) -> Any:
|
25
26
|
deprecate.deprecate(
|
26
|
-
field_name=
|
27
|
+
field_name=Deprecated.run_disabled,
|
27
28
|
warning_message="RunDisabled is deprecated and is a no-op. "
|
28
29
|
'`wandb.init(mode="disabled")` now returns and instance of `wandb.sdk.wandb_run.Run`.',
|
29
30
|
)
|
wandb/sdk/lib/printer.py
CHANGED
@@ -20,6 +20,7 @@ import click
|
|
20
20
|
|
21
21
|
import wandb
|
22
22
|
from wandb.errors import term
|
23
|
+
from wandb.sdk import wandb_setup
|
23
24
|
|
24
25
|
from . import ipython, sparkline
|
25
26
|
|
@@ -98,12 +99,21 @@ _JUPYTER_PANEL_STYLES = """
|
|
98
99
|
"""
|
99
100
|
|
100
101
|
|
101
|
-
def new_printer() -> Printer:
|
102
|
-
"""Returns a
|
102
|
+
def new_printer(settings: wandb.Settings | None = None) -> Printer:
|
103
|
+
"""Returns a printer appropriate for the environment we're in.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
settings: The settings of a run. If not provided and `wandb.setup()`
|
107
|
+
has been called, then global settings are used. Otherwise,
|
108
|
+
settings (such as silent mode) are ignored.
|
109
|
+
"""
|
110
|
+
if not settings and (singleton := wandb_setup.singleton()):
|
111
|
+
settings = singleton.settings
|
112
|
+
|
103
113
|
if ipython.in_jupyter():
|
104
|
-
return _PrinterJupyter()
|
114
|
+
return _PrinterJupyter(settings=settings)
|
105
115
|
else:
|
106
|
-
return _PrinterTerm()
|
116
|
+
return _PrinterTerm(settings=settings)
|
107
117
|
|
108
118
|
|
109
119
|
class Printer(abc.ABC):
|
@@ -281,13 +291,18 @@ class DynamicText(abc.ABC):
|
|
281
291
|
|
282
292
|
|
283
293
|
class _PrinterTerm(Printer):
|
284
|
-
def __init__(self) -> None:
|
294
|
+
def __init__(self, *, settings: wandb.Settings | None) -> None:
|
285
295
|
super().__init__()
|
296
|
+
self._settings = settings
|
286
297
|
self._progress = itertools.cycle(["-", "\\", "|", "/"])
|
287
298
|
|
288
299
|
@override
|
289
300
|
@contextlib.contextmanager
|
290
301
|
def dynamic_text(self) -> Iterator[DynamicText | None]:
|
302
|
+
if self._settings and self._settings.silent:
|
303
|
+
yield None
|
304
|
+
return
|
305
|
+
|
291
306
|
with term.dynamic_text() as handle:
|
292
307
|
if not handle:
|
293
308
|
yield None
|
@@ -301,6 +316,9 @@ class _PrinterTerm(Printer):
|
|
301
316
|
*,
|
302
317
|
level: str | int | None = None,
|
303
318
|
) -> None:
|
319
|
+
if self._settings and self._settings.silent:
|
320
|
+
return
|
321
|
+
|
304
322
|
text = "\n".join(text) if isinstance(text, (list, tuple)) else text
|
305
323
|
self._display_fn_mapping(level)(text)
|
306
324
|
|
@@ -323,10 +341,16 @@ class _PrinterTerm(Printer):
|
|
323
341
|
|
324
342
|
@override
|
325
343
|
def progress_update(self, text: str, percent_done: float | None = None) -> None:
|
344
|
+
if self._settings and self._settings.silent:
|
345
|
+
return
|
346
|
+
|
326
347
|
wandb.termlog(f"{next(self._progress)} {text}", newline=False)
|
327
348
|
|
328
349
|
@override
|
329
350
|
def progress_close(self, text: str | None = None) -> None:
|
351
|
+
if self._settings and self._settings.silent:
|
352
|
+
return
|
353
|
+
|
330
354
|
text = text or " " * 79
|
331
355
|
wandb.termlog(text)
|
332
356
|
|
@@ -422,8 +446,9 @@ class _DynamicTermText(DynamicText):
|
|
422
446
|
|
423
447
|
|
424
448
|
class _PrinterJupyter(Printer):
|
425
|
-
def __init__(self) -> None:
|
449
|
+
def __init__(self, *, settings: wandb.Settings | None) -> None:
|
426
450
|
super().__init__()
|
451
|
+
self._settings = settings
|
427
452
|
self._progress = ipython.jupyter_progress_bar()
|
428
453
|
|
429
454
|
from IPython import display
|
@@ -433,6 +458,10 @@ class _PrinterJupyter(Printer):
|
|
433
458
|
@override
|
434
459
|
@contextlib.contextmanager
|
435
460
|
def dynamic_text(self) -> Iterator[DynamicText | None]:
|
461
|
+
if self._settings and self._settings.silent:
|
462
|
+
yield None
|
463
|
+
return
|
464
|
+
|
436
465
|
handle = self._ipython_display.display(
|
437
466
|
self._ipython_display.HTML(""),
|
438
467
|
display_id=True,
|
@@ -452,7 +481,7 @@ class _PrinterJupyter(Printer):
|
|
452
481
|
*,
|
453
482
|
level: str | int | None = None,
|
454
483
|
) -> None:
|
455
|
-
if
|
484
|
+
if self._settings and self._settings.silent:
|
456
485
|
return
|
457
486
|
|
458
487
|
text = "<br>".join(text) if isinstance(text, (list, tuple)) else text
|
@@ -507,7 +536,7 @@ class _PrinterJupyter(Printer):
|
|
507
536
|
text: str,
|
508
537
|
percent_done: float | None = None,
|
509
538
|
) -> None:
|
510
|
-
if not self._progress:
|
539
|
+
if (self._settings and self._settings.silent) or not self._progress:
|
511
540
|
return
|
512
541
|
|
513
542
|
if percent_done is None:
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Callable, TypeVar
|
3
|
+
|
4
|
+
from wandb.sdk.lib import asyncio_compat, printer
|
5
|
+
|
6
|
+
_T = TypeVar("_T")
|
7
|
+
|
8
|
+
|
9
|
+
def run_async_with_spinner(
|
10
|
+
spinner_printer: printer.Printer,
|
11
|
+
text: str,
|
12
|
+
func: Callable[[], _T],
|
13
|
+
) -> _T:
|
14
|
+
"""Run a slow function while displaying a loading icon.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
spinner_printer: The printer to use to display text.
|
18
|
+
text: The text to display next to the spinner while the function runs.
|
19
|
+
func: The function to run.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
The result of func.
|
23
|
+
"""
|
24
|
+
|
25
|
+
async def _loop_run_with_spinner() -> _T:
|
26
|
+
func_running = asyncio.Event()
|
27
|
+
|
28
|
+
async def update_spinner() -> None:
|
29
|
+
tick = 0
|
30
|
+
with spinner_printer.dynamic_text() as text_area:
|
31
|
+
if text_area:
|
32
|
+
while not func_running.is_set():
|
33
|
+
spinner = spinner_printer.loading_symbol(tick)
|
34
|
+
text_area.set_text(f"{spinner} {text}")
|
35
|
+
tick += 1
|
36
|
+
await asyncio.sleep(0.1)
|
37
|
+
else:
|
38
|
+
spinner_printer.display(text)
|
39
|
+
|
40
|
+
async with asyncio_compat.open_task_group() as group:
|
41
|
+
group.start_soon(update_spinner())
|
42
|
+
res = await asyncio.get_running_loop().run_in_executor(None, func)
|
43
|
+
func_running.set()
|
44
|
+
return res
|
45
|
+
|
46
|
+
return asyncio_compat.run(_loop_run_with_spinner)
|
wandb/sdk/lib/redirect.py
CHANGED
@@ -534,12 +534,15 @@ class StreamWrapper(RedirectBase):
|
|
534
534
|
self,
|
535
535
|
src: Literal["stdout", "stderr"],
|
536
536
|
cbs: Iterable[Callable[[str], None]] = (),
|
537
|
+
*,
|
538
|
+
flush_periodically: bool,
|
537
539
|
) -> None:
|
538
540
|
super().__init__(src=src, cbs=cbs)
|
539
541
|
self._uninstall: Callable[[], None] | None = None
|
540
542
|
self._emulator = TerminalEmulator()
|
541
543
|
self._queue: queue.Queue[str] = queue.Queue()
|
542
544
|
self._stopped = threading.Event()
|
545
|
+
self._flush_periodically = flush_periodically
|
543
546
|
|
544
547
|
def _emulator_write(self) -> None:
|
545
548
|
while True:
|
@@ -600,7 +603,7 @@ class StreamWrapper(RedirectBase):
|
|
600
603
|
self._emulator_write_thread.daemon = True
|
601
604
|
self._emulator_write_thread.start()
|
602
605
|
|
603
|
-
if
|
606
|
+
if self._flush_periodically:
|
604
607
|
self._callback_thread = threading.Thread(target=self._callback)
|
605
608
|
self._callback_thread.daemon = True
|
606
609
|
self._callback_thread.start()
|
@@ -732,10 +735,11 @@ _redirects: dict[str, Redirect | None] = {"stdout": None, "stderr": None}
|
|
732
735
|
class Redirect(RedirectBase):
|
733
736
|
"""Redirect low level file descriptors."""
|
734
737
|
|
735
|
-
def __init__(self, src, cbs=()):
|
738
|
+
def __init__(self, src, cbs=(), *, flush_periodically: bool):
|
736
739
|
super().__init__(src=src, cbs=cbs)
|
737
740
|
self._installed = False
|
738
741
|
self._emulator = TerminalEmulator()
|
742
|
+
self._flush_periodically = flush_periodically
|
739
743
|
|
740
744
|
def _pipe(self):
|
741
745
|
if pty:
|
@@ -767,7 +771,7 @@ class Redirect(RedirectBase):
|
|
767
771
|
self._emulator_write_thread = threading.Thread(target=self._emulator_write)
|
768
772
|
self._emulator_write_thread.daemon = True
|
769
773
|
self._emulator_write_thread.start()
|
770
|
-
if
|
774
|
+
if self._flush_periodically:
|
771
775
|
self._callback_thread = threading.Thread(target=self._callback)
|
772
776
|
self._callback_thread.daemon = True
|
773
777
|
self._callback_thread.start()
|
@@ -776,8 +780,9 @@ class Redirect(RedirectBase):
|
|
776
780
|
if not self._installed:
|
777
781
|
return
|
778
782
|
self._installed = False
|
779
|
-
# If the user printed a very long string (millions of chars) right
|
780
|
-
# it will take a while for it to reach pipe relay.
|
783
|
+
# If the user printed a very long string (millions of chars) right
|
784
|
+
# before run.finish(), it will take a while for it to reach pipe relay.
|
785
|
+
# 1 second is enough time for ~5 million chars.
|
781
786
|
time.sleep(1)
|
782
787
|
self._stopped.set()
|
783
788
|
os.dup2(self._orig_src_fd, self.src_fd)
|