wandb 0.18.6__py3-none-win_amd64.whl → 0.19.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.
- package_readme.md +8 -0
- wandb/__init__.py +5 -7
- wandb/__init__.pyi +51 -30
- wandb/analytics/sentry.py +4 -10
- wandb/apis/importers/internals/internal.py +6 -6
- wandb/apis/importers/internals/protocols.py +11 -7
- wandb/apis/public/api.py +5 -1
- wandb/apis/public/jobs.py +1 -7
- wandb/apis/public/reports.py +6 -17
- wandb/apis/public/runs.py +12 -10
- wandb/bin/gpu_stats.exe +0 -0
- wandb/bin/wandb-core +0 -0
- wandb/cli/cli.py +9 -45
- wandb/env.py +3 -5
- wandb/errors/links.py +1 -1
- wandb/errors/term.py +1 -6
- wandb/filesync/dir_watcher.py +3 -3
- wandb/filesync/step_upload.py +2 -5
- wandb/integration/fastai/__init__.py +1 -6
- wandb/integration/gym/__init__.py +1 -7
- wandb/integration/keras/callbacks/metrics_logger.py +1 -8
- wandb/integration/keras/callbacks/model_checkpoint.py +1 -8
- wandb/integration/keras/keras.py +3 -5
- wandb/integration/lightgbm/__init__.py +1 -1
- wandb/integration/sb3/sb3.py +1 -7
- wandb/integration/sklearn/utils.py +1 -1
- wandb/integration/tensorboard/log.py +1 -2
- wandb/integration/torch/wandb_torch.py +1 -1
- wandb/integration/ultralytics/bbox_utils.py +9 -2
- wandb/jupyter.py +4 -4
- wandb/proto/v3/wandb_internal_pb2.py +31 -31
- wandb/proto/v3/wandb_settings_pb2.py +2 -2
- wandb/proto/v3/wandb_telemetry_pb2.py +4 -4
- wandb/proto/v4/wandb_internal_pb2.py +31 -31
- wandb/proto/v4/wandb_settings_pb2.py +2 -2
- wandb/proto/v4/wandb_telemetry_pb2.py +4 -4
- wandb/proto/v5/wandb_internal_pb2.py +31 -31
- wandb/proto/v5/wandb_settings_pb2.py +2 -2
- wandb/proto/v5/wandb_telemetry_pb2.py +4 -4
- wandb/proto/wandb_deprecated.py +3 -11
- wandb/proto/wandb_generate_deprecated.py +3 -7
- wandb/sdk/artifacts/artifact.py +3 -11
- wandb/sdk/artifacts/artifact_file_cache.py +2 -5
- wandb/sdk/artifacts/artifact_saver.py +2 -6
- wandb/sdk/artifacts/storage_handlers/gcs_handler.py +2 -4
- wandb/sdk/artifacts/storage_handlers/local_file_handler.py +2 -4
- wandb/sdk/artifacts/storage_handlers/s3_handler.py +2 -4
- wandb/sdk/backend/backend.py +1 -1
- wandb/sdk/data_types/base_types/wb_value.py +20 -10
- wandb/sdk/data_types/histogram.py +1 -3
- wandb/sdk/data_types/object_3d.py +2 -6
- wandb/sdk/data_types/table.py +1 -1
- wandb/sdk/data_types/utils.py +1 -2
- wandb/sdk/data_types/video.py +15 -4
- wandb/sdk/integration_utils/auto_logging.py +1 -8
- wandb/sdk/interface/interface.py +12 -5
- wandb/sdk/interface/interface_queue.py +0 -6
- wandb/sdk/interface/interface_shared.py +9 -0
- wandb/sdk/interface/router.py +1 -2
- wandb/sdk/interface/router_queue.py +0 -3
- wandb/sdk/interface/router_relay.py +0 -2
- wandb/sdk/internal/file_stream.py +1 -4
- wandb/sdk/internal/flow_control.py +1 -1
- wandb/sdk/internal/handler.py +8 -5
- wandb/sdk/internal/internal.py +3 -17
- wandb/sdk/internal/internal_api.py +3 -10
- wandb/sdk/internal/internal_util.py +0 -3
- wandb/sdk/internal/job_builder.py +20 -12
- wandb/sdk/internal/progress.py +1 -5
- wandb/sdk/internal/sender.py +9 -15
- wandb/sdk/internal/settings_static.py +4 -10
- wandb/sdk/internal/system/assets/cpu.py +2 -2
- wandb/sdk/internal/system/assets/disk.py +3 -3
- wandb/sdk/internal/system/assets/gpu.py +7 -7
- wandb/sdk/internal/system/assets/gpu_amd.py +1 -7
- wandb/sdk/internal/system/assets/interfaces.py +11 -13
- wandb/sdk/internal/system/assets/ipu.py +1 -1
- wandb/sdk/internal/system/assets/memory.py +2 -2
- wandb/sdk/internal/system/assets/open_metrics.py +2 -8
- wandb/sdk/internal/system/assets/trainium.py +3 -9
- wandb/sdk/internal/system/system_info.py +14 -13
- wandb/sdk/internal/system/system_monitor.py +5 -12
- wandb/sdk/internal/tb_watcher.py +1 -1
- wandb/sdk/internal/writer.py +2 -4
- wandb/sdk/launch/__init__.py +2 -1
- wandb/sdk/launch/agent/run_queue_item_file_saver.py +1 -7
- wandb/sdk/launch/create_job.py +2 -3
- wandb/sdk/launch/runner/abstract.py +1 -6
- wandb/sdk/launch/runner/kubernetes_monitor.py +2 -4
- wandb/sdk/lib/apikey.py +2 -6
- wandb/sdk/lib/fsm.py +12 -6
- wandb/sdk/lib/ipython.py +1 -6
- wandb/sdk/lib/module.py +0 -3
- wandb/sdk/lib/progress.py +2 -3
- wandb/sdk/lib/run_moment.py +1 -7
- wandb/sdk/lib/server.py +10 -24
- wandb/sdk/lib/sock_client.py +0 -5
- wandb/sdk/service/server.py +3 -12
- wandb/sdk/service/server_sock.py +0 -2
- wandb/sdk/service/service.py +5 -5
- wandb/sdk/wandb_init.py +215 -166
- wandb/sdk/wandb_login.py +17 -27
- wandb/sdk/wandb_run.py +129 -161
- wandb/sdk/wandb_settings.py +978 -1760
- wandb/sdk/wandb_setup.py +87 -94
- wandb/sdk/wandb_watch.py +1 -1
- wandb/sync/sync.py +1 -2
- wandb/util.py +7 -40
- wandb/wandb_controller.py +10 -12
- {wandb-0.18.6.dist-info → wandb-0.19.0.dist-info}/METADATA +14 -4
- {wandb-0.18.6.dist-info → wandb-0.19.0.dist-info}/RECORD +114 -120
- {wandb-0.18.6.dist-info → wandb-0.19.0.dist-info}/WHEEL +1 -1
- wandb/integration/magic.py +0 -556
- wandb/magic.py +0 -3
- wandb/sdk/lib/_settings_toposort_generate.py +0 -159
- wandb/sdk/lib/_settings_toposort_generated.py +0 -250
- wandb/sdk/lib/reporting.py +0 -99
- wandb/sdk/lib/tracelog.py +0 -255
- {wandb-0.18.6.dist-info → wandb-0.19.0.dist-info}/entry_points.txt +0 -0
- {wandb-0.18.6.dist-info → wandb-0.19.0.dist-info}/licenses/LICENSE +0 -0
wandb/sdk/wandb_settings.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import
|
1
|
+
from __future__ import annotations
|
2
|
+
|
2
3
|
import configparser
|
3
|
-
import enum
|
4
4
|
import getpass
|
5
5
|
import json
|
6
6
|
import logging
|
@@ -12,1026 +12,517 @@ import shutil
|
|
12
12
|
import socket
|
13
13
|
import sys
|
14
14
|
import tempfile
|
15
|
-
import time
|
16
|
-
from dataclasses import dataclass
|
17
15
|
from datetime import datetime
|
18
|
-
from
|
19
|
-
from
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
ItemsView,
|
26
|
-
Iterable,
|
27
|
-
Mapping,
|
28
|
-
Optional,
|
29
|
-
Sequence,
|
30
|
-
Set,
|
31
|
-
Tuple,
|
32
|
-
Union,
|
33
|
-
no_type_check,
|
34
|
-
)
|
35
|
-
from urllib.parse import quote, unquote, urlencode, urlparse, urlsplit
|
16
|
+
from typing import Any, Literal, Sequence
|
17
|
+
from urllib.parse import quote, unquote, urlencode
|
18
|
+
|
19
|
+
if sys.version_info >= (3, 11):
|
20
|
+
from typing import Self
|
21
|
+
else:
|
22
|
+
from typing_extensions import Self
|
36
23
|
|
37
24
|
from google.protobuf.wrappers_pb2 import BoolValue, DoubleValue, Int32Value, StringValue
|
25
|
+
from pydantic import (
|
26
|
+
BaseModel,
|
27
|
+
ConfigDict,
|
28
|
+
Field,
|
29
|
+
computed_field,
|
30
|
+
field_validator,
|
31
|
+
model_validator,
|
32
|
+
)
|
33
|
+
from pydantic_core import SchemaValidator, core_schema
|
38
34
|
|
39
35
|
import wandb
|
40
|
-
import
|
41
|
-
from wandb import util
|
36
|
+
from wandb import env, termwarn, util
|
42
37
|
from wandb.apis.internal import Api
|
43
38
|
from wandb.errors import UsageError
|
44
39
|
from wandb.proto import wandb_settings_pb2
|
45
|
-
from wandb.sdk.internal.system.env_probe_helpers import is_aws_lambda
|
46
|
-
from wandb.sdk.lib import credentials, filesystem
|
47
|
-
from wandb.sdk.lib._settings_toposort_generated import SETTINGS_TOPOLOGICALLY_SORTED
|
48
|
-
from wandb.sdk.lib.run_moment import RunMoment
|
49
|
-
from wandb.sdk.wandb_setup import _EarlyLogger
|
50
40
|
|
51
|
-
from .lib import apikey, ipython
|
41
|
+
from .lib import apikey, credentials, filesystem, ipython
|
52
42
|
from .lib.gitlib import GitRepo
|
43
|
+
from .lib.run_moment import RunMoment
|
53
44
|
from .lib.runid import generate_id
|
54
45
|
|
55
|
-
if sys.version_info >= (3, 8):
|
56
|
-
from typing import get_args, get_origin, get_type_hints
|
57
|
-
else:
|
58
|
-
from typing_extensions import get_args, get_origin, get_type_hints
|
59
|
-
|
60
46
|
|
61
|
-
|
62
|
-
"""
|
47
|
+
def _path_convert(*args: str) -> str:
|
48
|
+
"""Join path and apply os.path.expanduser to it."""
|
49
|
+
return os.path.expanduser(os.path.join(*args))
|
63
50
|
|
64
51
|
|
65
|
-
class
|
66
|
-
"""
|
67
|
-
|
68
|
-
|
69
|
-
class SettingsUnexpectedArgsError(UsageError):
|
70
|
-
"""Raised when unexpected arguments are passed to wandb.Settings()."""
|
71
|
-
|
72
|
-
|
73
|
-
def _get_wandb_dir(root_dir: str) -> str:
|
74
|
-
"""Get the full path to the wandb directory.
|
75
|
-
|
76
|
-
The setting exposed to users as `dir=` or `WANDB_DIR` is the `root_dir`.
|
77
|
-
We add the `__stage_dir__` to it to get the full `wandb_dir`
|
78
|
-
"""
|
79
|
-
# We use the hidden version if it already exists, otherwise non-hidden.
|
80
|
-
if os.path.exists(os.path.join(root_dir, ".wandb")):
|
81
|
-
__stage_dir__ = ".wandb" + os.sep
|
82
|
-
else:
|
83
|
-
__stage_dir__ = "wandb" + os.sep
|
84
|
-
|
85
|
-
path = os.path.join(root_dir, __stage_dir__)
|
86
|
-
if not os.access(root_dir or ".", os.W_OK):
|
87
|
-
wandb.termwarn(
|
88
|
-
f"Path {path} wasn't writable, using system temp directory.",
|
89
|
-
repeat=False,
|
90
|
-
)
|
91
|
-
path = os.path.join(tempfile.gettempdir(), __stage_dir__ or ("wandb" + os.sep))
|
92
|
-
|
93
|
-
return os.path.expanduser(path)
|
94
|
-
|
95
|
-
|
96
|
-
def _str_as_bool(val: Union[str, bool]) -> bool:
|
97
|
-
"""Parse a string as a bool."""
|
98
|
-
if isinstance(val, bool):
|
99
|
-
return val
|
100
|
-
try:
|
101
|
-
ret_val = bool(strtobool(str(val)))
|
102
|
-
return ret_val
|
103
|
-
except (AttributeError, ValueError):
|
104
|
-
pass
|
105
|
-
|
106
|
-
raise UsageError(f"Could not parse value {val} as a bool.")
|
107
|
-
|
108
|
-
|
109
|
-
def _str_as_json(val: Union[str, Dict[str, Any]]) -> Any:
|
110
|
-
"""Parse a string as a json object."""
|
111
|
-
if not isinstance(val, str):
|
112
|
-
return val
|
113
|
-
try:
|
114
|
-
return json.loads(val)
|
115
|
-
except (AttributeError, ValueError):
|
116
|
-
pass
|
117
|
-
|
118
|
-
raise UsageError(f"Could not parse value {val} as JSON.")
|
119
|
-
|
120
|
-
|
121
|
-
def _str_as_tuple(val: Union[str, Sequence[str]]) -> Tuple[str, ...]:
|
122
|
-
"""Parse a (potentially comma-separated) string as a tuple."""
|
123
|
-
if isinstance(val, str):
|
124
|
-
return tuple(val.split(","))
|
125
|
-
return tuple(val)
|
126
|
-
|
127
|
-
|
128
|
-
def _datetime_as_str(val: Union[datetime, str]) -> str:
|
129
|
-
"""Parse a datetime object as a string."""
|
130
|
-
if isinstance(val, datetime):
|
131
|
-
return datetime.strftime(val, "%Y%m%d_%H%M%S")
|
132
|
-
return val
|
133
|
-
|
134
|
-
|
135
|
-
def _redact_dict(
|
136
|
-
d: Dict[str, Any],
|
137
|
-
unsafe_keys: Union[Set[str], FrozenSet[str]] = frozenset({"api_key"}),
|
138
|
-
redact_str: str = "***REDACTED***",
|
139
|
-
) -> Dict[str, Any]:
|
140
|
-
"""Redact a dict of unsafe values specified by their key."""
|
141
|
-
if not d or unsafe_keys.isdisjoint(d):
|
142
|
-
return d
|
143
|
-
safe_dict = d.copy()
|
144
|
-
safe_dict.update({k: redact_str for k in unsafe_keys.intersection(d)})
|
145
|
-
return safe_dict
|
146
|
-
|
147
|
-
|
148
|
-
def _get_program() -> Optional[str]:
|
149
|
-
program = os.getenv(wandb.env.PROGRAM)
|
150
|
-
if program is not None:
|
151
|
-
return program
|
152
|
-
try:
|
153
|
-
import __main__
|
154
|
-
|
155
|
-
if __main__.__spec__ is None:
|
156
|
-
return __main__.__file__
|
157
|
-
# likely run as `python -m ...`
|
158
|
-
return f"-m {__main__.__spec__.name}"
|
159
|
-
except (ImportError, AttributeError):
|
160
|
-
return None
|
161
|
-
|
162
|
-
|
163
|
-
def _runmoment_preprocessor(val: Any) -> Optional[RunMoment]:
|
164
|
-
if isinstance(val, RunMoment) or val is None:
|
165
|
-
return val
|
166
|
-
elif isinstance(val, str):
|
167
|
-
return RunMoment.from_uri(val)
|
168
|
-
raise UsageError(f"Could not parse value {val} as a RunMoment.")
|
169
|
-
|
170
|
-
|
171
|
-
def _get_program_relpath(
|
172
|
-
program: str, root: Optional[str] = None, _logger: Optional[_EarlyLogger] = None
|
173
|
-
) -> Optional[str]:
|
174
|
-
if not program:
|
175
|
-
if _logger is not None:
|
176
|
-
_logger.warning("Empty program passed to get_program_relpath")
|
177
|
-
return None
|
178
|
-
|
179
|
-
root = root or os.getcwd()
|
180
|
-
if not root:
|
181
|
-
return None
|
52
|
+
class Settings(BaseModel, validate_assignment=True):
|
53
|
+
"""Settings for the W&B SDK."""
|
182
54
|
|
183
|
-
|
184
|
-
|
55
|
+
# Pydantic configuration.
|
56
|
+
model_config = ConfigDict(
|
57
|
+
extra="forbid", # throw an error if extra fields are provided
|
58
|
+
# validate_default=True, # validate default values
|
185
59
|
)
|
186
|
-
if os.path.exists(full_path_to_program):
|
187
|
-
relative_path = os.path.relpath(full_path_to_program, start=root)
|
188
|
-
if "../" in relative_path:
|
189
|
-
if _logger is not None:
|
190
|
-
_logger.warning(f"Could not save program above cwd: {program}")
|
191
|
-
return None
|
192
|
-
return relative_path
|
193
60
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
obj, (str, bytes, bytearray)
|
228
|
-
):
|
229
|
-
return False
|
230
|
-
|
231
|
-
if len(args) == 1 and args[0] != ...:
|
232
|
-
(item_type,) = args
|
233
|
-
for item in obj:
|
234
|
-
if not is_instance_recursive(item, item_type):
|
235
|
-
return False
|
236
|
-
elif len(args) == 2 and args[-1] == ...:
|
237
|
-
item_type = args[0]
|
238
|
-
for item in obj:
|
239
|
-
if not is_instance_recursive(item, item_type):
|
240
|
-
return False
|
241
|
-
elif len(args) == len(obj):
|
242
|
-
for item, item_type in zip(obj, args):
|
243
|
-
if not is_instance_recursive(item, item_type):
|
244
|
-
return False
|
245
|
-
else:
|
246
|
-
return False
|
247
|
-
|
248
|
-
return True
|
249
|
-
|
250
|
-
if issubclass(origin, collections.abc.Set):
|
251
|
-
if not isinstance(obj, collections.abc.Set):
|
252
|
-
return False
|
253
|
-
|
254
|
-
(item_type,) = args
|
255
|
-
for item in obj:
|
256
|
-
if not is_instance_recursive(item, item_type):
|
257
|
-
return False
|
258
|
-
|
259
|
-
return True
|
260
|
-
|
261
|
-
return False
|
262
|
-
|
263
|
-
|
264
|
-
@enum.unique
|
265
|
-
class Source(enum.IntEnum):
|
266
|
-
OVERRIDE: int = 0
|
267
|
-
BASE: int = 1 # todo: audit this
|
268
|
-
ORG: int = 2
|
269
|
-
ENTITY: int = 3
|
270
|
-
PROJECT: int = 4
|
271
|
-
USER: int = 5
|
272
|
-
SYSTEM: int = 6
|
273
|
-
WORKSPACE: int = 7
|
274
|
-
ENV: int = 8
|
275
|
-
SETUP: int = 9
|
276
|
-
LOGIN: int = 10
|
277
|
-
INIT: int = 11
|
278
|
-
SETTINGS: int = 12
|
279
|
-
ARGS: int = 13
|
280
|
-
RUN: int = 14
|
281
|
-
|
282
|
-
|
283
|
-
ConsoleValue = {
|
284
|
-
"auto",
|
285
|
-
"off",
|
286
|
-
"wrap",
|
287
|
-
"redirect",
|
288
|
-
# internal console states
|
289
|
-
"wrap_raw",
|
290
|
-
"wrap_emu",
|
291
|
-
}
|
292
|
-
|
293
|
-
|
294
|
-
@dataclass()
|
295
|
-
class SettingsData:
|
296
|
-
"""Settings for the W&B SDK."""
|
297
|
-
|
298
|
-
_args: Sequence[str]
|
299
|
-
_aws_lambda: bool
|
300
|
-
_cli_only_mode: bool # Avoid running any code specific for runs
|
301
|
-
_code_path_local: str
|
302
|
-
_colab: bool
|
303
|
-
# _config_dict: Config
|
304
|
-
_cuda: str
|
305
|
-
_disable_meta: bool # Do not collect system metadata
|
306
|
-
_disable_service: (
|
307
|
-
bool # Disable wandb-service, spin up internal process the old way
|
61
|
+
# Public settings.
|
62
|
+
|
63
|
+
# Flag to allow table artifacts to be synced in offline mode.
|
64
|
+
#
|
65
|
+
# To revert to the old behavior, set this to False.
|
66
|
+
allow_offline_artifacts: bool = True
|
67
|
+
allow_val_change: bool = False
|
68
|
+
anonymous: Literal["allow", "must", "never"] | None = None
|
69
|
+
# The W&B API key.
|
70
|
+
api_key: str | None = None
|
71
|
+
azure_account_url_to_access_key: dict[str, str] | None = None
|
72
|
+
# The URL of the W&B backend, used for GraphQL and filestream operations.
|
73
|
+
base_url: str = "https://api.wandb.ai"
|
74
|
+
code_dir: str | None = None
|
75
|
+
config_paths: Sequence[str] | None = None
|
76
|
+
# The type of console capture to be applied. Possible values are:
|
77
|
+
# "auto" - Automatically selects the console capture method based on the
|
78
|
+
# system environment and settings.
|
79
|
+
#
|
80
|
+
# "off" - Disables console capture.
|
81
|
+
#
|
82
|
+
# "redirect" - Redirects low-level file descriptors for capturing output.
|
83
|
+
#
|
84
|
+
# "wrap" - Overrides the write methods of sys.stdout/sys.stderr. Will be
|
85
|
+
# mapped to either "wrap_raw" or "wrap_emu" based on the state of the system.
|
86
|
+
#
|
87
|
+
# "wrap_raw" - Same as "wrap" but captures raw output directly instead of
|
88
|
+
# through an emulator.
|
89
|
+
#
|
90
|
+
# "wrap_emu" - Same as "wrap" but captures output through an emulator.
|
91
|
+
console: Literal["auto", "off", "wrap", "redirect", "wrap_raw", "wrap_emu"] = Field(
|
92
|
+
default="auto",
|
93
|
+
validate_default=True,
|
308
94
|
)
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
_executable: str
|
315
|
-
_extra_http_headers: Mapping[str, str]
|
316
|
-
_file_stream_max_bytes: int # max size for filestream requests in core
|
317
|
-
_file_stream_transmit_interval: float # tx interval for filestream requests in core
|
318
|
-
# file stream retry client configuration
|
319
|
-
_file_stream_retry_max: int # max number of retries
|
320
|
-
_file_stream_retry_wait_min_seconds: float # min wait time between retries
|
321
|
-
_file_stream_retry_wait_max_seconds: float # max wait time between retries
|
322
|
-
_file_stream_timeout_seconds: float # timeout for individual HTTP requests
|
323
|
-
# file transfer retry client configuration
|
324
|
-
_file_transfer_retry_max: int
|
325
|
-
_file_transfer_retry_wait_min_seconds: float
|
326
|
-
_file_transfer_retry_wait_max_seconds: float
|
327
|
-
_file_transfer_timeout_seconds: float
|
328
|
-
_flow_control_custom: bool
|
329
|
-
_flow_control_disabled: bool
|
330
|
-
# graphql retry client configuration
|
331
|
-
_graphql_retry_max: int
|
332
|
-
_graphql_retry_wait_min_seconds: float
|
333
|
-
_graphql_retry_wait_max_seconds: float
|
334
|
-
_graphql_timeout_seconds: float
|
335
|
-
_internal_check_process: float
|
336
|
-
_internal_queue_timeout: float
|
337
|
-
_ipython: bool
|
338
|
-
_jupyter: bool
|
339
|
-
_jupyter_name: str
|
340
|
-
_jupyter_path: str
|
341
|
-
_jupyter_root: str
|
342
|
-
_kaggle: bool
|
343
|
-
_live_policy_rate_limit: int
|
344
|
-
_live_policy_wait_time: int
|
345
|
-
_log_level: int
|
346
|
-
_network_buffer: int
|
347
|
-
_noop: bool
|
348
|
-
_notebook: bool
|
349
|
-
_offline: bool
|
350
|
-
_sync: bool
|
351
|
-
_os: str
|
352
|
-
_platform: str
|
353
|
-
_proxies: Mapping[
|
354
|
-
str, str
|
355
|
-
] # custom proxy servers for the requests to W&B [scheme -> url]
|
356
|
-
_python: str
|
357
|
-
_runqueue_item_id: str
|
358
|
-
_require_legacy_service: bool
|
359
|
-
_save_requirements: bool
|
360
|
-
_service_transport: str
|
361
|
-
_service_wait: float
|
362
|
-
_shared: bool
|
363
|
-
_start_datetime: str
|
364
|
-
_start_time: float
|
365
|
-
_stats_pid: int # (internal) base pid for system stats
|
366
|
-
_stats_sampling_interval: float # sampling interval for system stats
|
367
|
-
_stats_sample_rate_seconds: float # badly-named sampling interval, deprecated
|
368
|
-
_stats_samples_to_average: (
|
369
|
-
int # number of samples to average before reporting, deprecated
|
95
|
+
# Whether to produce multipart console log files.
|
96
|
+
console_multipart: bool = False
|
97
|
+
# Path to file for writing temporary access tokens.
|
98
|
+
credentials_file: str = Field(
|
99
|
+
default_factory=lambda: str(credentials.DEFAULT_WANDB_CREDENTIALS_FILE)
|
370
100
|
)
|
371
|
-
|
372
|
-
|
101
|
+
# Whether to disable code saving.
|
102
|
+
disable_code: bool = False
|
103
|
+
# Whether to disable capturing the git state.
|
104
|
+
disable_git: bool = False
|
105
|
+
# Whether to disable the creation of a job artifact for W&B Launch.
|
106
|
+
disable_job_creation: bool = True
|
107
|
+
# The Docker image used to execute the script.
|
108
|
+
docker: str | None = None
|
109
|
+
# The email address of the user.
|
110
|
+
email: str | None = None
|
111
|
+
# The W&B entity, like a user or a team.
|
112
|
+
entity: str | None = None
|
113
|
+
force: bool = False
|
114
|
+
fork_from: RunMoment | None = None
|
115
|
+
git_commit: str | None = None
|
116
|
+
git_remote: str = "origin"
|
117
|
+
git_remote_url: str | None = None
|
118
|
+
git_root: str | None = None
|
119
|
+
heartbeat_seconds: int = 30
|
120
|
+
host: str | None = None
|
121
|
+
# The custom proxy servers for http requests to W&B.
|
122
|
+
http_proxy: str | None = None
|
123
|
+
# The custom proxy servers for https requests to W&B.
|
124
|
+
https_proxy: str | None = None
|
125
|
+
# Path to file containing an identity token (JWT) for authentication.
|
126
|
+
identity_token_file: str | None = None
|
127
|
+
# Unix glob patterns relative to `files_dir` to not upload.
|
128
|
+
ignore_globs: tuple[str, ...] = ()
|
129
|
+
init_timeout: float = 90.0
|
130
|
+
job_name: str | None = None
|
131
|
+
job_source: Literal["repo", "artifact", "image"] | None = None
|
132
|
+
label_disable: bool = False
|
133
|
+
launch: bool = False
|
134
|
+
launch_config_path: str | None = None
|
135
|
+
login_timeout: float | None = None
|
136
|
+
mode: Literal["online", "offline", "dryrun", "disabled", "run", "shared"] = Field(
|
137
|
+
default="online",
|
138
|
+
validate_default=True,
|
373
139
|
)
|
374
|
-
|
375
|
-
|
140
|
+
notebook_name: str | None = None
|
141
|
+
# Path to the script that created the run, if available.
|
142
|
+
program: str | None = None
|
143
|
+
# The absolute path from the root repository directory to the script that
|
144
|
+
# created the run.
|
145
|
+
#
|
146
|
+
# Root repository directory is defined as the directory containing the
|
147
|
+
# .git directory, if it exists. Otherwise, it's the current working directory.
|
148
|
+
program_abspath: str | None = None
|
149
|
+
program_relpath: str | None = None
|
150
|
+
# The W&B project ID.
|
151
|
+
project: str | None = None
|
152
|
+
quiet: bool = False
|
153
|
+
reinit: bool = False
|
154
|
+
relogin: bool = False
|
155
|
+
# Specifies the resume behavior for the run. The available options are:
|
156
|
+
#
|
157
|
+
# "must": Resumes from an existing run with the same ID. If no such run exists,
|
158
|
+
# it will result in failure.
|
159
|
+
#
|
160
|
+
# "allow": Attempts to resume from an existing run with the same ID. If none is
|
161
|
+
# found, a new run will be created.
|
162
|
+
#
|
163
|
+
# "never": Always starts a new run. If a run with the same ID already exists,
|
164
|
+
# it will result in failure.
|
165
|
+
#
|
166
|
+
# "auto": Automatically resumes from the most recent failed run on the same
|
167
|
+
# machine.
|
168
|
+
resume: Literal["allow", "must", "never", "auto"] | None = None
|
169
|
+
resume_from: RunMoment | None = None
|
170
|
+
# Indication from the server about the state of the run.
|
171
|
+
#
|
172
|
+
# This is different from resume, a user provided flag.
|
173
|
+
resumed: bool = False
|
174
|
+
# The root directory that will be used to derive other paths,
|
175
|
+
# such as the wandb directory, and the run directory.
|
176
|
+
root_dir: str = Field(default_factory=lambda: os.path.abspath(os.getcwd()))
|
177
|
+
run_group: str | None = None
|
178
|
+
# The ID of the run.
|
179
|
+
run_id: str | None = None
|
180
|
+
run_job_type: str | None = None
|
181
|
+
run_name: str | None = None
|
182
|
+
run_notes: str | None = None
|
183
|
+
run_tags: tuple[str, ...] | None = None
|
184
|
+
sagemaker_disable: bool = False
|
185
|
+
save_code: bool | None = None
|
186
|
+
settings_system: str = Field(
|
187
|
+
default_factory=lambda: _path_convert(
|
188
|
+
os.path.join("~", ".config", "wandb", "settings")
|
189
|
+
)
|
190
|
+
)
|
191
|
+
show_colors: bool | None = None
|
192
|
+
show_emoji: bool | None = None
|
193
|
+
show_errors: bool = True
|
194
|
+
show_info: bool = True
|
195
|
+
show_warnings: bool = True
|
196
|
+
silent: bool = False
|
197
|
+
start_method: str | None = None
|
198
|
+
strict: bool | None = None
|
199
|
+
summary_timeout: int = 60
|
200
|
+
summary_warnings: int = 5 # TODO: kill this with fire
|
201
|
+
sweep_id: str | None = None
|
202
|
+
sweep_param_path: str | None = None
|
203
|
+
symlink: bool = Field(
|
204
|
+
default_factory=lambda: False if platform.system() == "Windows" else True
|
376
205
|
)
|
377
|
-
|
378
|
-
|
206
|
+
sync_tensorboard: bool | None = None
|
207
|
+
table_raise_on_max_row_limit_exceeded: bool = False
|
208
|
+
username: str | None = None
|
209
|
+
|
210
|
+
# Internal settings.
|
211
|
+
#
|
212
|
+
# These are typically not meant to be set by the user and should not be considered
|
213
|
+
# a part of the public API as they may change or be removed in future versions.
|
214
|
+
|
215
|
+
# CLI mode.
|
216
|
+
x_cli_only_mode: bool = False
|
217
|
+
# Disable the collection of system metadata.
|
218
|
+
x_disable_meta: bool = False
|
219
|
+
# Pre-wandb-core, this setting was used to disable the (now legacy) wandb service.
|
220
|
+
#
|
221
|
+
# TODO: this is deprecated and will be removed in future versions.
|
222
|
+
x_disable_service: bool = False
|
223
|
+
# Do not use setproctitle for internal process in legacy service.
|
224
|
+
x_disable_setproctitle: bool = False
|
225
|
+
# Disable system metrics collection.
|
226
|
+
x_disable_stats: bool = False
|
227
|
+
# Disable check for latest version of wandb, from PyPI.
|
228
|
+
x_disable_update_check: bool = False
|
229
|
+
# Prevent early viewer query.
|
230
|
+
x_disable_viewer: bool = False
|
231
|
+
# Disable automatic machine info collection.
|
232
|
+
x_disable_machine_info: bool = False
|
233
|
+
# Python executable
|
234
|
+
x_executable: str | None = None
|
235
|
+
# Additional headers to add to all outgoing HTTP requests.
|
236
|
+
x_extra_http_headers: dict[str, str] | None = None
|
237
|
+
# An approximate maximum request size for the filestream API.
|
238
|
+
#
|
239
|
+
# This applies when wandb-core is enabled. Its purpose is to prevent
|
240
|
+
# HTTP requests from failing due to containing too much data.
|
241
|
+
#
|
242
|
+
# This number is approximate: requests will be slightly larger.
|
243
|
+
x_file_stream_max_bytes: int | None = None
|
244
|
+
# Max line length for filestream jsonl files.
|
245
|
+
x_file_stream_max_line_bytes: int | None = None
|
246
|
+
# Interval in seconds between filestream transmissions.
|
247
|
+
x_file_stream_transmit_interval: float | None = None
|
248
|
+
# Filestream retry client configuration.
|
249
|
+
# max number of retries
|
250
|
+
x_file_stream_retry_max: int | None = None
|
251
|
+
# min wait time between retries
|
252
|
+
x_file_stream_retry_wait_min_seconds: float | None = None
|
253
|
+
# max wait time between retries
|
254
|
+
x_file_stream_retry_wait_max_seconds: float | None = None
|
255
|
+
# timeout for individual HTTP requests
|
256
|
+
x_file_stream_timeout_seconds: float | None = None
|
257
|
+
# file transfer retry client configuration
|
258
|
+
x_file_transfer_retry_max: int | None = None
|
259
|
+
x_file_transfer_retry_wait_min_seconds: float | None = None
|
260
|
+
x_file_transfer_retry_wait_max_seconds: float | None = None
|
261
|
+
x_file_transfer_timeout_seconds: float | None = None
|
262
|
+
# override setting for the computed files_dir
|
263
|
+
x_files_dir: str | None = None
|
264
|
+
# flow control configuration for file stream
|
265
|
+
x_flow_control_custom: bool | None = None
|
266
|
+
x_flow_control_disabled: bool | None = None
|
267
|
+
# graphql retry client configuration
|
268
|
+
x_graphql_retry_max: int | None = None
|
269
|
+
x_graphql_retry_wait_min_seconds: float | None = None
|
270
|
+
x_graphql_retry_wait_max_seconds: float | None = None
|
271
|
+
x_graphql_timeout_seconds: float | None = None
|
272
|
+
x_internal_check_process: float = 8.0
|
273
|
+
x_jupyter_name: str | None = None
|
274
|
+
x_jupyter_path: str | None = None
|
275
|
+
x_jupyter_root: str | None = None
|
276
|
+
# Label to assign to system metrics and console logs collected for the run
|
277
|
+
# to group by on the frontend. Can be used to distinguish data from different
|
278
|
+
# nodes in a distributed training job.
|
279
|
+
x_label: str | None = None
|
280
|
+
x_live_policy_rate_limit: int | None = None
|
281
|
+
x_live_policy_wait_time: int | None = None
|
282
|
+
x_log_level: int = logging.INFO
|
283
|
+
x_network_buffer: int | None = None
|
284
|
+
# Determines whether to save internal wandb files and metadata.
|
285
|
+
# In a distributed setting, this is useful for avoiding file overwrites on secondary nodes
|
286
|
+
# when only system metrics and logs are needed, as the primary node handles the main logging.
|
287
|
+
x_primary_node: bool = True
|
288
|
+
# [deprecated, use http(s)_proxy] custom proxy servers for the requests to W&B
|
289
|
+
# [scheme -> url].
|
290
|
+
x_proxies: dict[str, str] | None = None
|
291
|
+
x_runqueue_item_id: str | None = None
|
292
|
+
x_require_legacy_service: bool = False
|
293
|
+
x_save_requirements: bool = True
|
294
|
+
x_service_transport: str | None = None
|
295
|
+
x_service_wait: float = 30.0
|
296
|
+
x_show_operation_stats: bool = False
|
297
|
+
# The start time of the run in seconds since the Unix epoch.
|
298
|
+
x_start_time: float | None = None
|
299
|
+
# PID of the process that started the wandb-core process to collect system stats for.
|
300
|
+
x_stats_pid: int = os.getpid()
|
301
|
+
# Sampling interval for the system monitor in seconds.
|
302
|
+
x_stats_sampling_interval: float = Field(default=10.0)
|
303
|
+
# Path to store the default config file for the neuron-monitor tool
|
304
|
+
# used to monitor AWS Trainium devices.
|
305
|
+
x_stats_neuron_monitor_config_path: str | None = None
|
306
|
+
# Open metrics endpoint names and urls.
|
307
|
+
x_stats_open_metrics_endpoints: dict[str, str] | None = None
|
308
|
+
# Filter to apply to metrics collected from OpenMetrics endpoints.
|
309
|
+
# Supports two formats:
|
379
310
|
# - {"metric regex pattern, including endpoint name as prefix": {"label": "label value regex pattern"}}
|
380
311
|
# - ("metric regex pattern 1", "metric regex pattern 2", ...)
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
host: str
|
419
|
-
http_proxy: str # proxy server for the http requests to W&B
|
420
|
-
https_proxy: str # proxy server for the https requests to W&B
|
421
|
-
identity_token_file: str # file path to supply a jwt for authentication
|
422
|
-
ignore_globs: Tuple[str]
|
423
|
-
init_timeout: float
|
424
|
-
is_local: bool
|
425
|
-
job_name: str
|
426
|
-
job_source: str
|
427
|
-
label_disable: bool
|
428
|
-
launch: bool
|
429
|
-
launch_config_path: str
|
430
|
-
log_dir: str
|
431
|
-
log_internal: str
|
432
|
-
log_symlink_internal: str
|
433
|
-
log_symlink_user: str
|
434
|
-
log_user: str
|
435
|
-
login_timeout: float
|
436
|
-
# magic: Union[str, bool, dict] # never used in code, deprecated
|
437
|
-
mode: str
|
438
|
-
notebook_name: str
|
439
|
-
program: str
|
440
|
-
program_abspath: str
|
441
|
-
program_relpath: str
|
442
|
-
project: str
|
443
|
-
project_url: str
|
444
|
-
quiet: bool
|
445
|
-
reinit: bool
|
446
|
-
relogin: bool
|
447
|
-
# todo: add a preprocessing step to convert this to string
|
448
|
-
resume: Union[str, bool]
|
449
|
-
resume_fname: str
|
450
|
-
resumed: bool # indication from the server about the state of the run (different from resume - user provided flag)
|
451
|
-
root_dir: str
|
452
|
-
run_group: str
|
453
|
-
run_id: str
|
454
|
-
run_job_type: str
|
455
|
-
run_mode: str
|
456
|
-
run_name: str
|
457
|
-
run_notes: str
|
458
|
-
run_tags: Tuple[str]
|
459
|
-
run_url: str
|
460
|
-
sagemaker_disable: bool
|
461
|
-
save_code: bool
|
462
|
-
settings_system: str
|
463
|
-
settings_workspace: str
|
464
|
-
show_colors: bool
|
465
|
-
show_emoji: bool
|
466
|
-
show_errors: bool
|
467
|
-
show_info: bool
|
468
|
-
show_warnings: bool
|
469
|
-
silent: bool
|
470
|
-
start_method: str
|
471
|
-
strict: bool
|
472
|
-
summary_errors: int
|
473
|
-
summary_timeout: int
|
474
|
-
summary_warnings: int
|
475
|
-
sweep_id: str
|
476
|
-
sweep_param_path: str
|
477
|
-
sweep_url: str
|
478
|
-
symlink: bool
|
479
|
-
sync_dir: str
|
480
|
-
sync_file: str
|
481
|
-
sync_symlink_latest: str
|
482
|
-
table_raise_on_max_row_limit_exceeded: bool
|
483
|
-
timespec: str
|
484
|
-
tmp_dir: str
|
485
|
-
username: str
|
486
|
-
wandb_dir: str
|
487
|
-
|
488
|
-
|
489
|
-
class Property:
|
490
|
-
"""A class to represent attributes (individual settings) of the Settings object.
|
491
|
-
|
492
|
-
- Encapsulates the logic of how to preprocess and validate values of settings
|
493
|
-
throughout the lifetime of a class instance.
|
494
|
-
- Allows for runtime modification of settings with hooks, e.g. in the case when
|
495
|
-
a setting depends on another setting.
|
496
|
-
- The update() method is used to update the value of a setting.
|
497
|
-
- The `is_policy` attribute determines the source priority when updating the property value.
|
498
|
-
E.g. if `is_policy` is True, the smallest `Source` value takes precedence.
|
499
|
-
"""
|
500
|
-
|
501
|
-
def __init__( # pylint: disable=unused-argument
|
502
|
-
self,
|
503
|
-
name: str,
|
504
|
-
value: Optional[Any] = None,
|
505
|
-
preprocessor: Union[Callable, Sequence[Callable], None] = None,
|
506
|
-
# validators allow programming by contract
|
507
|
-
validator: Union[Callable, Sequence[Callable], None] = None,
|
508
|
-
# runtime converter (hook): properties can be e.g. tied to other properties
|
509
|
-
hook: Union[Callable, Sequence[Callable], None] = None,
|
510
|
-
# always apply hook even if value is None. can be used to replace @property's
|
511
|
-
auto_hook: bool = False,
|
512
|
-
is_policy: bool = False,
|
513
|
-
frozen: bool = False,
|
514
|
-
source: int = Source.BASE,
|
515
|
-
**kwargs: Any,
|
516
|
-
):
|
517
|
-
self.name = name
|
518
|
-
self._preprocessor = preprocessor
|
519
|
-
self._validator = validator
|
520
|
-
self._hook = hook
|
521
|
-
self._auto_hook = auto_hook
|
522
|
-
self._is_policy = is_policy
|
523
|
-
self._source = source
|
524
|
-
|
525
|
-
# preprocess and validate value
|
526
|
-
self._value = self._validate(self._preprocess(value))
|
527
|
-
|
528
|
-
self.__frozen = frozen
|
312
|
+
x_stats_open_metrics_filters: dict[str, dict[str, str]] | Sequence[str] | None = (
|
313
|
+
None
|
314
|
+
)
|
315
|
+
# HTTP headers to add to OpenMetrics requests.
|
316
|
+
x_stats_open_metrics_http_headers: dict[str, str] | None = None
|
317
|
+
# System paths to monitor for disk usage.
|
318
|
+
x_stats_disk_paths: Sequence[str] | None = Field(
|
319
|
+
default_factory=lambda: ("/", "/System/Volumes/Data")
|
320
|
+
if platform.system() == "Darwin"
|
321
|
+
else ("/",)
|
322
|
+
)
|
323
|
+
# Number of system metric samples to buffer in memory in the wandb-core process.
|
324
|
+
# Can be accessed via run._system_metrics.
|
325
|
+
x_stats_buffer_size: int = 0
|
326
|
+
# Flag to indicate whether we are syncing a run from the transaction log.
|
327
|
+
x_sync: bool = False
|
328
|
+
# Controls whether this process can update the run's final state (finished/failed) on the server.
|
329
|
+
# Set to False in distributed training when only the main process should determine the final state.
|
330
|
+
x_update_finish_state: bool = True
|
331
|
+
|
332
|
+
# Model validator to catch legacy settings.
|
333
|
+
@model_validator(mode="before")
|
334
|
+
@classmethod
|
335
|
+
def catch_private_settings(cls, values):
|
336
|
+
"""Check if a private field is provided and assign to the corrsponding public one.
|
337
|
+
|
338
|
+
This is a compatibility layer to handle previous versions of the settings.
|
339
|
+
"""
|
340
|
+
new_values = {}
|
341
|
+
for key in values:
|
342
|
+
# Internal settings are prefixed with "x_" instead of "_"
|
343
|
+
# as Pydantic does not allow "_" in field names.
|
344
|
+
if key.startswith("_"):
|
345
|
+
new_values["x" + key] = values[key]
|
346
|
+
else:
|
347
|
+
new_values[key] = values[key]
|
348
|
+
return new_values
|
529
349
|
|
530
|
-
@
|
531
|
-
def
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
350
|
+
@model_validator(mode="after")
|
351
|
+
def validate_mutual_exclusion_of_branching_args(self) -> Self:
|
352
|
+
if (
|
353
|
+
sum(
|
354
|
+
o is not None
|
355
|
+
for o in [
|
356
|
+
self.fork_from,
|
357
|
+
self.resume,
|
358
|
+
self.resume_from,
|
359
|
+
]
|
360
|
+
)
|
361
|
+
> 1
|
362
|
+
):
|
363
|
+
raise ValueError(
|
364
|
+
"`fork_from`, `resume`, or `resume_from` are mutually exclusive. "
|
365
|
+
"Please specify only one of them."
|
366
|
+
)
|
367
|
+
return self
|
539
368
|
|
540
|
-
|
541
|
-
def is_policy(self) -> bool:
|
542
|
-
return self._is_policy
|
369
|
+
# Field validators.
|
543
370
|
|
544
|
-
@
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
if callable(self._preprocessor)
|
553
|
-
else self._preprocessor
|
371
|
+
@field_validator("x_disable_service", mode="after")
|
372
|
+
@classmethod
|
373
|
+
def validate_disable_service(cls, value):
|
374
|
+
if value:
|
375
|
+
termwarn(
|
376
|
+
"Disabling the wandb service is deprecated as of version 0.18.0 "
|
377
|
+
"and will be removed in future versions. ",
|
378
|
+
repeat=False,
|
554
379
|
)
|
555
|
-
for p in _preprocessor:
|
556
|
-
try:
|
557
|
-
value = p(value)
|
558
|
-
except Exception:
|
559
|
-
raise SettingsPreprocessingError(
|
560
|
-
f"Unable to preprocess value for property {self.name}: {value}."
|
561
|
-
)
|
562
380
|
return value
|
563
381
|
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
)
|
569
|
-
for v in _validator:
|
570
|
-
if not v(value):
|
571
|
-
# failed validation will likely cause a downstream error
|
572
|
-
# when trying to convert to protobuf, so we raise a hard error
|
573
|
-
raise SettingsValidationError(
|
574
|
-
f"Invalid value for property {self.name}: {value}."
|
575
|
-
)
|
382
|
+
@field_validator("api_key", mode="after")
|
383
|
+
@classmethod
|
384
|
+
def validate_api_key(cls, value):
|
385
|
+
if value is not None and (len(value) > len(value.strip())):
|
386
|
+
raise UsageError("API key cannot start or end with whitespace")
|
576
387
|
return value
|
577
388
|
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
# -
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
(source == Source.OVERRIDE)
|
588
|
-
or (
|
589
|
-
self._is_policy
|
590
|
-
and self._source != Source.OVERRIDE
|
591
|
-
and source <= self._source
|
592
|
-
)
|
593
|
-
or (
|
594
|
-
not self._is_policy
|
595
|
-
and self._source != Source.OVERRIDE
|
596
|
-
and source >= self._source
|
389
|
+
@field_validator("base_url", mode="after")
|
390
|
+
@classmethod
|
391
|
+
def validate_base_url(cls, value):
|
392
|
+
cls.validate_url(value)
|
393
|
+
# wandb.ai-specific checks
|
394
|
+
if re.match(r".*wandb\.ai[^\.]*$", value) and "api." not in value:
|
395
|
+
# user might guess app.wandb.ai or wandb.ai is the default cloud server
|
396
|
+
raise ValueError(
|
397
|
+
f"{value} is not a valid server address, did you mean https://api.wandb.ai?"
|
597
398
|
)
|
399
|
+
elif re.match(r".*wandb\.ai[^\.]*$", value) and not value.startswith("https"):
|
400
|
+
raise ValueError("http is not secure, please use https://api.wandb.ai")
|
401
|
+
return value.rstrip("/")
|
402
|
+
|
403
|
+
@field_validator("console", mode="after")
|
404
|
+
@classmethod
|
405
|
+
def validate_console(cls, value, info):
|
406
|
+
if value != "auto":
|
407
|
+
return value
|
408
|
+
if (
|
409
|
+
ipython.in_jupyter()
|
410
|
+
or (info.data.get("start_method") == "thread")
|
411
|
+
or not info.data.get("disable_service")
|
412
|
+
or platform.system() == "Windows"
|
598
413
|
):
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
def __setattr__(self, key: str, value: Any) -> None:
|
604
|
-
if "_Property__frozen" in self.__dict__ and self.__frozen:
|
605
|
-
raise TypeError(f"Property object {self.name} is frozen")
|
606
|
-
if key == "value":
|
607
|
-
raise AttributeError("Use update() to update property value")
|
608
|
-
self.__dict__[key] = value
|
609
|
-
|
610
|
-
def __str__(self) -> str:
|
611
|
-
return f"{self.value!r}" if isinstance(self.value, str) else f"{self.value}"
|
612
|
-
|
613
|
-
def __repr__(self) -> str:
|
614
|
-
return (
|
615
|
-
f"<Property {self.name}: value={self.value} "
|
616
|
-
f"_value={self._value} source={self._source} is_policy={self._is_policy}>"
|
617
|
-
)
|
618
|
-
# return f"<Property {self.name}: value={self.value}>"
|
619
|
-
# return self.__dict__.__repr__()
|
414
|
+
value = "wrap"
|
415
|
+
else:
|
416
|
+
value = "redirect"
|
417
|
+
return value
|
620
418
|
|
419
|
+
@field_validator("x_file_stream_max_line_bytes", mode="after")
|
420
|
+
@classmethod
|
421
|
+
def validate_file_stream_max_line_bytes(cls, value):
|
422
|
+
if value is not None and value < 1:
|
423
|
+
raise ValueError("File stream max line bytes must be greater than 0")
|
424
|
+
return value
|
621
425
|
|
622
|
-
|
623
|
-
|
426
|
+
@field_validator("fork_from", mode="before")
|
427
|
+
@classmethod
|
428
|
+
def validate_fork_from(cls, value, info) -> RunMoment | None:
|
429
|
+
run_moment = cls._runmoment_preprocessor(value)
|
430
|
+
if run_moment and info.data.get("run_id") == run_moment.run:
|
431
|
+
raise ValueError(
|
432
|
+
"Provided `run_id` is the same as the run to `fork_from`. "
|
433
|
+
"Please provide a different `run_id` or remove the `run_id` argument. "
|
434
|
+
"If you want to rewind the current run, please use `resume_from` instead."
|
435
|
+
)
|
436
|
+
return run_moment
|
624
437
|
|
625
|
-
|
626
|
-
|
438
|
+
@field_validator("http_proxy", mode="after")
|
439
|
+
@classmethod
|
440
|
+
def validate_http_proxy(cls, value):
|
441
|
+
if value is None:
|
442
|
+
return None
|
443
|
+
cls.validate_url(value)
|
444
|
+
return value.rstrip("/")
|
627
445
|
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
},
|
636
|
-
_code_path_local={
|
637
|
-
"hook": lambda _: _get_program_relpath(self.program),
|
638
|
-
"auto_hook": True,
|
639
|
-
},
|
640
|
-
_colab={
|
641
|
-
"hook": lambda _: "google.colab" in sys.modules,
|
642
|
-
"auto_hook": True,
|
643
|
-
},
|
644
|
-
_disable_machine_info={
|
645
|
-
"value": False,
|
646
|
-
"preprocessor": _str_as_bool,
|
647
|
-
},
|
648
|
-
_disable_meta={
|
649
|
-
"value": False,
|
650
|
-
"preprocessor": _str_as_bool,
|
651
|
-
"hook": lambda x: self._disable_machine_info or x,
|
652
|
-
},
|
653
|
-
_disable_service={
|
654
|
-
"value": False,
|
655
|
-
"preprocessor": self._process_disable_service,
|
656
|
-
"is_policy": True,
|
657
|
-
},
|
658
|
-
_disable_setproctitle={"value": False, "preprocessor": _str_as_bool},
|
659
|
-
_disable_stats={
|
660
|
-
"value": False,
|
661
|
-
"preprocessor": _str_as_bool,
|
662
|
-
"hook": lambda x: self._disable_machine_info or x,
|
663
|
-
},
|
664
|
-
_disable_update_check={"preprocessor": _str_as_bool},
|
665
|
-
_disable_viewer={"preprocessor": _str_as_bool},
|
666
|
-
_extra_http_headers={"preprocessor": _str_as_json},
|
667
|
-
_file_stream_max_bytes={"preprocessor": int},
|
668
|
-
_file_stream_transmit_interval={"preprocessor": float},
|
669
|
-
_file_stream_retry_max={"preprocessor": int},
|
670
|
-
_file_stream_retry_wait_min_seconds={"preprocessor": float},
|
671
|
-
_file_stream_retry_wait_max_seconds={"preprocessor": float},
|
672
|
-
_file_stream_timeout_seconds={"preprocessor": float},
|
673
|
-
_file_transfer_retry_max={"preprocessor": int},
|
674
|
-
_file_transfer_retry_wait_min_seconds={"preprocessor": float},
|
675
|
-
_file_transfer_retry_wait_max_seconds={"preprocessor": float},
|
676
|
-
_file_transfer_timeout_seconds={"preprocessor": float},
|
677
|
-
_flow_control_disabled={
|
678
|
-
"hook": lambda _: self._network_buffer == 0,
|
679
|
-
"auto_hook": True,
|
680
|
-
},
|
681
|
-
_flow_control_custom={
|
682
|
-
"hook": lambda _: bool(self._network_buffer),
|
683
|
-
"auto_hook": True,
|
684
|
-
},
|
685
|
-
_graphql_retry_max={"preprocessor": int},
|
686
|
-
_graphql_retry_wait_min_seconds={"preprocessor": float},
|
687
|
-
_graphql_retry_wait_max_seconds={"preprocessor": float},
|
688
|
-
_graphql_timeout_seconds={"preprocessor": float},
|
689
|
-
_internal_check_process={"value": 8, "preprocessor": float},
|
690
|
-
_internal_queue_timeout={"value": 2, "preprocessor": float},
|
691
|
-
_ipython={
|
692
|
-
"hook": lambda _: ipython.in_ipython(),
|
693
|
-
"auto_hook": True,
|
694
|
-
},
|
695
|
-
_jupyter={
|
696
|
-
"hook": lambda _: ipython.in_jupyter(),
|
697
|
-
"auto_hook": True,
|
698
|
-
},
|
699
|
-
_kaggle={"hook": lambda _: util._is_likely_kaggle(), "auto_hook": True},
|
700
|
-
_log_level={"value": logging.DEBUG},
|
701
|
-
_network_buffer={"preprocessor": int},
|
702
|
-
_noop={"hook": lambda _: self.mode == "disabled", "auto_hook": True},
|
703
|
-
_notebook={
|
704
|
-
"hook": lambda _: self._ipython
|
705
|
-
or self._jupyter
|
706
|
-
or self._colab
|
707
|
-
or self._kaggle,
|
708
|
-
"auto_hook": True,
|
709
|
-
},
|
710
|
-
_offline={
|
711
|
-
"hook": (
|
712
|
-
lambda _: True
|
713
|
-
if self.disabled or (self.mode in ("dryrun", "offline"))
|
714
|
-
else False
|
715
|
-
),
|
716
|
-
"auto_hook": True,
|
717
|
-
},
|
718
|
-
_platform={"value": util.get_platform_name()},
|
719
|
-
_proxies={
|
720
|
-
# TODO: deprecate and ask the user to use http_proxy and https_proxy instead
|
721
|
-
"preprocessor": _str_as_json,
|
722
|
-
},
|
723
|
-
_require_legacy_service={"value": False, "preprocessor": _str_as_bool},
|
724
|
-
_save_requirements={"value": True, "preprocessor": _str_as_bool},
|
725
|
-
_service_wait={
|
726
|
-
"value": 30,
|
727
|
-
"preprocessor": float,
|
728
|
-
"validator": self._validate__service_wait,
|
729
|
-
},
|
730
|
-
_shared={
|
731
|
-
"hook": lambda _: self.mode == "shared",
|
732
|
-
"auto_hook": True,
|
733
|
-
},
|
734
|
-
_start_datetime={"preprocessor": _datetime_as_str},
|
735
|
-
_stats_sampling_interval={
|
736
|
-
"value": 10.0,
|
737
|
-
"preprocessor": float,
|
738
|
-
"validator": self._validate__stats_sampling_interval,
|
739
|
-
},
|
740
|
-
_stats_sample_rate_seconds={
|
741
|
-
"value": 2.0,
|
742
|
-
"preprocessor": float,
|
743
|
-
"validator": self._validate__stats_sample_rate_seconds,
|
744
|
-
},
|
745
|
-
_stats_samples_to_average={
|
746
|
-
"value": 15,
|
747
|
-
"preprocessor": int,
|
748
|
-
"validator": self._validate__stats_samples_to_average,
|
749
|
-
},
|
750
|
-
_stats_join_assets={"value": True, "preprocessor": _str_as_bool},
|
751
|
-
_stats_neuron_monitor_config_path={
|
752
|
-
"hook": lambda x: self._path_convert(x),
|
753
|
-
},
|
754
|
-
_stats_open_metrics_endpoints={
|
755
|
-
"preprocessor": _str_as_json,
|
756
|
-
},
|
757
|
-
_stats_open_metrics_filters={
|
758
|
-
# capture all metrics on all endpoints by default
|
759
|
-
"value": (".*",),
|
760
|
-
"preprocessor": _str_as_json,
|
761
|
-
},
|
762
|
-
_stats_disk_paths={
|
763
|
-
"value": ("/",),
|
764
|
-
"preprocessor": _str_as_json,
|
765
|
-
},
|
766
|
-
_stats_buffer_size={
|
767
|
-
"value": 0,
|
768
|
-
"preprocessor": int,
|
769
|
-
},
|
770
|
-
_sync={"value": False},
|
771
|
-
_tmp_code_dir={
|
772
|
-
"value": "code",
|
773
|
-
"hook": lambda x: self._path_convert(self.tmp_dir, x),
|
774
|
-
},
|
775
|
-
_windows={
|
776
|
-
"hook": lambda _: platform.system() == "Windows",
|
777
|
-
"auto_hook": True,
|
778
|
-
},
|
779
|
-
_show_operation_stats={"preprocessor": _str_as_bool},
|
780
|
-
anonymous={"validator": self._validate_anonymous},
|
781
|
-
api_key={"validator": self._validate_api_key},
|
782
|
-
base_url={
|
783
|
-
"value": "https://api.wandb.ai",
|
784
|
-
"preprocessor": lambda x: str(x).strip().rstrip("/"),
|
785
|
-
"validator": self._validate_base_url,
|
786
|
-
},
|
787
|
-
colab_url={
|
788
|
-
"hook": lambda _: self._get_colab_url(),
|
789
|
-
"auto_hook": True,
|
790
|
-
},
|
791
|
-
config_paths={"preprocessor": _str_as_tuple},
|
792
|
-
console={
|
793
|
-
"value": "auto",
|
794
|
-
"validator": self._validate_console,
|
795
|
-
"hook": lambda x: self._convert_console(x),
|
796
|
-
"auto_hook": True,
|
797
|
-
},
|
798
|
-
console_multipart={"value": False, "preprocessor": _str_as_bool},
|
799
|
-
credentials_file={
|
800
|
-
"value": str(credentials.DEFAULT_WANDB_CREDENTIALS_FILE),
|
801
|
-
"preprocessor": str,
|
802
|
-
},
|
803
|
-
deployment={
|
804
|
-
"hook": lambda _: "local" if self.is_local else "cloud",
|
805
|
-
"auto_hook": True,
|
806
|
-
},
|
807
|
-
disable_code={
|
808
|
-
"value": False,
|
809
|
-
"preprocessor": _str_as_bool,
|
810
|
-
"hook": lambda x: self._disable_machine_info or x,
|
811
|
-
},
|
812
|
-
disable_hints={"preprocessor": _str_as_bool},
|
813
|
-
disable_git={
|
814
|
-
"value": False,
|
815
|
-
"preprocessor": _str_as_bool,
|
816
|
-
"hook": lambda x: self._disable_machine_info or x,
|
817
|
-
},
|
818
|
-
disable_job_creation={
|
819
|
-
"value": False,
|
820
|
-
"preprocessor": _str_as_bool,
|
821
|
-
"hook": lambda x: self._disable_machine_info or x,
|
822
|
-
},
|
823
|
-
disabled={"value": False, "preprocessor": _str_as_bool},
|
824
|
-
files_dir={
|
825
|
-
"value": "files",
|
826
|
-
"hook": lambda x: self._path_convert(
|
827
|
-
self.wandb_dir, f"{self.run_mode}-{self.timespec}-{self.run_id}", x
|
828
|
-
),
|
829
|
-
},
|
830
|
-
force={"preprocessor": _str_as_bool},
|
831
|
-
fork_from={
|
832
|
-
"value": None,
|
833
|
-
"preprocessor": _runmoment_preprocessor,
|
834
|
-
},
|
835
|
-
resume_from={
|
836
|
-
"value": None,
|
837
|
-
"preprocessor": _runmoment_preprocessor,
|
838
|
-
},
|
839
|
-
git_remote={"value": "origin"},
|
840
|
-
heartbeat_seconds={"value": 30},
|
841
|
-
http_proxy={
|
842
|
-
"hook": lambda x: self._proxies and self._proxies.get("http") or x,
|
843
|
-
"auto_hook": True,
|
844
|
-
},
|
845
|
-
https_proxy={
|
846
|
-
"hook": lambda x: self._proxies and self._proxies.get("https") or x,
|
847
|
-
"auto_hook": True,
|
848
|
-
},
|
849
|
-
identity_token_file={"value": None, "preprocessor": str},
|
850
|
-
ignore_globs={
|
851
|
-
"value": tuple(),
|
852
|
-
"preprocessor": lambda x: tuple(x) if not isinstance(x, tuple) else x,
|
853
|
-
},
|
854
|
-
init_timeout={"value": 90, "preprocessor": lambda x: float(x)},
|
855
|
-
is_local={
|
856
|
-
"hook": (
|
857
|
-
lambda _: self.base_url != "https://api.wandb.ai"
|
858
|
-
if self.base_url is not None
|
859
|
-
else False
|
860
|
-
),
|
861
|
-
"auto_hook": True,
|
862
|
-
},
|
863
|
-
job_name={"preprocessor": str},
|
864
|
-
job_source={"validator": self._validate_job_source},
|
865
|
-
label_disable={"preprocessor": _str_as_bool},
|
866
|
-
launch={"preprocessor": _str_as_bool},
|
867
|
-
log_dir={
|
868
|
-
"value": "logs",
|
869
|
-
"hook": lambda x: self._path_convert(
|
870
|
-
self.wandb_dir, f"{self.run_mode}-{self.timespec}-{self.run_id}", x
|
871
|
-
),
|
872
|
-
},
|
873
|
-
log_internal={
|
874
|
-
"value": "debug-internal.log",
|
875
|
-
"hook": lambda x: self._path_convert(self.log_dir, x),
|
876
|
-
},
|
877
|
-
log_symlink_internal={
|
878
|
-
"value": "debug-internal.log",
|
879
|
-
"hook": lambda x: self._path_convert(self.wandb_dir, x),
|
880
|
-
},
|
881
|
-
log_symlink_user={
|
882
|
-
"value": "debug.log",
|
883
|
-
"hook": lambda x: self._path_convert(self.wandb_dir, x),
|
884
|
-
},
|
885
|
-
log_user={
|
886
|
-
"value": "debug.log",
|
887
|
-
"hook": lambda x: self._path_convert(self.log_dir, x),
|
888
|
-
},
|
889
|
-
login_timeout={"preprocessor": lambda x: float(x)},
|
890
|
-
mode={"value": "online", "validator": self._validate_mode},
|
891
|
-
program={
|
892
|
-
"hook": lambda x: self._get_program(x),
|
893
|
-
},
|
894
|
-
project={
|
895
|
-
"validator": self._validate_project,
|
896
|
-
},
|
897
|
-
project_url={"hook": lambda _: self._project_url(), "auto_hook": True},
|
898
|
-
quiet={"preprocessor": _str_as_bool},
|
899
|
-
reinit={"preprocessor": _str_as_bool},
|
900
|
-
relogin={"preprocessor": _str_as_bool},
|
901
|
-
# todo: hack to make to_proto() always happy
|
902
|
-
resume={"preprocessor": lambda x: None if x is False else x},
|
903
|
-
resume_fname={
|
904
|
-
"value": "wandb-resume.json",
|
905
|
-
"hook": lambda x: self._path_convert(self.wandb_dir, x),
|
906
|
-
},
|
907
|
-
resumed={"value": "False", "preprocessor": _str_as_bool},
|
908
|
-
root_dir={
|
909
|
-
"preprocessor": lambda x: str(x),
|
910
|
-
"value": os.path.abspath(os.getcwd()),
|
911
|
-
},
|
912
|
-
run_id={
|
913
|
-
"validator": self._validate_run_id,
|
914
|
-
},
|
915
|
-
run_mode={
|
916
|
-
"hook": lambda _: "offline-run" if self._offline else "run",
|
917
|
-
"auto_hook": True,
|
918
|
-
},
|
919
|
-
run_tags={
|
920
|
-
"preprocessor": lambda x: tuple(x) if not isinstance(x, tuple) else x,
|
921
|
-
},
|
922
|
-
run_url={"hook": lambda _: self._run_url(), "auto_hook": True},
|
923
|
-
sagemaker_disable={"preprocessor": _str_as_bool},
|
924
|
-
save_code={"preprocessor": _str_as_bool},
|
925
|
-
settings_system={
|
926
|
-
"value": os.path.join("~", ".config", "wandb", "settings"),
|
927
|
-
"hook": lambda x: self._path_convert(x),
|
928
|
-
},
|
929
|
-
settings_workspace={
|
930
|
-
"value": "settings",
|
931
|
-
"hook": lambda x: self._path_convert(self.wandb_dir, x),
|
932
|
-
},
|
933
|
-
show_colors={"preprocessor": _str_as_bool},
|
934
|
-
show_emoji={"preprocessor": _str_as_bool},
|
935
|
-
show_errors={"value": "True", "preprocessor": _str_as_bool},
|
936
|
-
show_info={"value": "True", "preprocessor": _str_as_bool},
|
937
|
-
show_warnings={"value": "True", "preprocessor": _str_as_bool},
|
938
|
-
silent={"value": "False", "preprocessor": _str_as_bool},
|
939
|
-
start_method={"validator": self._validate_start_method},
|
940
|
-
strict={"preprocessor": _str_as_bool},
|
941
|
-
summary_timeout={"value": 60, "preprocessor": lambda x: int(x)},
|
942
|
-
summary_warnings={
|
943
|
-
"value": 5,
|
944
|
-
"preprocessor": lambda x: int(x),
|
945
|
-
"is_policy": True,
|
946
|
-
},
|
947
|
-
sweep_url={"hook": lambda _: self._sweep_url(), "auto_hook": True},
|
948
|
-
symlink={"preprocessor": _str_as_bool},
|
949
|
-
sync_dir={
|
950
|
-
"hook": [
|
951
|
-
lambda _: self._path_convert(
|
952
|
-
self.wandb_dir, f"{self.run_mode}-{self.timespec}-{self.run_id}"
|
953
|
-
)
|
954
|
-
],
|
955
|
-
"auto_hook": True,
|
956
|
-
},
|
957
|
-
sync_file={
|
958
|
-
"hook": lambda _: self._path_convert(
|
959
|
-
self.sync_dir, f"run-{self.run_id}.wandb"
|
960
|
-
),
|
961
|
-
"auto_hook": True,
|
962
|
-
},
|
963
|
-
sync_symlink_latest={
|
964
|
-
"value": "latest-run",
|
965
|
-
"hook": lambda x: self._path_convert(self.wandb_dir, x),
|
966
|
-
},
|
967
|
-
table_raise_on_max_row_limit_exceeded={
|
968
|
-
"value": False,
|
969
|
-
"preprocessor": _str_as_bool,
|
970
|
-
},
|
971
|
-
timespec={
|
972
|
-
"hook": lambda _: self._start_datetime,
|
973
|
-
"auto_hook": True,
|
974
|
-
},
|
975
|
-
tmp_dir={
|
976
|
-
"value": "tmp",
|
977
|
-
"hook": lambda x: (
|
978
|
-
self._path_convert(
|
979
|
-
self.wandb_dir,
|
980
|
-
f"{self.run_mode}-{self.timespec}-{self.run_id}",
|
981
|
-
x,
|
982
|
-
)
|
983
|
-
or tempfile.gettempdir()
|
984
|
-
),
|
985
|
-
},
|
986
|
-
wandb_dir={
|
987
|
-
"hook": lambda _: _get_wandb_dir(self.root_dir or ""),
|
988
|
-
"auto_hook": True,
|
989
|
-
},
|
990
|
-
)
|
991
|
-
return props
|
446
|
+
@field_validator("https_proxy", mode="after")
|
447
|
+
@classmethod
|
448
|
+
def validate_https_proxy(cls, value):
|
449
|
+
if value is None:
|
450
|
+
return None
|
451
|
+
cls.validate_url(value)
|
452
|
+
return value.rstrip("/")
|
992
453
|
|
993
|
-
|
994
|
-
@
|
995
|
-
def
|
996
|
-
|
454
|
+
@field_validator("ignore_globs", mode="after")
|
455
|
+
@classmethod
|
456
|
+
def validate_ignore_globs(cls, value):
|
457
|
+
return tuple(value) if not isinstance(value, tuple) else value
|
997
458
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
459
|
+
@field_validator("project", mode="after")
|
460
|
+
@classmethod
|
461
|
+
def validate_project(cls, value, info):
|
462
|
+
if value is None:
|
463
|
+
return None
|
464
|
+
invalid_chars_list = list("/\\#?%:")
|
465
|
+
if len(value) > 128:
|
466
|
+
raise UsageError(f"Invalid project name {value!r}: exceeded 128 characters")
|
467
|
+
invalid_chars = {char for char in invalid_chars_list if char in value}
|
468
|
+
if invalid_chars:
|
469
|
+
raise UsageError(
|
470
|
+
f"Invalid project name {value!r}: "
|
471
|
+
f"cannot contain characters {','.join(invalid_chars_list)!r}, "
|
472
|
+
f"found {','.join(invalid_chars)!r}"
|
473
|
+
)
|
474
|
+
return value
|
1004
475
|
|
1005
|
-
|
476
|
+
@field_validator("resume", mode="before")
|
477
|
+
@classmethod
|
478
|
+
def validate_resume(cls, value):
|
479
|
+
if value is False:
|
480
|
+
return None
|
481
|
+
if value is True:
|
482
|
+
return "auto"
|
483
|
+
return value
|
1006
484
|
|
1007
|
-
|
485
|
+
@field_validator("resume_from", mode="before")
|
486
|
+
@classmethod
|
487
|
+
def validate_resume_from(cls, value, info) -> RunMoment | None:
|
488
|
+
run_moment = cls._runmoment_preprocessor(value)
|
489
|
+
if run_moment and info.data.get("run_id") != run_moment.run:
|
490
|
+
raise ValueError(
|
491
|
+
"Both `run_id` and `resume_from` have been specified with different ids."
|
492
|
+
)
|
493
|
+
return run_moment
|
1008
494
|
|
1009
|
-
@
|
1010
|
-
|
1011
|
-
|
1012
|
-
if value
|
1013
|
-
|
1014
|
-
return True
|
495
|
+
@field_validator("run_id", mode="after")
|
496
|
+
@classmethod
|
497
|
+
def validate_run_id(cls, value, info):
|
498
|
+
if value is None:
|
499
|
+
return None
|
1015
500
|
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
)
|
1024
|
-
invalid_chars = {char for char in invalid_chars_list if char in value}
|
1025
|
-
if invalid_chars:
|
1026
|
-
raise UsageError(
|
1027
|
-
f"Invalid project name {value!r}: "
|
1028
|
-
f"cannot contain characters {','.join(invalid_chars_list)!r}, "
|
1029
|
-
f"found {','.join(invalid_chars)!r}"
|
1030
|
-
)
|
1031
|
-
return True
|
501
|
+
if len(value) == 0:
|
502
|
+
raise UsageError("Run ID cannot be empty")
|
503
|
+
if len(value) > len(value.strip()):
|
504
|
+
raise UsageError("Run ID cannot start or end with whitespace")
|
505
|
+
if not bool(value.strip()):
|
506
|
+
raise UsageError("Run ID cannot contain only whitespace")
|
507
|
+
return value
|
1032
508
|
|
1033
|
-
@
|
1034
|
-
|
509
|
+
@field_validator("settings_system", mode="after")
|
510
|
+
@classmethod
|
511
|
+
def validate_settings_system(cls, value):
|
512
|
+
return _path_convert(value)
|
513
|
+
|
514
|
+
@field_validator("x_service_wait", mode="after")
|
515
|
+
@classmethod
|
516
|
+
def validate_service_wait(cls, value):
|
517
|
+
if value < 0:
|
518
|
+
raise UsageError("Service wait time cannot be negative")
|
519
|
+
return
|
520
|
+
|
521
|
+
@field_validator("start_method")
|
522
|
+
@classmethod
|
523
|
+
def validate_start_method(cls, value):
|
524
|
+
if value is None:
|
525
|
+
return value
|
1035
526
|
available_methods = ["thread"]
|
1036
527
|
if hasattr(multiprocessing, "get_all_start_methods"):
|
1037
528
|
available_methods += multiprocessing.get_all_start_methods()
|
@@ -1039,251 +530,237 @@ class Settings(SettingsData):
|
|
1039
530
|
raise UsageError(
|
1040
531
|
f"Settings field `start_method`: {value!r} not in {available_methods}"
|
1041
532
|
)
|
1042
|
-
return
|
1043
|
-
|
1044
|
-
@staticmethod
|
1045
|
-
def _validate_console(value: str) -> bool:
|
1046
|
-
choices = ConsoleValue
|
1047
|
-
if value not in choices:
|
1048
|
-
# do not advertise internal console states
|
1049
|
-
choices -= {"wrap_emu", "wrap_raw"}
|
1050
|
-
raise UsageError(f"Settings field `console`: {value!r} not in {choices}")
|
1051
|
-
return True
|
533
|
+
return value
|
1052
534
|
|
1053
|
-
@
|
1054
|
-
|
1055
|
-
|
1056
|
-
if value
|
1057
|
-
raise UsageError(
|
1058
|
-
return
|
535
|
+
@field_validator("x_stats_sampling_interval", mode="after")
|
536
|
+
@classmethod
|
537
|
+
def validate_stats_sampling_interval(cls, value):
|
538
|
+
if value < 0.1:
|
539
|
+
raise UsageError("Stats sampling interval cannot be less than 0.1 seconds")
|
540
|
+
return value
|
1059
541
|
|
1060
|
-
@
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
542
|
+
@field_validator("x_stats_open_metrics_endpoints", mode="before")
|
543
|
+
@classmethod
|
544
|
+
def validate_stats_open_metrics_endpoints(cls, value):
|
545
|
+
if isinstance(value, str):
|
546
|
+
return json.loads(value)
|
547
|
+
return value
|
1065
548
|
|
1066
|
-
@
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
549
|
+
@field_validator("x_stats_open_metrics_filters", mode="before")
|
550
|
+
@classmethod
|
551
|
+
def validate_stats_open_metrics_filters(cls, value):
|
552
|
+
if isinstance(value, str):
|
553
|
+
return json.loads(value)
|
554
|
+
return value
|
1070
555
|
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
556
|
+
@field_validator("x_stats_open_metrics_http_headers", mode="before")
|
557
|
+
@classmethod
|
558
|
+
def validate_stats_open_metrics_http_headers(cls, value):
|
559
|
+
if isinstance(value, str):
|
560
|
+
return json.loads(value)
|
561
|
+
return value
|
1077
562
|
|
1078
|
-
|
563
|
+
@field_validator("sweep_id", mode="after")
|
564
|
+
@classmethod
|
565
|
+
def validate_sweep_id(cls, value):
|
566
|
+
if value is None:
|
567
|
+
return None
|
568
|
+
if len(value) == 0:
|
569
|
+
raise UsageError("Sweep ID cannot be empty")
|
570
|
+
if len(value) > len(value.strip()):
|
571
|
+
raise UsageError("Sweep ID cannot start or end with whitespace")
|
572
|
+
if not bool(value.strip()):
|
573
|
+
raise UsageError("Sweep ID cannot contain only whitespace")
|
574
|
+
return value
|
1079
575
|
|
1080
|
-
|
1081
|
-
def _validate_base_url(value: Optional[str]) -> bool:
|
1082
|
-
"""Validate the base url of the wandb server.
|
576
|
+
# Computed fields.
|
1083
577
|
|
1084
|
-
|
578
|
+
@computed_field # type: ignore[prop-decorator]
|
579
|
+
@property
|
580
|
+
def _args(self) -> list[str]:
|
581
|
+
if not self._jupyter:
|
582
|
+
return sys.argv[1:]
|
583
|
+
return []
|
1085
584
|
|
1086
|
-
|
585
|
+
@computed_field # type: ignore[prop-decorator]
|
586
|
+
@property
|
587
|
+
def _aws_lambda(self) -> bool:
|
588
|
+
"""Check if we are running in a lambda environment."""
|
589
|
+
from sentry_sdk.integrations.aws_lambda import ( # type: ignore[import-not-found]
|
590
|
+
get_lambda_bootstrap,
|
591
|
+
)
|
1087
592
|
|
1088
|
-
|
1089
|
-
|
593
|
+
lambda_bootstrap = get_lambda_bootstrap()
|
594
|
+
if not lambda_bootstrap or not hasattr(
|
595
|
+
lambda_bootstrap, "handle_event_request"
|
596
|
+
):
|
597
|
+
return False
|
598
|
+
return True
|
1090
599
|
|
1091
|
-
|
1092
|
-
|
600
|
+
@computed_field # type: ignore[prop-decorator]
|
601
|
+
@property
|
602
|
+
def _code_path_local(self) -> str | None:
|
603
|
+
"""The relative path from the current working directory to the code path.
|
1093
604
|
|
1094
|
-
|
1095
|
-
|
605
|
+
For example, if the code path is /home/user/project/example.py, and the
|
606
|
+
current working directory is /home/user/project, then the code path local
|
607
|
+
is example.py.
|
1096
608
|
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
609
|
+
If couldn't find the relative path, this will be an empty string.
|
610
|
+
"""
|
611
|
+
return self._get_program_relpath(self.program) if self.program else None
|
1100
612
|
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
613
|
+
@computed_field # type: ignore[prop-decorator]
|
614
|
+
@property
|
615
|
+
def _colab(self) -> bool:
|
616
|
+
return "google.colab" in sys.modules
|
1104
617
|
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
1110
|
-
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
1111
|
-
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
1112
|
-
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
1113
|
-
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
1114
|
-
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
1115
|
-
"""
|
1116
|
-
if value is None:
|
1117
|
-
return True
|
618
|
+
@computed_field # type: ignore[prop-decorator]
|
619
|
+
@property
|
620
|
+
def _ipython(self) -> bool:
|
621
|
+
return ipython.in_ipython()
|
1118
622
|
|
1119
|
-
|
623
|
+
@computed_field # type: ignore[prop-decorator]
|
624
|
+
@property
|
625
|
+
def _jupyter(self) -> bool:
|
626
|
+
return ipython.in_jupyter()
|
1120
627
|
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
)
|
1126
|
-
ipv6_re = r"\[[0-9a-f:.]+\]" # (simple regex, validated later)
|
628
|
+
@computed_field # type: ignore[prop-decorator]
|
629
|
+
@property
|
630
|
+
def _kaggle(self) -> bool:
|
631
|
+
return util._is_likely_kaggle()
|
1127
632
|
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
# Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1
|
1133
|
-
domain_re = r"(?:\.(?!-)[a-z" + ul + r"0-9-]{1,63}(?<!-))*"
|
1134
|
-
tld_re = (
|
1135
|
-
r"\." # dot
|
1136
|
-
r"(?!-)" # can't start with a dash
|
1137
|
-
r"(?:[a-z" + ul + "-]{2,63}" # domain label
|
1138
|
-
r"|xn--[a-z0-9]{1,59})" # or punycode label
|
1139
|
-
r"(?<!-)" # can't end with a dash
|
1140
|
-
r"\.?" # may have a trailing dot
|
1141
|
-
)
|
1142
|
-
# host_re = "(" + hostname_re + domain_re + tld_re + "|localhost)"
|
1143
|
-
# todo?: allow hostname to be just a hostname (no tld)?
|
1144
|
-
host_re = "(" + hostname_re + domain_re + f"({tld_re})?" + "|localhost)"
|
1145
|
-
|
1146
|
-
regex = re.compile(
|
1147
|
-
r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
|
1148
|
-
r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
|
1149
|
-
r"(?:" + ipv4_re + "|" + ipv6_re + "|" + host_re + ")"
|
1150
|
-
r"(?::[0-9]{1,5})?" # port
|
1151
|
-
r"(?:[/?#][^\s]*)?" # resource path
|
1152
|
-
r"\Z",
|
1153
|
-
re.IGNORECASE,
|
1154
|
-
)
|
1155
|
-
schemes = {"http", "https"}
|
1156
|
-
unsafe_chars = frozenset("\t\r\n")
|
633
|
+
@computed_field # type: ignore[prop-decorator]
|
634
|
+
@property
|
635
|
+
def _noop(self) -> bool:
|
636
|
+
return self.mode == "disabled"
|
1157
637
|
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
638
|
+
@computed_field # type: ignore[prop-decorator]
|
639
|
+
@property
|
640
|
+
def _notebook(self) -> bool:
|
641
|
+
return self._ipython or self._jupyter or self._colab or self._kaggle
|
1161
642
|
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
)
|
1167
|
-
elif re.match(r".*wandb\.ai[^\.]*$", value) and scheme != "https":
|
1168
|
-
raise UsageError("http is not secure, please use https://api.wandb.ai")
|
1169
|
-
elif parsed_url.netloc == "":
|
1170
|
-
raise UsageError(f"Invalid URL: {value}")
|
1171
|
-
elif unsafe_chars.intersection(value):
|
1172
|
-
raise UsageError("URL cannot contain unsafe characters")
|
1173
|
-
elif scheme not in schemes:
|
1174
|
-
raise UsageError("URL must start with `http(s)://`")
|
1175
|
-
elif not regex.search(value):
|
1176
|
-
raise UsageError(f"{value} is not a valid server address")
|
1177
|
-
elif split_url.hostname is None or len(split_url.hostname) > 253:
|
1178
|
-
raise UsageError("hostname is invalid")
|
643
|
+
@computed_field # type: ignore[prop-decorator]
|
644
|
+
@property
|
645
|
+
def _offline(self) -> bool:
|
646
|
+
return self.mode in ("offline", "dryrun")
|
1179
647
|
|
1180
|
-
|
648
|
+
@computed_field # type: ignore[prop-decorator]
|
649
|
+
@property
|
650
|
+
def _os(self) -> str:
|
651
|
+
"""The operating system of the machine running the script."""
|
652
|
+
return platform.platform(aliased=True)
|
1181
653
|
|
1182
|
-
@
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
wandb.termwarn(
|
1187
|
-
"Disabling the wandb service is deprecated as of version 0.18.0 and will be removed in future versions. ",
|
1188
|
-
repeat=False,
|
1189
|
-
)
|
1190
|
-
return value
|
654
|
+
@computed_field # type: ignore[prop-decorator]
|
655
|
+
@property
|
656
|
+
def _platform(self) -> str:
|
657
|
+
return f"{platform.system()}-{platform.machine()}".lower()
|
1191
658
|
|
1192
|
-
@
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
return True
|
659
|
+
@computed_field # type: ignore[prop-decorator]
|
660
|
+
@property
|
661
|
+
def _python(self) -> str:
|
662
|
+
return f"{platform.python_implementation()} {platform.python_version()}"
|
1197
663
|
|
1198
|
-
@
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
return True
|
664
|
+
@computed_field # type: ignore[prop-decorator]
|
665
|
+
@property
|
666
|
+
def _shared(self) -> bool:
|
667
|
+
"""Whether we are in shared mode.
|
1203
668
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
return True
|
669
|
+
In "shared" mode, multiple processes can write to the same run,
|
670
|
+
for example from different machines.
|
671
|
+
"""
|
672
|
+
return self.mode == "shared"
|
1209
673
|
|
1210
|
-
@
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
674
|
+
@computed_field # type: ignore[prop-decorator]
|
675
|
+
@property
|
676
|
+
def _start_datetime(self) -> str:
|
677
|
+
if self.x_start_time is None:
|
678
|
+
return ""
|
679
|
+
datetime_now = datetime.fromtimestamp(self.x_start_time)
|
680
|
+
return datetime_now.strftime("%Y%m%d_%H%M%S")
|
1215
681
|
|
1216
|
-
@
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
682
|
+
@computed_field # type: ignore[prop-decorator]
|
683
|
+
@property
|
684
|
+
def _tmp_code_dir(self) -> str:
|
685
|
+
return _path_convert(
|
686
|
+
self.wandb_dir,
|
687
|
+
f"{self.run_mode}-{self.timespec}-{self.run_id}",
|
688
|
+
"tmp",
|
689
|
+
"code",
|
690
|
+
)
|
1224
691
|
|
1225
|
-
#
|
1226
|
-
@
|
1227
|
-
def
|
1228
|
-
|
1229
|
-
return os.path.expanduser(os.path.join(*args))
|
1230
|
-
|
1231
|
-
def _convert_console(self, console: str) -> str:
|
1232
|
-
if console == "auto":
|
1233
|
-
if (
|
1234
|
-
self._jupyter
|
1235
|
-
or (self.start_method == "thread")
|
1236
|
-
or not self._disable_service
|
1237
|
-
or self._windows
|
1238
|
-
):
|
1239
|
-
console = "wrap"
|
1240
|
-
else:
|
1241
|
-
console = "redirect"
|
1242
|
-
return console
|
692
|
+
@computed_field # type: ignore[prop-decorator]
|
693
|
+
@property
|
694
|
+
def _windows(self) -> bool:
|
695
|
+
return platform.system() == "Windows"
|
1243
696
|
|
1244
|
-
|
697
|
+
@computed_field # type: ignore[prop-decorator]
|
698
|
+
@property
|
699
|
+
def colab_url(self) -> str | None:
|
700
|
+
"""The URL to the Colab notebook, if running in Colab."""
|
1245
701
|
if not self._colab:
|
1246
702
|
return None
|
1247
|
-
if self.
|
1248
|
-
unescaped = unquote(self.
|
703
|
+
if self.x_jupyter_path and self.x_jupyter_path.startswith("fileId="):
|
704
|
+
unescaped = unquote(self.x_jupyter_path)
|
1249
705
|
return "https://colab.research.google.com/notebook#" + unescaped
|
1250
706
|
return None
|
1251
707
|
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
if not self._jupyter:
|
1257
|
-
return program
|
1258
|
-
|
1259
|
-
if self.notebook_name:
|
1260
|
-
return self.notebook_name
|
708
|
+
@computed_field # type: ignore[prop-decorator]
|
709
|
+
@property
|
710
|
+
def deployment(self) -> Literal["local", "cloud"]:
|
711
|
+
return "local" if self.is_local else "cloud"
|
1261
712
|
|
1262
|
-
|
1263
|
-
|
713
|
+
@computed_field # type: ignore[prop-decorator]
|
714
|
+
@property
|
715
|
+
def files_dir(self) -> str:
|
716
|
+
"""Absolute path to the local directory where the run's files are stored."""
|
717
|
+
return self.x_files_dir or _path_convert(
|
718
|
+
self.wandb_dir,
|
719
|
+
f"{self.run_mode}-{self.timespec}-{self.run_id}",
|
720
|
+
"files",
|
721
|
+
)
|
1264
722
|
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
723
|
+
@computed_field # type: ignore[prop-decorator]
|
724
|
+
@property
|
725
|
+
def is_local(self) -> bool:
|
726
|
+
return str(self.base_url) != "https://api.wandb.ai"
|
1269
727
|
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
728
|
+
@computed_field # type: ignore[prop-decorator]
|
729
|
+
@property
|
730
|
+
def log_dir(self) -> str:
|
731
|
+
"""The directory for storing log files."""
|
732
|
+
return _path_convert(
|
733
|
+
self.wandb_dir, f"{self.run_mode}-{self.timespec}-{self.run_id}", "logs"
|
734
|
+
)
|
1274
735
|
|
1275
|
-
|
736
|
+
@computed_field # type: ignore[prop-decorator]
|
737
|
+
@property
|
738
|
+
def log_internal(self) -> str:
|
739
|
+
"""The path to the file to use for internal logs."""
|
740
|
+
return _path_convert(self.log_dir, "debug-internal.log")
|
1276
741
|
|
1277
|
-
|
742
|
+
@computed_field # type: ignore[prop-decorator]
|
743
|
+
@property
|
744
|
+
def log_symlink_internal(self) -> str:
|
745
|
+
"""The path to the symlink to the internal log file of the most recent run."""
|
746
|
+
return _path_convert(self.wandb_dir, "debug-internal.log")
|
1278
747
|
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
748
|
+
@computed_field # type: ignore[prop-decorator]
|
749
|
+
@property
|
750
|
+
def log_symlink_user(self) -> str:
|
751
|
+
"""The path to the symlink to the user-process log file of the most recent run."""
|
752
|
+
return _path_convert(self.wandb_dir, "debug.log")
|
1282
753
|
|
1283
|
-
|
1284
|
-
|
754
|
+
@computed_field # type: ignore[prop-decorator]
|
755
|
+
@property
|
756
|
+
def log_user(self) -> str:
|
757
|
+
"""The path to the file to use for user-process logs."""
|
758
|
+
return _path_convert(self.log_dir, "debug.log")
|
1285
759
|
|
1286
|
-
|
760
|
+
@computed_field # type: ignore[prop-decorator]
|
761
|
+
@property
|
762
|
+
def project_url(self) -> str:
|
763
|
+
"""The W&B URL where the project can be viewed."""
|
1287
764
|
project_url = self._project_url_base()
|
1288
765
|
if not project_url:
|
1289
766
|
return ""
|
@@ -1292,401 +769,133 @@ class Settings(SettingsData):
|
|
1292
769
|
|
1293
770
|
return f"{project_url}{query}"
|
1294
771
|
|
1295
|
-
|
1296
|
-
|
772
|
+
@computed_field # type: ignore[prop-decorator]
|
773
|
+
@property
|
774
|
+
def resume_fname(self) -> str:
|
775
|
+
"""The path to the resume file."""
|
776
|
+
return _path_convert(self.wandb_dir, "wandb-resume.json")
|
777
|
+
|
778
|
+
@computed_field # type: ignore[prop-decorator]
|
779
|
+
@property
|
780
|
+
def run_mode(self) -> Literal["run", "offline-run"]:
|
781
|
+
return "run" if not self._offline else "offline-run"
|
782
|
+
|
783
|
+
@computed_field # type: ignore[prop-decorator]
|
784
|
+
@property
|
785
|
+
def run_url(self) -> str:
|
786
|
+
"""The W&B URL where the run can be viewed."""
|
1297
787
|
project_url = self._project_url_base()
|
1298
788
|
if not all([project_url, self.run_id]):
|
1299
789
|
return ""
|
1300
790
|
|
1301
791
|
query = self._get_url_query_string()
|
1302
|
-
return f"{project_url}/runs/{quote(self.run_id)}{query}"
|
792
|
+
return f"{project_url}/runs/{quote(self.run_id or '')}{query}"
|
1303
793
|
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
""
|
1309
|
-
time_stamp: float = time.time()
|
1310
|
-
datetime_now: datetime = datetime.fromtimestamp(time_stamp)
|
1311
|
-
datetime_now_str = _datetime_as_str(datetime_now)
|
1312
|
-
object.__setattr__(self, "_Settings_start_datetime", datetime_now_str)
|
1313
|
-
object.__setattr__(self, "_Settings_start_time", time_stamp)
|
1314
|
-
self.update(
|
1315
|
-
_start_datetime=datetime_now_str,
|
1316
|
-
_start_time=time_stamp,
|
1317
|
-
source=source,
|
1318
|
-
)
|
794
|
+
@computed_field # type: ignore[prop-decorator]
|
795
|
+
@property
|
796
|
+
def settings_workspace(self) -> str:
|
797
|
+
"""The path to the workspace settings file."""
|
798
|
+
return _path_convert(self.wandb_dir, "settings")
|
1319
799
|
|
1320
|
-
|
1321
|
-
|
800
|
+
@computed_field # type: ignore[prop-decorator]
|
801
|
+
@property
|
802
|
+
def sweep_url(self) -> str:
|
803
|
+
"""The W&B URL where the sweep can be viewed."""
|
1322
804
|
project_url = self._project_url_base()
|
1323
805
|
if not all([project_url, self.sweep_id]):
|
1324
806
|
return ""
|
1325
807
|
|
1326
808
|
query = self._get_url_query_string()
|
1327
|
-
return f"{project_url}/sweeps/{quote(self.sweep_id)}{query}"
|
1328
|
-
|
1329
|
-
def __init__(self, **kwargs: Any) -> None:
|
1330
|
-
self.__frozen: bool = False
|
1331
|
-
self.__initialized: bool = False
|
1332
|
-
|
1333
|
-
self.__modification_order = SETTINGS_TOPOLOGICALLY_SORTED
|
1334
|
-
|
1335
|
-
# Set default settings values
|
1336
|
-
# We start off with the class attributes and `default_props` dicts
|
1337
|
-
# and then create Property objects.
|
1338
|
-
# Once initialized, attributes are to only be updated using the `update` method
|
1339
|
-
default_props = self._default_props()
|
1340
|
-
|
1341
|
-
# Init instance attributes as Property objects.
|
1342
|
-
# Type hints of class attributes are used to generate a type validator function
|
1343
|
-
# for runtime checks for each attribute.
|
1344
|
-
# These are defaults, using Source.BASE for non-policy attributes and Source.RUN for policies.
|
1345
|
-
for prop, type_hint in get_type_hints(SettingsData).items():
|
1346
|
-
validators = [self._validator_factory(type_hint)]
|
1347
|
-
|
1348
|
-
if prop in default_props:
|
1349
|
-
validator = default_props[prop].pop("validator", [])
|
1350
|
-
# Property validator could be either Callable or Sequence[Callable]
|
1351
|
-
if callable(validator):
|
1352
|
-
validators.append(validator)
|
1353
|
-
elif isinstance(validator, Sequence):
|
1354
|
-
validators.extend(list(validator))
|
1355
|
-
object.__setattr__(
|
1356
|
-
self,
|
1357
|
-
prop,
|
1358
|
-
Property(
|
1359
|
-
name=prop,
|
1360
|
-
**default_props[prop],
|
1361
|
-
validator=validators,
|
1362
|
-
# todo: double-check this logic:
|
1363
|
-
source=Source.RUN
|
1364
|
-
if default_props[prop].get("is_policy", False)
|
1365
|
-
else Source.BASE,
|
1366
|
-
),
|
1367
|
-
)
|
1368
|
-
else:
|
1369
|
-
object.__setattr__(
|
1370
|
-
self,
|
1371
|
-
prop,
|
1372
|
-
Property(
|
1373
|
-
name=prop,
|
1374
|
-
validator=validators,
|
1375
|
-
source=Source.BASE,
|
1376
|
-
),
|
1377
|
-
)
|
809
|
+
return f"{project_url}/sweeps/{quote(self.sweep_id or '')}{query}"
|
1378
810
|
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
)
|
811
|
+
@computed_field # type: ignore[prop-decorator]
|
812
|
+
@property
|
813
|
+
def sync_dir(self) -> str:
|
814
|
+
return _path_convert(
|
815
|
+
self.wandb_dir, f"{self.run_mode}-{self.timespec}-{self.run_id}"
|
816
|
+
)
|
1386
817
|
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
self.update({prop: kwargs[prop]}, source=source)
|
1393
|
-
kwargs.pop(prop)
|
1394
|
-
|
1395
|
-
for k, v in kwargs.items():
|
1396
|
-
# todo: double-check this logic:
|
1397
|
-
source = Source.RUN if self.__dict__[k].is_policy else Source.BASE
|
1398
|
-
self.update({k: v}, source=source)
|
1399
|
-
|
1400
|
-
# setup private attributes
|
1401
|
-
object.__setattr__(self, "_Settings_start_datetime", None)
|
1402
|
-
object.__setattr__(self, "_Settings_start_time", None)
|
1403
|
-
|
1404
|
-
# done with init, use self.update() to update attributes from now on
|
1405
|
-
self.__initialized = True
|
1406
|
-
|
1407
|
-
# todo? freeze settings to prevent accidental changes
|
1408
|
-
# self.freeze()
|
1409
|
-
|
1410
|
-
def __str__(self) -> str:
|
1411
|
-
# get attributes that are instances of the Property class:
|
1412
|
-
representation = {
|
1413
|
-
k: v.value for k, v in self.__dict__.items() if isinstance(v, Property)
|
1414
|
-
}
|
1415
|
-
return f"<Settings {_redact_dict(representation)}>"
|
1416
|
-
|
1417
|
-
def __repr__(self) -> str:
|
1418
|
-
# private attributes
|
1419
|
-
private = {k: v for k, v in self.__dict__.items() if k.startswith("_Settings")}
|
1420
|
-
# get attributes that are instances of the Property class:
|
1421
|
-
attributes = {
|
1422
|
-
k: f"<Property value={v.value} source={v.source}>"
|
1423
|
-
for k, v in self.__dict__.items()
|
1424
|
-
if isinstance(v, Property)
|
1425
|
-
}
|
1426
|
-
representation = {**private, **attributes}
|
1427
|
-
return f"<Settings {representation}>"
|
818
|
+
@computed_field # type: ignore[prop-decorator]
|
819
|
+
@property
|
820
|
+
def sync_file(self) -> str:
|
821
|
+
"""Path to the append-only binary transaction log file."""
|
822
|
+
return _path_convert(self.sync_dir, f"run-{self.run_id}.wandb")
|
1428
823
|
|
1429
|
-
|
1430
|
-
|
824
|
+
@computed_field # type: ignore[prop-decorator]
|
825
|
+
@property
|
826
|
+
def sync_symlink_latest(self) -> str:
|
827
|
+
return _path_convert(self.wandb_dir, "latest-run")
|
1431
828
|
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
new = Settings()
|
1437
|
-
# update properties that have deps or are dependent on in the topologically-sorted order
|
1438
|
-
for prop in self.__modification_order:
|
1439
|
-
new.update({prop: attributes[prop]._value}, source=attributes[prop].source)
|
1440
|
-
attributes.pop(prop)
|
1441
|
-
|
1442
|
-
# update the remaining attributes
|
1443
|
-
for k, v in attributes.items():
|
1444
|
-
# make sure to use the raw property value (v._value),
|
1445
|
-
# not the potential result of runtime hooks applied to it (v.value)
|
1446
|
-
new.update({k: v._value}, source=v.source)
|
1447
|
-
new.unfreeze()
|
1448
|
-
|
1449
|
-
return new
|
1450
|
-
|
1451
|
-
def __deepcopy__(self, memo: dict) -> "Settings":
|
1452
|
-
return self.__copy__()
|
1453
|
-
|
1454
|
-
# attribute access methods
|
1455
|
-
@no_type_check # this is a hack to make mypy happy
|
1456
|
-
def __getattribute__(self, name: str) -> Any:
|
1457
|
-
"""Expose `attribute.value` if `attribute` is a Property."""
|
1458
|
-
item = object.__getattribute__(self, name)
|
1459
|
-
if isinstance(item, Property):
|
1460
|
-
return item.value
|
1461
|
-
return item
|
1462
|
-
|
1463
|
-
def __setattr__(self, key: str, value: Any) -> None:
|
1464
|
-
if "_Settings__initialized" in self.__dict__ and self.__initialized:
|
1465
|
-
raise TypeError(f"Please use update() to update attribute `{key}` value")
|
1466
|
-
object.__setattr__(self, key, value)
|
1467
|
-
|
1468
|
-
def __iter__(self) -> Iterable:
|
1469
|
-
return iter(self.to_dict())
|
1470
|
-
|
1471
|
-
def copy(self) -> "Settings":
|
1472
|
-
return self.__copy__()
|
1473
|
-
|
1474
|
-
# implement the Mapping interface
|
1475
|
-
def keys(self) -> Iterable[str]:
|
1476
|
-
return self.to_dict().keys()
|
1477
|
-
|
1478
|
-
@no_type_check # this is a hack to make mypy happy
|
1479
|
-
def __getitem__(self, name: str) -> Any:
|
1480
|
-
"""Expose attribute.value if attribute is a Property."""
|
1481
|
-
item = object.__getattribute__(self, name)
|
1482
|
-
if isinstance(item, Property):
|
1483
|
-
return item.value
|
1484
|
-
return item
|
1485
|
-
|
1486
|
-
def update(
|
1487
|
-
self,
|
1488
|
-
settings: Optional[Union[Dict[str, Any], "Settings"]] = None,
|
1489
|
-
source: int = Source.OVERRIDE,
|
1490
|
-
**kwargs: Any,
|
1491
|
-
) -> None:
|
1492
|
-
"""Update individual settings."""
|
1493
|
-
if "_Settings__frozen" in self.__dict__ and self.__frozen:
|
1494
|
-
raise TypeError("Settings object is frozen")
|
1495
|
-
|
1496
|
-
if isinstance(settings, Settings):
|
1497
|
-
# If a Settings object is passed, detect the settings that differ
|
1498
|
-
# from defaults, collect them into a dict, and apply them using `source`.
|
1499
|
-
# This comes up in `wandb.init(settings=wandb.Settings(...))` and
|
1500
|
-
# seems like the behavior that the user would expect when calling init that way.
|
1501
|
-
defaults = Settings()
|
1502
|
-
settings_dict = dict()
|
1503
|
-
for k, v in settings.__dict__.items():
|
1504
|
-
if isinstance(v, Property):
|
1505
|
-
if v._value != defaults.__dict__[k]._value:
|
1506
|
-
settings_dict[k] = v._value
|
1507
|
-
# replace with the generated dict
|
1508
|
-
settings = settings_dict
|
1509
|
-
|
1510
|
-
# add kwargs to settings
|
1511
|
-
settings = settings or dict()
|
1512
|
-
# explicit kwargs take precedence over settings
|
1513
|
-
settings = {**settings, **kwargs}
|
1514
|
-
unknown_properties = []
|
1515
|
-
for key in settings.keys():
|
1516
|
-
# only allow updating known Properties
|
1517
|
-
if key not in self.__dict__ or not isinstance(self.__dict__[key], Property):
|
1518
|
-
unknown_properties.append(key)
|
1519
|
-
if unknown_properties:
|
1520
|
-
raise KeyError(f"Unknown settings: {unknown_properties}")
|
1521
|
-
# only if all keys are valid, update them
|
1522
|
-
|
1523
|
-
# store settings to be updated in a dict to preserve stats on preprocessing and validation errors
|
1524
|
-
settings.copy()
|
1525
|
-
|
1526
|
-
# update properties that have deps or are dependent on in the topologically-sorted order
|
1527
|
-
for key in self.__modification_order:
|
1528
|
-
if key in settings:
|
1529
|
-
self.__dict__[key].update(settings.pop(key), source=source)
|
1530
|
-
|
1531
|
-
# update the remaining properties
|
1532
|
-
for key, value in settings.items():
|
1533
|
-
self.__dict__[key].update(value, source)
|
1534
|
-
|
1535
|
-
def items(self) -> ItemsView[str, Any]:
|
1536
|
-
return self.to_dict().items()
|
1537
|
-
|
1538
|
-
def get(self, key: str, default: Optional[Any] = None) -> Any:
|
1539
|
-
return self.to_dict().get(key, default)
|
1540
|
-
|
1541
|
-
def freeze(self) -> None:
|
1542
|
-
object.__setattr__(self, "_Settings__frozen", True)
|
1543
|
-
|
1544
|
-
def unfreeze(self) -> None:
|
1545
|
-
object.__setattr__(self, "_Settings__frozen", False)
|
1546
|
-
|
1547
|
-
def is_frozen(self) -> bool:
|
1548
|
-
return self.__frozen
|
1549
|
-
|
1550
|
-
def to_dict(self) -> Dict[str, Any]:
|
1551
|
-
"""Return a dict representation of the settings."""
|
1552
|
-
# get attributes that are instances of the Property class:
|
1553
|
-
attributes = {
|
1554
|
-
k: v.value for k, v in self.__dict__.items() if isinstance(v, Property)
|
1555
|
-
}
|
1556
|
-
return attributes
|
829
|
+
@computed_field # type: ignore[prop-decorator]
|
830
|
+
@property
|
831
|
+
def timespec(self) -> str:
|
832
|
+
return self._start_datetime
|
1557
833
|
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
settings = wandb_settings_pb2.Settings()
|
1563
|
-
for field in fields(SettingsData):
|
1564
|
-
k = field.name
|
1565
|
-
v = getattr(self, k)
|
1566
|
-
# special case for _stats_open_metrics_filters
|
1567
|
-
if k == "_stats_open_metrics_filters":
|
1568
|
-
if isinstance(v, (list, set, tuple)):
|
1569
|
-
setting = getattr(settings, k)
|
1570
|
-
setting.sequence.value.extend(v)
|
1571
|
-
elif isinstance(v, dict):
|
1572
|
-
setting = getattr(settings, k)
|
1573
|
-
for key, value in v.items():
|
1574
|
-
for kk, vv in value.items():
|
1575
|
-
setting.mapping.value[key].value[kk] = vv
|
1576
|
-
else:
|
1577
|
-
raise TypeError(f"Unsupported type {type(v)} for setting {k}")
|
1578
|
-
continue
|
834
|
+
@computed_field # type: ignore[prop-decorator]
|
835
|
+
@property
|
836
|
+
def wandb_dir(self) -> str:
|
837
|
+
"""Full path to the wandb directory.
|
1579
838
|
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
elif isinstance(v, float):
|
1585
|
-
getattr(settings, k).CopyFrom(DoubleValue(value=v))
|
1586
|
-
elif isinstance(v, str):
|
1587
|
-
getattr(settings, k).CopyFrom(StringValue(value=v))
|
1588
|
-
elif isinstance(v, (list, set, tuple)):
|
1589
|
-
# we only support sequences of strings for now
|
1590
|
-
sequence = getattr(settings, k)
|
1591
|
-
sequence.value.extend(v)
|
1592
|
-
elif isinstance(v, dict):
|
1593
|
-
mapping = getattr(settings, k)
|
1594
|
-
for key, value in v.items():
|
1595
|
-
# we only support dicts with string values for now
|
1596
|
-
mapping.value[key] = value
|
1597
|
-
elif isinstance(v, RunMoment):
|
1598
|
-
getattr(settings, k).CopyFrom(
|
1599
|
-
wandb_settings_pb2.RunMoment(
|
1600
|
-
run=v.run,
|
1601
|
-
value=v.value,
|
1602
|
-
metric=v.metric,
|
1603
|
-
)
|
1604
|
-
)
|
1605
|
-
elif v is None:
|
1606
|
-
# None is the default value for all settings, so we don't need to set it,
|
1607
|
-
# i.e. None means that the value was not set.
|
1608
|
-
pass
|
1609
|
-
else:
|
1610
|
-
raise TypeError(f"Unsupported type {type(v)} for setting {k}")
|
1611
|
-
# TODO: store property sources in the protobuf so that we can reconstruct the
|
1612
|
-
# settings object from the protobuf
|
1613
|
-
return settings
|
1614
|
-
|
1615
|
-
# apply settings from different sources
|
1616
|
-
# TODO(dd): think about doing some|all of that at init
|
1617
|
-
def _apply_settings(
|
1618
|
-
self,
|
1619
|
-
settings: "Settings",
|
1620
|
-
_logger: Optional[_EarlyLogger] = None,
|
1621
|
-
) -> None:
|
1622
|
-
"""Apply settings from a Settings object."""
|
1623
|
-
if _logger is not None:
|
1624
|
-
_logger.info(f"Applying settings from {settings}")
|
1625
|
-
attributes = {
|
1626
|
-
k: v for k, v in settings.__dict__.items() if isinstance(v, Property)
|
1627
|
-
}
|
1628
|
-
# update properties that have deps or are dependent on in the topologically-sorted order
|
1629
|
-
for prop in self.__modification_order:
|
1630
|
-
self.update({prop: attributes[prop]._value}, source=attributes[prop].source)
|
1631
|
-
attributes.pop(prop)
|
1632
|
-
# update the remaining properties
|
1633
|
-
for k, v in attributes.items():
|
1634
|
-
# note that only the same/higher priority settings are propagated
|
1635
|
-
self.update({k: v._value}, source=v.source)
|
839
|
+
The setting exposed to users as `dir=` or `WANDB_DIR` is the `root_dir`.
|
840
|
+
We add the `__stage_dir__` to it to get the full `wandb_dir`
|
841
|
+
"""
|
842
|
+
root_dir = self.root_dir or ""
|
1636
843
|
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
config: Dict[str, Any] = dict()
|
1643
|
-
for k in parser[section]:
|
1644
|
-
config[k] = parser[section][k]
|
1645
|
-
# TODO (cvp): we didn't do this in the old cli, but it seems necessary
|
1646
|
-
if k == "ignore_globs":
|
1647
|
-
config[k] = config[k].split(",")
|
1648
|
-
return config
|
844
|
+
# We use the hidden version if it already exists, otherwise non-hidden.
|
845
|
+
if os.path.exists(os.path.join(root_dir, ".wandb")):
|
846
|
+
__stage_dir__ = ".wandb" + os.sep
|
847
|
+
else:
|
848
|
+
__stage_dir__ = "wandb" + os.sep
|
1649
849
|
|
1650
|
-
|
1651
|
-
if
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
def _apply_config_files(self, _logger: Optional[_EarlyLogger] = None) -> None:
|
1657
|
-
# TODO(jhr): permit setting of config in system and workspace
|
1658
|
-
if self.settings_system is not None:
|
1659
|
-
if _logger is not None:
|
1660
|
-
_logger.info(f"Loading settings from {self.settings_system}")
|
1661
|
-
self.update(
|
1662
|
-
self._load_config_file(self.settings_system),
|
1663
|
-
source=Source.SYSTEM,
|
850
|
+
path = os.path.join(root_dir, __stage_dir__)
|
851
|
+
if not os.access(root_dir or ".", os.W_OK):
|
852
|
+
termwarn(
|
853
|
+
f"Path {path} wasn't writable, using system temp directory.",
|
854
|
+
repeat=False,
|
1664
855
|
)
|
1665
|
-
|
1666
|
-
|
1667
|
-
_logger.info(f"Loading settings from {self.settings_workspace}")
|
1668
|
-
self.update(
|
1669
|
-
self._load_config_file(self.settings_workspace),
|
1670
|
-
source=Source.WORKSPACE,
|
856
|
+
path = os.path.join(
|
857
|
+
tempfile.gettempdir(), __stage_dir__ or ("wandb" + os.sep)
|
1671
858
|
)
|
1672
859
|
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
860
|
+
return os.path.expanduser(path)
|
861
|
+
|
862
|
+
# Methods to collect and update settings from different sources.
|
863
|
+
#
|
864
|
+
# The Settings class does not track the source of the settings,
|
865
|
+
# so it is up to the developer to ensure that the settings are applied
|
866
|
+
# in the correct order. Most of the updates are done in
|
867
|
+
# wandb/sdk/wandb_setup.py::_WandbSetup__WandbSetup._settings_setup.
|
868
|
+
|
869
|
+
def update_from_system_config_file(self):
|
870
|
+
"""Update settings from the system config file."""
|
871
|
+
if not self.settings_system or not os.path.exists(self.settings_system):
|
872
|
+
return
|
873
|
+
for key, value in self._load_config_file(self.settings_system).items():
|
874
|
+
if value is not None:
|
875
|
+
setattr(self, key, value)
|
876
|
+
|
877
|
+
def update_from_workspace_config_file(self):
|
878
|
+
"""Update settings from the workspace config file."""
|
879
|
+
if not self.settings_workspace or not os.path.exists(self.settings_workspace):
|
880
|
+
return
|
881
|
+
for key, value in self._load_config_file(self.settings_workspace).items():
|
882
|
+
if value is not None:
|
883
|
+
setattr(self, key, value)
|
884
|
+
|
885
|
+
def update_from_env_vars(self, environ: dict[str, Any]):
|
886
|
+
"""Update settings from environment variables."""
|
1678
887
|
env_prefix: str = "WANDB_"
|
888
|
+
private_env_prefix: str = env_prefix + "_"
|
1679
889
|
special_env_var_names = {
|
1680
|
-
"
|
1681
|
-
"
|
1682
|
-
"WANDB_SERVICE_TRANSPORT": "_service_transport",
|
890
|
+
"WANDB_DISABLE_SERVICE": "x_disable_service",
|
891
|
+
"WANDB_SERVICE_TRANSPORT": "x_service_transport",
|
1683
892
|
"WANDB_DIR": "root_dir",
|
1684
893
|
"WANDB_NAME": "run_name",
|
1685
894
|
"WANDB_NOTES": "run_notes",
|
1686
895
|
"WANDB_TAGS": "run_tags",
|
1687
896
|
"WANDB_JOB_TYPE": "run_job_type",
|
1688
|
-
"WANDB_HTTP_TIMEOUT": "
|
1689
|
-
"WANDB_FILE_PUSHER_TIMEOUT": "
|
897
|
+
"WANDB_HTTP_TIMEOUT": "x_graphql_timeout_seconds",
|
898
|
+
"WANDB_FILE_PUSHER_TIMEOUT": "x_file_transfer_timeout_seconds",
|
1690
899
|
"WANDB_USER_EMAIL": "email",
|
1691
900
|
}
|
1692
901
|
env = dict()
|
@@ -1696,6 +905,8 @@ class Settings(SettingsData):
|
|
1696
905
|
|
1697
906
|
if setting in special_env_var_names:
|
1698
907
|
key = special_env_var_names[setting]
|
908
|
+
elif setting.startswith(private_env_prefix):
|
909
|
+
key = "x_" + setting[len(private_env_prefix) :].lower()
|
1699
910
|
else:
|
1700
911
|
# otherwise, strip the prefix and convert to lowercase
|
1701
912
|
key = setting[len(env_prefix) :].lower()
|
@@ -1704,52 +915,37 @@ class Settings(SettingsData):
|
|
1704
915
|
if key in ("ignore_globs", "run_tags"):
|
1705
916
|
value = value.split(",")
|
1706
917
|
env[key] = value
|
1707
|
-
elif _logger is not None:
|
1708
|
-
_logger.warning(f"Unknown environment variable: {setting}")
|
1709
918
|
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
)
|
1714
|
-
self.update(env, source=Source.ENV)
|
1715
|
-
|
1716
|
-
def _infer_settings_from_environment(
|
1717
|
-
self, _logger: Optional[_EarlyLogger] = None
|
1718
|
-
) -> None:
|
1719
|
-
"""Modify settings based on environment (for runs and cli)."""
|
1720
|
-
settings: Dict[str, Union[bool, str, Sequence, None]] = dict()
|
1721
|
-
# disable symlinks if on windows (requires admin or developer setup)
|
1722
|
-
settings["symlink"] = True
|
1723
|
-
if self._windows:
|
1724
|
-
settings["symlink"] = False
|
1725
|
-
|
1726
|
-
# TODO(jhr): this needs to be moved last in setting up settings ?
|
1727
|
-
# (dd): loading order does not matter as long as source is set correctly
|
919
|
+
for key, value in env.items():
|
920
|
+
if value is not None:
|
921
|
+
setattr(self, key, value)
|
1728
922
|
|
923
|
+
def update_from_system_environment(self):
|
924
|
+
"""Update settings from the system environment."""
|
1729
925
|
# For code saving, only allow env var override if value from server is true, or
|
1730
926
|
# if no preference was specified.
|
1731
927
|
if (self.save_code is True or self.save_code is None) and (
|
1732
|
-
os.getenv(
|
1733
|
-
or os.getenv(
|
928
|
+
os.getenv(env.SAVE_CODE) is not None
|
929
|
+
or os.getenv(env.DISABLE_CODE) is not None
|
1734
930
|
):
|
1735
|
-
|
931
|
+
self.save_code = env.should_save_code()
|
1736
932
|
|
1737
|
-
|
933
|
+
self.disable_git = env.disable_git()
|
1738
934
|
|
1739
935
|
# Attempt to get notebook information if not already set by the user
|
1740
936
|
if self._jupyter and (self.notebook_name is None or self.notebook_name == ""):
|
1741
937
|
meta = wandb.jupyter.notebook_metadata(self.silent) # type: ignore
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
938
|
+
self.x_jupyter_path = meta.get("path")
|
939
|
+
self.x_jupyter_name = meta.get("name")
|
940
|
+
self.x_jupyter_root = meta.get("root")
|
1745
941
|
elif (
|
1746
942
|
self._jupyter
|
1747
943
|
and self.notebook_name is not None
|
1748
944
|
and os.path.exists(self.notebook_name)
|
1749
945
|
):
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
946
|
+
self.x_jupyter_path = self.notebook_name
|
947
|
+
self.x_jupyter_name = self.notebook_name
|
948
|
+
self.x_jupyter_root = os.getcwd()
|
1753
949
|
elif self._jupyter:
|
1754
950
|
wandb.termwarn(
|
1755
951
|
"WANDB_NOTEBOOK_NAME should be a path to a notebook file, "
|
@@ -1759,244 +955,266 @@ class Settings(SettingsData):
|
|
1759
955
|
# host and username are populated by apply_env_vars if corresponding env
|
1760
956
|
# vars exist -- but if they don't, we'll fill them in here
|
1761
957
|
if self.host is None:
|
1762
|
-
|
958
|
+
self.host = socket.gethostname() # type: ignore
|
1763
959
|
|
1764
960
|
if self.username is None:
|
1765
961
|
try: # type: ignore
|
1766
|
-
|
962
|
+
self.username = getpass.getuser()
|
1767
963
|
except KeyError:
|
1768
964
|
# getuser() could raise KeyError in restricted environments like
|
1769
965
|
# chroot jails or docker containers. Return user id in these cases.
|
1770
|
-
|
966
|
+
self.username = str(os.getuid())
|
1771
967
|
|
1772
968
|
_executable = (
|
1773
|
-
self.
|
1774
|
-
or os.environ.get(
|
969
|
+
self.x_executable
|
970
|
+
or os.environ.get(env._EXECUTABLE)
|
1775
971
|
or sys.executable
|
1776
972
|
or shutil.which("python3")
|
1777
973
|
or "python3"
|
1778
974
|
)
|
1779
|
-
|
975
|
+
self.x_executable = _executable
|
1780
976
|
|
1781
|
-
|
977
|
+
self.docker = env.get_docker(util.image_id_from_k8s())
|
1782
978
|
|
1783
|
-
#
|
1784
|
-
if
|
1785
|
-
|
1786
|
-
settings["_cuda"] = f.read().split(" ")[-1].strip()
|
1787
|
-
if not self._jupyter:
|
1788
|
-
settings["_args"] = sys.argv[1:]
|
1789
|
-
settings["_os"] = platform.platform(aliased=True)
|
1790
|
-
settings["_python"] = platform.python_version()
|
1791
|
-
|
1792
|
-
if _logger is not None:
|
1793
|
-
_logger.info(
|
1794
|
-
f"Inferring settings from compute environment: {_redact_dict(settings)}"
|
1795
|
-
)
|
979
|
+
# proceed if not in CLI mode
|
980
|
+
if self.x_cli_only_mode:
|
981
|
+
return
|
1796
982
|
|
1797
|
-
self.
|
983
|
+
program = self.program or self._get_program()
|
1798
984
|
|
1799
|
-
def _infer_run_settings_from_environment(
|
1800
|
-
self,
|
1801
|
-
_logger: Optional[_EarlyLogger] = None,
|
1802
|
-
) -> None:
|
1803
|
-
"""Modify settings based on environment (for runs only)."""
|
1804
|
-
# If there's not already a program file, infer it now.
|
1805
|
-
settings: Dict[str, Union[bool, str, None]] = dict()
|
1806
|
-
program = self.program or _get_program()
|
1807
985
|
if program is not None:
|
1808
986
|
repo = GitRepo()
|
1809
987
|
root = repo.root or os.getcwd()
|
1810
988
|
|
1811
|
-
program_relpath = self.program_relpath or _get_program_relpath(
|
1812
|
-
program,
|
989
|
+
self.program_relpath = self.program_relpath or self._get_program_relpath(
|
990
|
+
program, root
|
1813
991
|
)
|
1814
|
-
settings["program_relpath"] = program_relpath
|
1815
992
|
program_abspath = os.path.abspath(
|
1816
993
|
os.path.join(root, os.path.relpath(os.getcwd(), root), program)
|
1817
994
|
)
|
1818
995
|
if os.path.exists(program_abspath):
|
1819
|
-
|
996
|
+
self.program_abspath = program_abspath
|
1820
997
|
else:
|
1821
998
|
program = "<python with no main file>"
|
1822
999
|
|
1823
|
-
|
1000
|
+
self.program = program
|
1824
1001
|
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1002
|
+
def update_from_dict(self, settings: dict[str, Any]) -> None:
|
1003
|
+
"""Update settings from a dictionary."""
|
1004
|
+
for key, value in dict(settings).items():
|
1005
|
+
if value is not None:
|
1006
|
+
setattr(self, key, value)
|
1829
1007
|
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
if _logger:
|
1836
|
-
_logger.info(f"Applying setup settings: {_redact_dict(setup_settings)}")
|
1837
|
-
self.update(setup_settings, source=Source.SETUP)
|
1838
|
-
|
1839
|
-
def _apply_user(
|
1840
|
-
self, user_settings: Dict[str, Any], _logger: Optional[_EarlyLogger] = None
|
1841
|
-
) -> None:
|
1842
|
-
if _logger:
|
1843
|
-
_logger.info(f"Applying user settings: {_redact_dict(user_settings)}")
|
1844
|
-
self.update(user_settings, source=Source.USER)
|
1845
|
-
|
1846
|
-
def _apply_init(self, init_settings: Dict[str, Union[str, int, None]]) -> None:
|
1847
|
-
# pop magic from init settings
|
1848
|
-
init_settings.pop("magic", None)
|
1849
|
-
|
1850
|
-
# prevent setting project, entity if in sweep
|
1851
|
-
# TODO(jhr): these should be locked elements in the future
|
1852
|
-
if self.sweep_id:
|
1853
|
-
for key in ("project", "entity", "id"):
|
1854
|
-
val = init_settings.pop(key, None)
|
1855
|
-
if val:
|
1856
|
-
wandb.termwarn(
|
1857
|
-
f"Ignored wandb.init() arg {key} when running a sweep."
|
1858
|
-
)
|
1859
|
-
if self.launch:
|
1860
|
-
if self.project is not None and init_settings.pop("project", None):
|
1861
|
-
wandb.termwarn(
|
1862
|
-
"Project is ignored when running from wandb launch context. "
|
1863
|
-
"Ignored wandb.init() arg project when running running from launch.",
|
1864
|
-
)
|
1865
|
-
for key in ("entity", "id"):
|
1866
|
-
# Init settings cannot override launch settings.
|
1867
|
-
if init_settings.pop(key, None):
|
1868
|
-
wandb.termwarn(
|
1869
|
-
"Project, entity and id are ignored when running from wandb launch context. "
|
1870
|
-
f"Ignored wandb.init() arg {key} when running running from launch.",
|
1871
|
-
)
|
1008
|
+
def update_from_settings(self, settings: Settings) -> None:
|
1009
|
+
"""Update settings from another instance of `Settings`."""
|
1010
|
+
d = {field: getattr(settings, field) for field in settings.model_fields_set}
|
1011
|
+
if d:
|
1012
|
+
self.update_from_dict(d)
|
1872
1013
|
|
1873
|
-
|
1874
|
-
param_map = dict(
|
1875
|
-
name="run_name",
|
1876
|
-
id="run_id",
|
1877
|
-
tags="run_tags",
|
1878
|
-
group="run_group",
|
1879
|
-
job_type="run_job_type",
|
1880
|
-
notes="run_notes",
|
1881
|
-
dir="root_dir",
|
1882
|
-
sweep_id="sweep_id",
|
1883
|
-
)
|
1884
|
-
init_settings = {
|
1885
|
-
param_map.get(k, k): v for k, v in init_settings.items() if v is not None
|
1886
|
-
}
|
1887
|
-
# fun logic to convert the resume init arg
|
1888
|
-
if init_settings.get("resume"):
|
1889
|
-
if isinstance(init_settings["resume"], str):
|
1890
|
-
if init_settings["resume"] not in ("allow", "must", "never", "auto"):
|
1891
|
-
if init_settings.get("run_id") is None:
|
1892
|
-
# TODO: deprecate or don't support
|
1893
|
-
init_settings["run_id"] = init_settings["resume"]
|
1894
|
-
init_settings["resume"] = "allow"
|
1895
|
-
elif init_settings["resume"] is True:
|
1896
|
-
# todo: add deprecation warning, switch to literal strings for resume
|
1897
|
-
init_settings["resume"] = "auto"
|
1898
|
-
|
1899
|
-
# update settings
|
1900
|
-
self.update(init_settings, source=Source.INIT)
|
1901
|
-
self._handle_fork_logic()
|
1902
|
-
self._handle_rewind_logic()
|
1903
|
-
self._handle_resume_logic()
|
1904
|
-
|
1905
|
-
def _handle_fork_logic(self) -> None:
|
1906
|
-
if self.fork_from is None:
|
1907
|
-
return
|
1014
|
+
# Helper methods.
|
1908
1015
|
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1913
|
-
|
1914
|
-
|
1016
|
+
def to_proto(self) -> wandb_settings_pb2.Settings:
|
1017
|
+
"""Generate a protobuf representation of the settings."""
|
1018
|
+
settings_proto = wandb_settings_pb2.Settings()
|
1019
|
+
for k, v in self.model_dump(exclude_none=True).items():
|
1020
|
+
# special case for x_stats_open_metrics_filters
|
1021
|
+
if k == "x_stats_open_metrics_filters":
|
1022
|
+
if isinstance(v, (list, set, tuple)):
|
1023
|
+
setting = getattr(settings_proto, k)
|
1024
|
+
setting.sequence.value.extend(v)
|
1025
|
+
elif isinstance(v, dict):
|
1026
|
+
setting = getattr(settings_proto, k)
|
1027
|
+
for key, value in v.items():
|
1028
|
+
for kk, vv in value.items():
|
1029
|
+
setting.mapping.value[key].value[kk] = vv
|
1030
|
+
else:
|
1031
|
+
raise TypeError(f"Unsupported type {type(v)} for setting {k}")
|
1032
|
+
continue
|
1915
1033
|
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
1034
|
+
if isinstance(v, bool):
|
1035
|
+
getattr(settings_proto, k).CopyFrom(BoolValue(value=v))
|
1036
|
+
elif isinstance(v, int):
|
1037
|
+
getattr(settings_proto, k).CopyFrom(Int32Value(value=v))
|
1038
|
+
elif isinstance(v, float):
|
1039
|
+
getattr(settings_proto, k).CopyFrom(DoubleValue(value=v))
|
1040
|
+
elif isinstance(v, str):
|
1041
|
+
getattr(settings_proto, k).CopyFrom(StringValue(value=v))
|
1042
|
+
elif isinstance(v, (list, set, tuple)):
|
1043
|
+
# we only support sequences of strings for now
|
1044
|
+
sequence = getattr(settings_proto, k)
|
1045
|
+
sequence.value.extend(v)
|
1046
|
+
elif isinstance(v, dict):
|
1047
|
+
mapping = getattr(settings_proto, k)
|
1048
|
+
for key, value in v.items():
|
1049
|
+
# we only support dicts with string values for now
|
1050
|
+
mapping.value[key] = value
|
1051
|
+
elif isinstance(v, RunMoment):
|
1052
|
+
getattr(settings_proto, k).CopyFrom(
|
1053
|
+
wandb_settings_pb2.RunMoment(
|
1054
|
+
run=v.run,
|
1055
|
+
value=v.value,
|
1056
|
+
metric=v.metric,
|
1057
|
+
)
|
1058
|
+
)
|
1059
|
+
elif v is None:
|
1060
|
+
# None means that the setting value was not set.
|
1061
|
+
pass
|
1062
|
+
else:
|
1063
|
+
raise TypeError(f"Unsupported type {type(v)} for setting {k}")
|
1919
1064
|
|
1920
|
-
|
1921
|
-
wandb.termwarn(
|
1922
|
-
"Both `run_id` and `resume_from` have been specified with different ids. "
|
1923
|
-
"`run_id` will be ignored."
|
1924
|
-
)
|
1925
|
-
self.update({"run_id": self.resume_from.run}, source=Source.INIT)
|
1065
|
+
return settings_proto
|
1926
1066
|
|
1927
|
-
def
|
1067
|
+
def handle_resume_logic(self):
|
1068
|
+
"""Handle logic for resuming runs."""
|
1928
1069
|
# handle auto resume logic
|
1929
1070
|
if self.resume == "auto":
|
1930
1071
|
if os.path.exists(self.resume_fname):
|
1931
1072
|
with open(self.resume_fname) as f:
|
1932
1073
|
resume_run_id = json.load(f)["run_id"]
|
1933
1074
|
if self.run_id is None:
|
1934
|
-
self.
|
1075
|
+
self.run_id = resume_run_id
|
1935
1076
|
elif self.run_id != resume_run_id:
|
1936
1077
|
wandb.termwarn(
|
1937
1078
|
"Tried to auto resume run with "
|
1938
1079
|
f"id {resume_run_id} but id {self.run_id} is set.",
|
1939
1080
|
)
|
1081
|
+
if self.run_id is None:
|
1082
|
+
self.run_id = generate_id()
|
1940
1083
|
|
1941
|
-
|
1942
|
-
# persist our run id in case of failure
|
1943
|
-
# check None for mypy
|
1084
|
+
# persist run_id in case of failure
|
1944
1085
|
if self.resume == "auto" and self.resume_fname is not None:
|
1945
1086
|
filesystem.mkdir_exists_ok(self.wandb_dir)
|
1946
1087
|
with open(self.resume_fname, "w") as f:
|
1947
1088
|
f.write(json.dumps({"run_id": self.run_id}))
|
1948
1089
|
|
1949
|
-
def
|
1950
|
-
|
1951
|
-
login_settings: Dict[str, Any],
|
1952
|
-
_logger: Optional[_EarlyLogger] = None,
|
1953
|
-
) -> None:
|
1954
|
-
key_map = {
|
1955
|
-
"key": "api_key",
|
1956
|
-
"host": "base_url",
|
1957
|
-
"timeout": "login_timeout",
|
1958
|
-
}
|
1090
|
+
def handle_sweep_logic(self):
|
1091
|
+
"""Update settings based on sweep context.
|
1959
1092
|
|
1960
|
-
|
1961
|
-
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1093
|
+
When running a sweep, the project, entity, and run_id are handled externally,
|
1094
|
+
and should be ignored if they are set.
|
1095
|
+
"""
|
1096
|
+
if self.sweep_id is None:
|
1097
|
+
return
|
1098
|
+
|
1099
|
+
for key in ("project", "entity", "run_id"):
|
1100
|
+
value = getattr(self, key)
|
1101
|
+
if value is not None:
|
1102
|
+
wandb.termwarn(f"Ignoring {key} {value!r} when running a sweep.")
|
1103
|
+
setattr(self, key, None)
|
1969
1104
|
|
1970
|
-
|
1971
|
-
|
1105
|
+
def handle_launch_logic(self):
|
1106
|
+
"""Update settings based on launch context.
|
1972
1107
|
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1108
|
+
When running in a launch context, the project, entity, and run_id are handled
|
1109
|
+
externally, and should be ignored if they are set.
|
1110
|
+
"""
|
1111
|
+
if not self.launch:
|
1112
|
+
return
|
1113
|
+
|
1114
|
+
for key in ("project", "entity", "run_id"):
|
1115
|
+
value = getattr(self, key)
|
1116
|
+
if value is not None:
|
1117
|
+
wandb.termwarn(
|
1118
|
+
f"Ignoring {key} {value!r} when running from wandb launch context."
|
1119
|
+
)
|
1120
|
+
setattr(self, key, None)
|
1121
|
+
|
1122
|
+
@staticmethod
|
1123
|
+
def validate_url(url: str) -> None:
|
1124
|
+
"""Validate a URL string."""
|
1125
|
+
url_validator = SchemaValidator(
|
1126
|
+
core_schema.url_schema(
|
1127
|
+
allowed_schemes=["http", "https"],
|
1128
|
+
strict=True,
|
1129
|
+
)
|
1976
1130
|
)
|
1131
|
+
url_validator.validate_python(url)
|
1977
1132
|
|
1978
|
-
def
|
1979
|
-
|
1980
|
-
|
1981
|
-
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
1989
|
-
|
1990
|
-
|
1991
|
-
|
1992
|
-
|
1993
|
-
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
2002
|
-
self.
|
1133
|
+
def _get_program(self) -> str | None:
|
1134
|
+
"""Get the program that started the current process."""
|
1135
|
+
if not self._jupyter:
|
1136
|
+
# If not in a notebook, try to get the program from the environment
|
1137
|
+
# or the __main__ module for scripts run as `python -m ...`.
|
1138
|
+
program = os.getenv(env.PROGRAM)
|
1139
|
+
if program is not None:
|
1140
|
+
return program
|
1141
|
+
try:
|
1142
|
+
import __main__
|
1143
|
+
|
1144
|
+
if __main__.__spec__ is None:
|
1145
|
+
return __main__.__file__
|
1146
|
+
return f"-m {__main__.__spec__.name}"
|
1147
|
+
except (ImportError, AttributeError):
|
1148
|
+
return None
|
1149
|
+
else:
|
1150
|
+
# If in a notebook, try to get the program from the notebook metadata.
|
1151
|
+
if self.notebook_name:
|
1152
|
+
return self.notebook_name
|
1153
|
+
|
1154
|
+
if not self.x_jupyter_path:
|
1155
|
+
return self.program
|
1156
|
+
|
1157
|
+
if self.x_jupyter_path.startswith("fileId="):
|
1158
|
+
return self.x_jupyter_name
|
1159
|
+
else:
|
1160
|
+
return self.x_jupyter_path
|
1161
|
+
|
1162
|
+
@staticmethod
|
1163
|
+
def _get_program_relpath(program: str, root: str | None = None) -> str | None:
|
1164
|
+
"""Get the relative path to the program from the root directory."""
|
1165
|
+
if not program:
|
1166
|
+
return None
|
1167
|
+
|
1168
|
+
root = root or os.getcwd()
|
1169
|
+
if not root:
|
1170
|
+
return None
|
1171
|
+
|
1172
|
+
full_path_to_program = os.path.join(
|
1173
|
+
root, os.path.relpath(os.getcwd(), root), program
|
1174
|
+
)
|
1175
|
+
if os.path.exists(full_path_to_program):
|
1176
|
+
relative_path = os.path.relpath(full_path_to_program, start=root)
|
1177
|
+
if "../" in relative_path:
|
1178
|
+
return None
|
1179
|
+
return relative_path
|
1180
|
+
|
1181
|
+
return None
|
1182
|
+
|
1183
|
+
@staticmethod
|
1184
|
+
def _load_config_file(file_name: str, section: str = "default") -> dict:
|
1185
|
+
"""Load a config file and return the settings for a given section."""
|
1186
|
+
parser = configparser.ConfigParser()
|
1187
|
+
parser.add_section(section)
|
1188
|
+
parser.read(file_name)
|
1189
|
+
config: dict[str, Any] = dict()
|
1190
|
+
for k in parser[section]:
|
1191
|
+
config[k] = parser[section][k]
|
1192
|
+
if k == "ignore_globs":
|
1193
|
+
config[k] = config[k].split(",")
|
1194
|
+
return config
|
1195
|
+
|
1196
|
+
def _project_url_base(self) -> str:
|
1197
|
+
"""Construct the base URL for the project."""
|
1198
|
+
if not all([self.entity, self.project]):
|
1199
|
+
return ""
|
1200
|
+
|
1201
|
+
app_url = util.app_url(self.base_url)
|
1202
|
+
return f"{app_url}/{quote(self.entity or '')}/{quote(self.project or '')}"
|
1203
|
+
|
1204
|
+
def _get_url_query_string(self) -> str:
|
1205
|
+
"""Construct the query string for project, run, and sweep URLs."""
|
1206
|
+
# TODO: remove dependency on Api()
|
1207
|
+
if Api().settings().get("anonymous") != "true":
|
1208
|
+
return ""
|
1209
|
+
|
1210
|
+
api_key = apikey.api_key(settings=self)
|
1211
|
+
|
1212
|
+
return f"?{urlencode({'apiKey': api_key})}"
|
1213
|
+
|
1214
|
+
@staticmethod
|
1215
|
+
def _runmoment_preprocessor(val: RunMoment | str | None) -> RunMoment | None:
|
1216
|
+
"""Preprocess the setting for forking or resuming a run."""
|
1217
|
+
if isinstance(val, RunMoment) or val is None:
|
1218
|
+
return val
|
1219
|
+
elif isinstance(val, str):
|
1220
|
+
return RunMoment.from_uri(val)
|