wandb 0.19.8__py3-none-musllinux_1_2_aarch64.whl → 0.19.9__py3-none-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. wandb/__init__.py +5 -1
  2. wandb/__init__.pyi +12 -8
  3. wandb/_pydantic/__init__.py +23 -0
  4. wandb/_pydantic/base.py +113 -0
  5. wandb/_pydantic/v1_compat.py +262 -0
  6. wandb/apis/paginator.py +82 -38
  7. wandb/apis/public/api.py +10 -64
  8. wandb/apis/public/artifacts.py +73 -17
  9. wandb/apis/public/files.py +2 -2
  10. wandb/apis/public/projects.py +3 -2
  11. wandb/apis/public/reports.py +2 -2
  12. wandb/apis/public/runs.py +19 -11
  13. wandb/bin/gpu_stats +0 -0
  14. wandb/bin/wandb-core +0 -0
  15. wandb/integration/metaflow/metaflow.py +19 -17
  16. wandb/integration/sacred/__init__.py +1 -1
  17. wandb/jupyter.py +18 -15
  18. wandb/proto/v3/wandb_internal_pb2.py +7 -3
  19. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  20. wandb/proto/v3/wandb_telemetry_pb2.py +4 -4
  21. wandb/proto/v4/wandb_internal_pb2.py +3 -3
  22. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  23. wandb/proto/v4/wandb_telemetry_pb2.py +4 -4
  24. wandb/proto/v5/wandb_internal_pb2.py +3 -3
  25. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  26. wandb/proto/v5/wandb_telemetry_pb2.py +4 -4
  27. wandb/proto/wandb_deprecated.py +2 -0
  28. wandb/sdk/artifacts/_graphql_fragments.py +18 -20
  29. wandb/sdk/artifacts/_validators.py +1 -0
  30. wandb/sdk/artifacts/artifact.py +70 -36
  31. wandb/sdk/artifacts/artifact_saver.py +16 -2
  32. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +23 -2
  33. wandb/sdk/data_types/audio.py +1 -3
  34. wandb/sdk/data_types/base_types/media.py +11 -4
  35. wandb/sdk/data_types/image.py +44 -25
  36. wandb/sdk/data_types/molecule.py +1 -5
  37. wandb/sdk/data_types/object_3d.py +2 -1
  38. wandb/sdk/data_types/saved_model.py +7 -9
  39. wandb/sdk/data_types/video.py +1 -4
  40. wandb/{apis/public → sdk/internal}/_generated/__init__.py +0 -6
  41. wandb/sdk/internal/_generated/base.py +226 -0
  42. wandb/{apis/public → sdk/internal}/_generated/server_features_query.py +3 -3
  43. wandb/{apis/public → sdk/internal}/_generated/typing_compat.py +1 -1
  44. wandb/sdk/internal/internal_api.py +138 -47
  45. wandb/sdk/internal/sender.py +2 -0
  46. wandb/sdk/internal/sender_config.py +8 -11
  47. wandb/sdk/internal/settings_static.py +24 -2
  48. wandb/sdk/lib/apikey.py +15 -16
  49. wandb/sdk/lib/run_moment.py +4 -6
  50. wandb/sdk/lib/wb_logging.py +161 -0
  51. wandb/sdk/wandb_config.py +44 -43
  52. wandb/sdk/wandb_init.py +141 -79
  53. wandb/sdk/wandb_metadata.py +107 -91
  54. wandb/sdk/wandb_run.py +152 -44
  55. wandb/sdk/wandb_settings.py +403 -201
  56. wandb/sdk/wandb_setup.py +3 -1
  57. {wandb-0.19.8.dist-info → wandb-0.19.9.dist-info}/METADATA +3 -3
  58. {wandb-0.19.8.dist-info → wandb-0.19.9.dist-info}/RECORD +642 -638
  59. wandb/apis/public/_generated/base.py +0 -128
  60. /wandb/{apis/public → sdk/internal}/_generated/enums.py +0 -0
  61. /wandb/{apis/public → sdk/internal}/_generated/input_types.py +0 -0
  62. /wandb/{apis/public → sdk/internal}/_generated/operations.py +0 -0
  63. {wandb-0.19.8.dist-info → wandb-0.19.9.dist-info}/WHEEL +0 -0
  64. {wandb-0.19.8.dist-info → wandb-0.19.9.dist-info}/entry_points.txt +0 -0
  65. {wandb-0.19.8.dist-info → wandb-0.19.9.dist-info}/licenses/LICENSE +0 -0
@@ -11,30 +11,28 @@ import re
11
11
  import shutil
12
12
  import socket
13
13
  import sys
14
- import tempfile
15
14
  from datetime import datetime
16
- from typing import Any, Literal, Sequence
17
- from urllib.parse import quote, unquote, urlencode
18
15
 
19
- if sys.version_info >= (3, 11):
20
- from typing import Self
21
- else:
22
- from typing_extensions import Self
16
+ # Optional and Union are used for type hinting instead of | because
17
+ # the latter is not supported in pydantic<2.6 and Python<3.10.
18
+ # Dict, List, and Tuple are used for backwards compatibility
19
+ # with pydantic v1 and Python<3.9.
20
+ from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, Union
21
+ from urllib.parse import quote, unquote, urlencode
23
22
 
24
23
  from google.protobuf.wrappers_pb2 import BoolValue, DoubleValue, Int32Value, StringValue
25
- from pydantic import (
24
+ from pydantic import BaseModel, ConfigDict, Field
25
+ from typing_extensions import Self
26
+
27
+ import wandb
28
+ from wandb import env, termwarn, util
29
+ from wandb._pydantic import (
30
+ IS_PYDANTIC_V2,
26
31
  AliasChoices,
27
- BaseModel,
28
- ConfigDict,
29
- Field,
30
32
  computed_field,
31
33
  field_validator,
32
34
  model_validator,
33
35
  )
34
- from pydantic_core import SchemaValidator, core_schema
35
-
36
- import wandb
37
- from wandb import env, termwarn, util
38
36
  from wandb.errors import UsageError
39
37
  from wandb.proto import wandb_settings_pb2
40
38
 
@@ -42,6 +40,117 @@ from .lib import apikey, credentials, ipython
42
40
  from .lib.gitlib import GitRepo
43
41
  from .lib.run_moment import RunMoment
44
42
 
43
+ validate_url: Callable[[str], None]
44
+
45
+ if IS_PYDANTIC_V2:
46
+ from pydantic_core import SchemaValidator, core_schema
47
+
48
+ def validate_url(url: str) -> None:
49
+ """Validate a URL string."""
50
+ url_validator = SchemaValidator(
51
+ core_schema.url_schema(
52
+ allowed_schemes=["http", "https"],
53
+ strict=True,
54
+ )
55
+ )
56
+ url_validator.validate_python(url)
57
+ else:
58
+ from pydantic import root_validator
59
+
60
+ def validate_url(url: str) -> None:
61
+ """Validate the base url of the wandb server.
62
+
63
+ param value: URL to validate
64
+
65
+ Based on the Django URLValidator, but with a few additional checks.
66
+
67
+ Copyright (c) Django Software Foundation and individual contributors.
68
+ All rights reserved.
69
+
70
+ Redistribution and use in source and binary forms, with or without modification,
71
+ are permitted provided that the following conditions are met:
72
+
73
+ 1. Redistributions of source code must retain the above copyright notice,
74
+ this list of conditions and the following disclaimer.
75
+
76
+ 2. Redistributions in binary form must reproduce the above copyright
77
+ notice, this list of conditions and the following disclaimer in the
78
+ documentation and/or other materials provided with the distribution.
79
+
80
+ 3. Neither the name of Django nor the names of its contributors may be used
81
+ to endorse or promote products derived from this software without
82
+ specific prior written permission.
83
+
84
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
85
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
86
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
87
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
88
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
89
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
90
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
91
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
92
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
93
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
94
+ """
95
+ from urllib.parse import urlparse, urlsplit
96
+
97
+ if url is None:
98
+ return
99
+
100
+ ul = "\u00a1-\uffff" # Unicode letters range (must not be a raw string).
101
+
102
+ # IP patterns
103
+ ipv4_re = (
104
+ r"(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)"
105
+ r"(?:\.(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)){3}"
106
+ )
107
+ ipv6_re = r"\[[0-9a-f:.]+\]" # (simple regex, validated later)
108
+
109
+ # Host patterns
110
+ hostname_re = (
111
+ r"[a-z" + ul + r"0-9](?:[a-z" + ul + r"0-9-]{0,61}[a-z" + ul + r"0-9])?"
112
+ )
113
+ # Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1
114
+ domain_re = r"(?:\.(?!-)[a-z" + ul + r"0-9-]{1,63}(?<!-))*"
115
+ tld_re = (
116
+ r"\." # dot
117
+ r"(?!-)" # can't start with a dash
118
+ r"(?:[a-z" + ul + "-]{2,63}" # domain label
119
+ r"|xn--[a-z0-9]{1,59})" # or punycode label
120
+ r"(?<!-)" # can't end with a dash
121
+ r"\.?" # may have a trailing dot
122
+ )
123
+ # host_re = "(" + hostname_re + domain_re + tld_re + "|localhost)"
124
+ # todo?: allow hostname to be just a hostname (no tld)?
125
+ host_re = "(" + hostname_re + domain_re + f"({tld_re})?" + "|localhost)"
126
+
127
+ regex = re.compile(
128
+ r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
129
+ r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
130
+ r"(?:" + ipv4_re + "|" + ipv6_re + "|" + host_re + ")"
131
+ r"(?::[0-9]{1,5})?" # port
132
+ r"(?:[/?#][^\s]*)?" # resource path
133
+ r"\Z",
134
+ re.IGNORECASE,
135
+ )
136
+ schemes = {"http", "https"}
137
+ unsafe_chars = frozenset("\t\r\n")
138
+
139
+ scheme = url.split("://")[0].lower()
140
+ split_url = urlsplit(url)
141
+ parsed_url = urlparse(url)
142
+
143
+ if parsed_url.netloc == "":
144
+ raise ValueError(f"Invalid URL: {url}")
145
+ elif unsafe_chars.intersection(url):
146
+ raise ValueError("URL cannot contain unsafe characters")
147
+ elif scheme not in schemes:
148
+ raise ValueError("URL must start with `http(s)://`")
149
+ elif not regex.search(url):
150
+ raise ValueError(f"{url} is not a valid server address")
151
+ elif split_url.hostname is None or len(split_url.hostname) > 253:
152
+ raise ValueError("hostname is invalid")
153
+
45
154
 
46
155
  def _path_convert(*args: str) -> str:
47
156
  """Join path and apply os.path.expanduser to it."""
@@ -86,7 +195,7 @@ class Settings(BaseModel, validate_assignment=True):
86
195
  allow_val_change: bool = False
87
196
  """Flag to allow modification of `Config` values after they've been set."""
88
197
 
89
- anonymous: Literal["allow", "must", "never"] | None = None
198
+ anonymous: Optional[Literal["allow", "must", "never"]] = None
90
199
  """Controls anonymous data logging.
91
200
 
92
201
  Possible values are:
@@ -100,19 +209,19 @@ class Settings(BaseModel, validate_assignment=True):
100
209
  signed-up user account.
101
210
  """
102
211
 
103
- api_key: str | None = None
212
+ api_key: Optional[str] = None
104
213
  """The W&B API key."""
105
214
 
106
- azure_account_url_to_access_key: dict[str, str] | None = None
215
+ azure_account_url_to_access_key: Optional[Dict[str, str]] = None
107
216
  """Mapping of Azure account URLs to their corresponding access keys for Azure integration."""
108
217
 
109
218
  base_url: str = "https://api.wandb.ai"
110
219
  """The URL of the W&B backend for data synchronization."""
111
220
 
112
- code_dir: str | None = None
221
+ code_dir: Optional[str] = None
113
222
  """Directory containing the code to be tracked by W&B."""
114
223
 
115
- config_paths: Sequence[str] | None = None
224
+ config_paths: Optional[Sequence[str]] = None
116
225
  """Paths to files to load configuration from into the `Config` object."""
117
226
 
118
227
  console: Literal["auto", "off", "wrap", "redirect", "wrap_raw", "wrap_emu"] = Field(
@@ -156,57 +265,57 @@ class Settings(BaseModel, validate_assignment=True):
156
265
  disable_job_creation: bool = True
157
266
  """Whether to disable the creation of a job artifact for W&B Launch."""
158
267
 
159
- docker: str | None = None
268
+ docker: Optional[str] = None
160
269
  """The Docker image used to execute the script."""
161
270
 
162
- email: str | None = None
271
+ email: Optional[str] = None
163
272
  """The email address of the user."""
164
273
 
165
- entity: str | None = None
274
+ entity: Optional[str] = None
166
275
  """The W&B entity, such as a user or a team."""
167
276
 
168
- organization: str | None = None
277
+ organization: Optional[str] = None
169
278
  """The W&B organization."""
170
279
 
171
280
  force: bool = False
172
281
  """Whether to pass the `force` flag to `wandb.login()`."""
173
282
 
174
- fork_from: RunMoment | None = None
283
+ fork_from: Optional[RunMoment] = None
175
284
  """Specifies a point in a previous execution of a run to fork from.
176
285
 
177
286
  The point is defined by the run ID, a metric, and its value.
178
287
  Currently, only the metric '_step' is supported.
179
288
  """
180
289
 
181
- git_commit: str | None = None
290
+ git_commit: Optional[str] = None
182
291
  """The git commit hash to associate with the run."""
183
292
 
184
293
  git_remote: str = "origin"
185
294
  """The git remote to associate with the run."""
186
295
 
187
- git_remote_url: str | None = None
296
+ git_remote_url: Optional[str] = None
188
297
  """The URL of the git remote repository."""
189
298
 
190
- git_root: str | None = None
299
+ git_root: Optional[str] = None
191
300
  """Root directory of the git repository."""
192
301
 
193
302
  heartbeat_seconds: int = 30
194
303
  """Interval in seconds between heartbeat signals sent to the W&B servers."""
195
304
 
196
- host: str | None = None
305
+ host: Optional[str] = None
197
306
  """Hostname of the machine running the script."""
198
307
 
199
- http_proxy: str | None = None
308
+ http_proxy: Optional[str] = None
200
309
  """Custom proxy servers for http requests to W&B."""
201
310
 
202
- https_proxy: str | None = None
311
+ https_proxy: Optional[str] = None
203
312
  """Custom proxy servers for https requests to W&B."""
204
313
 
205
314
  # Path to file containing an identity token (JWT) for authentication.
206
- identity_token_file: str | None = None
315
+ identity_token_file: Optional[str] = None
207
316
  """Path to file containing an identity token (JWT) for authentication."""
208
317
 
209
- ignore_globs: tuple[str, ...] = ()
318
+ ignore_globs: Sequence[str] = ()
210
319
  """Unix glob patterns relative to `files_dir` specifying files to exclude from upload."""
211
320
 
212
321
  init_timeout: float = 90.0
@@ -215,10 +324,10 @@ class Settings(BaseModel, validate_assignment=True):
215
324
  insecure_disable_ssl: bool = False
216
325
  """Whether to insecurely disable SSL verification."""
217
326
 
218
- job_name: str | None = None
327
+ job_name: Optional[str] = None
219
328
  """Name of the Launch job running the script."""
220
329
 
221
- job_source: Literal["repo", "artifact", "image"] | None = None
330
+ job_source: Optional[Literal["repo", "artifact", "image"]] = None
222
331
  """Source type for Launch."""
223
332
 
224
333
  label_disable: bool = False
@@ -227,10 +336,10 @@ class Settings(BaseModel, validate_assignment=True):
227
336
  launch: bool = False
228
337
  """Flag to indicate if the run is being launched through W&B Launch."""
229
338
 
230
- launch_config_path: str | None = None
339
+ launch_config_path: Optional[str] = None
231
340
  """Path to the launch configuration file."""
232
341
 
233
- login_timeout: float | None = None
342
+ login_timeout: Optional[float] = None
234
343
  """Time in seconds to wait for login operations before timing out."""
235
344
 
236
345
  mode: Literal["online", "offline", "dryrun", "disabled", "run", "shared"] = Field(
@@ -239,13 +348,13 @@ class Settings(BaseModel, validate_assignment=True):
239
348
  )
240
349
  """The operating mode for W&B logging and synchronization."""
241
350
 
242
- notebook_name: str | None = None
351
+ notebook_name: Optional[str] = None
243
352
  """Name of the notebook if running in a Jupyter-like environment."""
244
353
 
245
- program: str | None = None
354
+ program: Optional[str] = None
246
355
  """Path to the script that created the run, if available."""
247
356
 
248
- program_abspath: str | None = None
357
+ program_abspath: Optional[str] = None
249
358
  """The absolute path from the root repository directory to the script that
250
359
  created the run.
251
360
 
@@ -253,26 +362,39 @@ class Settings(BaseModel, validate_assignment=True):
253
362
  .git directory, if it exists. Otherwise, it's the current working directory.
254
363
  """
255
364
 
256
- program_relpath: str | None = None
365
+ program_relpath: Optional[str] = None
257
366
  """The relative path to the script that created the run."""
258
367
 
259
- project: str | None = None
368
+ project: Optional[str] = None
260
369
  """The W&B project ID."""
261
370
 
262
371
  quiet: bool = False
263
372
  """Flag to suppress non-essential output."""
264
373
 
265
- reinit: bool = False
266
- """Flag to allow reinitialization of a run.
267
-
268
- If not set, when an active run exists, calling `wandb.init()` returns the existing run
269
- instead of creating a new one.
374
+ reinit: Union[
375
+ Literal[
376
+ "default",
377
+ "return_previous",
378
+ "finish_previous",
379
+ ],
380
+ bool,
381
+ ] = "default"
382
+ """What to do when `wandb.init()` is called while a run is active.
383
+
384
+ Options:
385
+ - "default": Use "finish_previous" in notebooks and "return_previous"
386
+ otherwise.
387
+ - "return_previous": Return the active run.
388
+ - "finish_previous": Finish the active run, then return a new one.
389
+
390
+ Can also be a boolean, but this is deprecated. False is the same as
391
+ "return_previous", and True is the same as "finish_previous".
270
392
  """
271
393
 
272
394
  relogin: bool = False
273
395
  """Flag to force a new login attempt."""
274
396
 
275
- resume: Literal["allow", "must", "never", "auto"] | None = None
397
+ resume: Optional[Literal["allow", "must", "never", "auto"]] = None
276
398
  """Specifies the resume behavior for the run.
277
399
 
278
400
  The available options are:
@@ -290,7 +412,7 @@ class Settings(BaseModel, validate_assignment=True):
290
412
  machine.
291
413
  """
292
414
 
293
- resume_from: RunMoment | None = None
415
+ resume_from: Optional[RunMoment] = None
294
416
  """Specifies a point in a previous execution of a run to resume from.
295
417
 
296
418
  The point is defined by the run ID, a metric, and its value.
@@ -309,31 +431,31 @@ class Settings(BaseModel, validate_assignment=True):
309
431
  In particular, this is used to derive the wandb directory and the run directory.
310
432
  """
311
433
 
312
- run_group: str | None = None
434
+ run_group: Optional[str] = None
313
435
  """Group identifier for related runs.
314
436
 
315
437
  Used for grouping runs in the UI.
316
438
  """
317
439
 
318
- run_id: str | None = None
440
+ run_id: Optional[str] = None
319
441
  """The ID of the run."""
320
442
 
321
- run_job_type: str | None = None
443
+ run_job_type: Optional[str] = None
322
444
  """Type of job being run (e.g., training, evaluation)."""
323
445
 
324
- run_name: str | None = None
446
+ run_name: Optional[str] = None
325
447
  """Human-readable name for the run."""
326
448
 
327
- run_notes: str | None = None
449
+ run_notes: Optional[str] = None
328
450
  """Additional notes or description for the run."""
329
451
 
330
- run_tags: tuple[str, ...] | None = None
452
+ run_tags: Optional[Tuple[str, ...]] = None
331
453
  """Tags to associate with the run for organization and filtering."""
332
454
 
333
455
  sagemaker_disable: bool = False
334
456
  """Flag to disable SageMaker-specific functionality."""
335
457
 
336
- save_code: bool | None = None
458
+ save_code: Optional[bool] = None
337
459
  """Whether to save the code associated with the run."""
338
460
 
339
461
  settings_system: str = Field(
@@ -343,10 +465,10 @@ class Settings(BaseModel, validate_assignment=True):
343
465
  )
344
466
  """Path to the system-wide settings file."""
345
467
 
346
- show_colors: bool | None = None
468
+ show_colors: Optional[bool] = None
347
469
  """Whether to use colored output in the console."""
348
470
 
349
- show_emoji: bool | None = None
471
+ show_emoji: Optional[bool] = None
350
472
  """Whether to show emoji in the console output."""
351
473
 
352
474
  show_errors: bool = True
@@ -361,10 +483,10 @@ class Settings(BaseModel, validate_assignment=True):
361
483
  silent: bool = False
362
484
  """Flag to suppress all output."""
363
485
 
364
- start_method: str | None = None
486
+ start_method: Optional[str] = None
365
487
  """Method to use for starting subprocesses."""
366
488
 
367
- strict: bool | None = None
489
+ strict: Optional[bool] = None
368
490
  """Whether to enable strict mode for validation and error checking."""
369
491
 
370
492
  summary_timeout: int = 60
@@ -373,10 +495,10 @@ class Settings(BaseModel, validate_assignment=True):
373
495
  summary_warnings: int = 5 # TODO: kill this with fire
374
496
  """Maximum number of summary warnings to display."""
375
497
 
376
- sweep_id: str | None = None
498
+ sweep_id: Optional[str] = None
377
499
  """Identifier of the sweep this run belongs to."""
378
500
 
379
- sweep_param_path: str | None = None
501
+ sweep_param_path: Optional[str] = None
380
502
  """Path to the sweep parameters configuration."""
381
503
 
382
504
  symlink: bool = Field(
@@ -384,13 +506,13 @@ class Settings(BaseModel, validate_assignment=True):
384
506
  )
385
507
  """Whether to use symlinks (True by default except on Windows)."""
386
508
 
387
- sync_tensorboard: bool | None = None
509
+ sync_tensorboard: Optional[bool] = None
388
510
  """Whether to synchronize TensorBoard logs with W&B."""
389
511
 
390
512
  table_raise_on_max_row_limit_exceeded: bool = False
391
513
  """Whether to raise an exception when table row limits are exceeded."""
392
514
 
393
- username: str | None = None
515
+ username: Optional[str] = None
394
516
  """Username."""
395
517
 
396
518
  # Internal settings.
@@ -424,13 +546,13 @@ class Settings(BaseModel, validate_assignment=True):
424
546
  x_disable_machine_info: bool = False
425
547
  """Flag to disable automatic machine info collection."""
426
548
 
427
- x_executable: str | None = None
549
+ x_executable: Optional[str] = None
428
550
  """Path to the Python executable."""
429
551
 
430
- x_extra_http_headers: dict[str, str] | None = None
552
+ x_extra_http_headers: Optional[Dict[str, str]] = None
431
553
  """Additional headers to add to all outgoing HTTP requests."""
432
554
 
433
- x_file_stream_max_bytes: int | None = None
555
+ x_file_stream_max_bytes: Optional[int] = None
434
556
  """An approximate maximum request size for the filestream API.
435
557
 
436
558
  Its purpose is to prevent HTTP requests from failing due to
@@ -438,50 +560,50 @@ class Settings(BaseModel, validate_assignment=True):
438
560
  requests will be slightly larger.
439
561
  """
440
562
 
441
- x_file_stream_max_line_bytes: int | None = None
563
+ x_file_stream_max_line_bytes: Optional[int] = None
442
564
  """Maximum line length for filestream JSONL files."""
443
565
 
444
- x_file_stream_transmit_interval: float | None = None
566
+ x_file_stream_transmit_interval: Optional[float] = None
445
567
  """Interval in seconds between filestream transmissions."""
446
568
 
447
569
  # Filestream retry client configuration.
448
570
 
449
- x_file_stream_retry_max: int | None = None
571
+ x_file_stream_retry_max: Optional[int] = None
450
572
  """Max number of retries for filestream operations."""
451
573
 
452
- x_file_stream_retry_wait_min_seconds: float | None = None
574
+ x_file_stream_retry_wait_min_seconds: Optional[float] = None
453
575
  """Minimum wait time between retries for filestream operations."""
454
576
 
455
- x_file_stream_retry_wait_max_seconds: float | None = None
577
+ x_file_stream_retry_wait_max_seconds: Optional[float] = None
456
578
  """Maximum wait time between retries for filestream operations."""
457
579
 
458
- x_file_stream_timeout_seconds: float | None = None
580
+ x_file_stream_timeout_seconds: Optional[float] = None
459
581
  """Timeout in seconds for individual filestream HTTP requests."""
460
582
 
461
583
  # file transfer retry client configuration
462
584
 
463
- x_file_transfer_retry_max: int | None = None
585
+ x_file_transfer_retry_max: Optional[int] = None
464
586
  """Max number of retries for file transfer operations."""
465
587
 
466
- x_file_transfer_retry_wait_min_seconds: float | None = None
588
+ x_file_transfer_retry_wait_min_seconds: Optional[float] = None
467
589
  """Minimum wait time between retries for file transfer operations."""
468
590
 
469
- x_file_transfer_retry_wait_max_seconds: float | None = None
591
+ x_file_transfer_retry_wait_max_seconds: Optional[float] = None
470
592
  """Maximum wait time between retries for file transfer operations."""
471
593
 
472
- x_file_transfer_timeout_seconds: float | None = None
594
+ x_file_transfer_timeout_seconds: Optional[float] = None
473
595
  """Timeout in seconds for individual file transfer HTTP requests."""
474
596
 
475
- x_files_dir: str | None = None
597
+ x_files_dir: Optional[str] = None
476
598
  """Override setting for the computed files_dir.."""
477
599
 
478
- x_flow_control_custom: bool | None = None
600
+ x_flow_control_custom: Optional[bool] = None
479
601
  """Flag indicating custom flow control for filestream.
480
602
 
481
603
  TODO: Not implemented in wandb-core.
482
604
  """
483
605
 
484
- x_flow_control_disabled: bool | None = None
606
+ x_flow_control_disabled: Optional[bool] = None
485
607
  """Flag indicating flow control is disabled for filestream.
486
608
 
487
609
  TODO: Not implemented in wandb-core.
@@ -489,47 +611,47 @@ class Settings(BaseModel, validate_assignment=True):
489
611
 
490
612
  # graphql retry client configuration
491
613
 
492
- x_graphql_retry_max: int | None = None
614
+ x_graphql_retry_max: Optional[int] = None
493
615
  """Max number of retries for GraphQL operations."""
494
616
 
495
- x_graphql_retry_wait_min_seconds: float | None = None
617
+ x_graphql_retry_wait_min_seconds: Optional[float] = None
496
618
  """Minimum wait time between retries for GraphQL operations."""
497
619
 
498
- x_graphql_retry_wait_max_seconds: float | None = None
620
+ x_graphql_retry_wait_max_seconds: Optional[float] = None
499
621
  """Maximum wait time between retries for GraphQL operations."""
500
622
 
501
- x_graphql_timeout_seconds: float | None = None
623
+ x_graphql_timeout_seconds: Optional[float] = None
502
624
  """Timeout in seconds for individual GraphQL requests."""
503
625
 
504
626
  x_internal_check_process: float = 8.0
505
627
  """Interval for internal process health checks in seconds."""
506
628
 
507
- x_jupyter_name: str | None = None
629
+ x_jupyter_name: Optional[str] = None
508
630
  """Name of the Jupyter notebook."""
509
631
 
510
- x_jupyter_path: str | None = None
632
+ x_jupyter_path: Optional[str] = None
511
633
  """Path to the Jupyter notebook."""
512
634
 
513
- x_jupyter_root: str | None = None
635
+ x_jupyter_root: Optional[str] = None
514
636
  """Root directory of the Jupyter notebook."""
515
637
 
516
- x_label: str | None = None
638
+ x_label: Optional[str] = None
517
639
  """Label to assign to system metrics and console logs collected for the run.
518
640
 
519
641
  This is used to group data by on the frontend and can be used to distinguish data
520
642
  from different processes in a distributed training job.
521
643
  """
522
644
 
523
- x_live_policy_rate_limit: int | None = None
645
+ x_live_policy_rate_limit: Optional[int] = None
524
646
  """Rate limit for live policy updates in seconds."""
525
647
 
526
- x_live_policy_wait_time: int | None = None
648
+ x_live_policy_wait_time: Optional[int] = None
527
649
  """Wait time between live policy updates in seconds."""
528
650
 
529
651
  x_log_level: int = logging.INFO
530
652
  """Logging level for internal operations."""
531
653
 
532
- x_network_buffer: int | None = None
654
+ x_network_buffer: Optional[int] = None
533
655
  """Size of the network buffer used in flow control.
534
656
 
535
657
  TODO: Not implemented in wandb-core.
@@ -545,14 +667,14 @@ class Settings(BaseModel, validate_assignment=True):
545
667
  as the primary process handles the main logging.
546
668
  """
547
669
 
548
- x_proxies: dict[str, str] | None = None
670
+ x_proxies: Optional[Dict[str, str]] = None
549
671
  """Custom proxy servers for requests to W&B.
550
672
 
551
673
  This is deprecated and will be removed in future versions.
552
674
  Please use `http_proxy` and `https_proxy` instead.
553
675
  """
554
676
 
555
- x_runqueue_item_id: str | None = None
677
+ x_runqueue_item_id: Optional[str] = None
556
678
  """ID of the Launch run queue item being processed."""
557
679
 
558
680
  x_require_legacy_service: bool = False
@@ -567,13 +689,13 @@ class Settings(BaseModel, validate_assignment=True):
567
689
  This does not disable user-provided summary updates.
568
690
  """
569
691
 
570
- x_service_transport: str | None = None
692
+ x_service_transport: Optional[str] = None
571
693
  """Transport method for communication with the wandb service."""
572
694
 
573
695
  x_service_wait: float = 30.0
574
696
  """Time in seconds to wait for the wandb-core internal service to start."""
575
697
 
576
- x_start_time: float | None = None
698
+ x_start_time: Optional[float] = None
577
699
  """The start time of the run in seconds since the Unix epoch."""
578
700
 
579
701
  x_stats_pid: int = os.getpid()
@@ -582,13 +704,13 @@ class Settings(BaseModel, validate_assignment=True):
582
704
  x_stats_sampling_interval: float = Field(default=15.0)
583
705
  """Sampling interval for the system monitor in seconds."""
584
706
 
585
- x_stats_neuron_monitor_config_path: str | None = None
707
+ x_stats_neuron_monitor_config_path: Optional[str] = None
586
708
  """Path to the default config file for the neuron-monitor tool.
587
709
 
588
710
  This is used to monitor AWS Trainium devices.
589
711
  """
590
712
 
591
- x_stats_dcgm_exporter: str | None = None
713
+ x_stats_dcgm_exporter: Optional[str] = None
592
714
  """Endpoint to extract Nvidia DCGM metrics from.
593
715
 
594
716
  Two options are supported:
@@ -602,12 +724,12 @@ class Settings(BaseModel, validate_assignment=True):
602
724
  - TODO: `http://192.168.0.1:9400/metrics`.
603
725
  """
604
726
 
605
- x_stats_open_metrics_endpoints: dict[str, str] | None = None
727
+ x_stats_open_metrics_endpoints: Optional[Dict[str, str]] = None
606
728
  """OpenMetrics `/metrics` endpoints to monitor for system metrics."""
607
729
 
608
- x_stats_open_metrics_filters: dict[str, dict[str, str]] | Sequence[str] | None = (
609
- None
610
- )
730
+ x_stats_open_metrics_filters: Union[
731
+ Dict[str, Dict[str, str]], Sequence[str], None
732
+ ] = None
611
733
  """Filter to apply to metrics collected from OpenMetrics `/metrics` endpoints.
612
734
 
613
735
  Supports two formats:
@@ -615,17 +737,17 @@ class Settings(BaseModel, validate_assignment=True):
615
737
  - ("metric regex pattern 1", "metric regex pattern 2", ...)
616
738
  """
617
739
 
618
- x_stats_open_metrics_http_headers: dict[str, str] | None = None
740
+ x_stats_open_metrics_http_headers: Optional[Dict[str, str]] = None
619
741
  """HTTP headers to add to OpenMetrics requests."""
620
742
 
621
- x_stats_disk_paths: Sequence[str] | None = Field(
743
+ x_stats_disk_paths: Optional[Sequence[str]] = Field(
622
744
  default_factory=lambda: ("/", "/System/Volumes/Data")
623
745
  if platform.system() == "Darwin"
624
746
  else ("/",)
625
747
  )
626
748
  """System paths to monitor for disk usage."""
627
749
 
628
- x_stats_gpu_device_ids: Sequence[int] | None = None
750
+ x_stats_gpu_device_ids: Optional[Sequence[int]] = None
629
751
  """GPU device indices to monitor.
630
752
 
631
753
  If not set, captures metrics for all GPUs.
@@ -665,24 +787,39 @@ class Settings(BaseModel, validate_assignment=True):
665
787
  new_values[key] = values[key]
666
788
  return new_values
667
789
 
668
- @model_validator(mode="after")
669
- def validate_mutual_exclusion_of_branching_args(self) -> Self:
670
- if (
671
- sum(
672
- o is not None
673
- for o in [
674
- self.fork_from,
675
- self.resume,
676
- self.resume_from,
677
- ]
678
- )
679
- > 1
680
- ):
681
- raise ValueError(
682
- "`fork_from`, `resume`, or `resume_from` are mutually exclusive. "
683
- "Please specify only one of them."
684
- )
685
- return self
790
+ if IS_PYDANTIC_V2:
791
+
792
+ @model_validator(mode="after")
793
+ def validate_mutual_exclusion_of_branching_args(self) -> Self:
794
+ if (
795
+ sum(
796
+ o is not None
797
+ for o in [self.fork_from, self.resume, self.resume_from]
798
+ )
799
+ > 1
800
+ ):
801
+ raise ValueError(
802
+ "`fork_from`, `resume`, or `resume_from` are mutually exclusive. "
803
+ "Please specify only one of them."
804
+ )
805
+ return self
806
+ else:
807
+
808
+ @root_validator(pre=False) # type: ignore [call-overload]
809
+ @classmethod
810
+ def validate_mutual_exclusion_of_branching_args(cls, values):
811
+ if (
812
+ sum(
813
+ values.get(o) is not None
814
+ for o in ["fork_from", "resume", "resume_from"]
815
+ )
816
+ > 1
817
+ ):
818
+ raise ValueError(
819
+ "`fork_from`, `resume`, or `resume_from` are mutually exclusive. "
820
+ "Please specify only one of them."
821
+ )
822
+ return values
686
823
 
687
824
  # Field validators.
688
825
 
@@ -707,7 +844,7 @@ class Settings(BaseModel, validate_assignment=True):
707
844
  @field_validator("base_url", mode="after")
708
845
  @classmethod
709
846
  def validate_base_url(cls, value):
710
- cls.validate_url(value)
847
+ validate_url(value)
711
848
  # wandb.ai-specific checks
712
849
  if re.match(r".*wandb\.ai[^\.]*$", value) and "api." not in value:
713
850
  # user might guess app.wandb.ai or wandb.ai is the default cloud server
@@ -728,13 +865,21 @@ class Settings(BaseModel, validate_assignment=True):
728
865
 
729
866
  @field_validator("console", mode="after")
730
867
  @classmethod
731
- def validate_console(cls, value, info):
868
+ def validate_console(cls, value, values):
732
869
  if value != "auto":
733
870
  return value
871
+
872
+ if hasattr(values, "data"):
873
+ # pydantic v2
874
+ values = values.data
875
+ else:
876
+ # pydantic v1
877
+ values = values
878
+
734
879
  if (
735
880
  ipython.in_jupyter()
736
- or (info.data.get("start_method") == "thread")
737
- or not info.data.get("disable_service")
881
+ or (values.get("start_method") == "thread")
882
+ or not values.get("x_disable_service")
738
883
  or platform.system() == "Windows"
739
884
  ):
740
885
  value = "wrap"
@@ -767,12 +912,20 @@ class Settings(BaseModel, validate_assignment=True):
767
912
 
768
913
  @field_validator("fork_from", mode="before")
769
914
  @classmethod
770
- def validate_fork_from(cls, value, info) -> RunMoment | None:
915
+ def validate_fork_from(cls, value, values) -> Optional[RunMoment]:
771
916
  run_moment = cls._runmoment_preprocessor(value)
917
+
918
+ if hasattr(values, "data"):
919
+ # pydantic v2
920
+ values = values.data
921
+ else:
922
+ # pydantic v1
923
+ values = values
924
+
772
925
  if (
773
926
  run_moment
774
- and info.data.get("run_id") is not None
775
- and info.data.get("run_id") == run_moment.run
927
+ and values.get("run_id") is not None
928
+ and values.get("run_id") == run_moment.run
776
929
  ):
777
930
  raise ValueError(
778
931
  "Provided `run_id` is the same as the run to `fork_from`. "
@@ -786,7 +939,7 @@ class Settings(BaseModel, validate_assignment=True):
786
939
  def validate_http_proxy(cls, value):
787
940
  if value is None:
788
941
  return None
789
- cls.validate_url(value)
942
+ validate_url(value)
790
943
  return value.rstrip("/")
791
944
 
792
945
  @field_validator("https_proxy", mode="after")
@@ -794,7 +947,7 @@ class Settings(BaseModel, validate_assignment=True):
794
947
  def validate_https_proxy(cls, value):
795
948
  if value is None:
796
949
  return None
797
- cls.validate_url(value)
950
+ validate_url(value)
798
951
  return value.rstrip("/")
799
952
 
800
953
  @field_validator("ignore_globs", mode="after")
@@ -828,7 +981,7 @@ class Settings(BaseModel, validate_assignment=True):
828
981
 
829
982
  @field_validator("project", mode="after")
830
983
  @classmethod
831
- def validate_project(cls, value, info):
984
+ def validate_project(cls, value, values):
832
985
  if value is None:
833
986
  return None
834
987
  invalid_chars_list = list("/\\#?%:")
@@ -854,12 +1007,20 @@ class Settings(BaseModel, validate_assignment=True):
854
1007
 
855
1008
  @field_validator("resume_from", mode="before")
856
1009
  @classmethod
857
- def validate_resume_from(cls, value, info) -> RunMoment | None:
1010
+ def validate_resume_from(cls, value, values) -> Optional[RunMoment]:
858
1011
  run_moment = cls._runmoment_preprocessor(value)
1012
+
1013
+ if hasattr(values, "data"):
1014
+ # pydantic v2
1015
+ values = values.data
1016
+ else:
1017
+ # pydantic v1
1018
+ values = values
1019
+
859
1020
  if (
860
1021
  run_moment
861
- and info.data.get("run_id") is not None
862
- and info.data.get("run_id") != run_moment.run
1022
+ and values.get("run_id") is not None
1023
+ and values.get("run_id") != run_moment.run
863
1024
  ):
864
1025
  raise ValueError(
865
1026
  "Both `run_id` and `resume_from` have been specified with different ids."
@@ -876,7 +1037,7 @@ class Settings(BaseModel, validate_assignment=True):
876
1037
 
877
1038
  @field_validator("run_id", mode="after")
878
1039
  @classmethod
879
- def validate_run_id(cls, value, info):
1040
+ def validate_run_id(cls, value, values):
880
1041
  if value is None:
881
1042
  return None
882
1043
 
@@ -902,7 +1063,7 @@ class Settings(BaseModel, validate_assignment=True):
902
1063
  raise UsageError("Service wait time cannot be negative")
903
1064
  return value
904
1065
 
905
- @field_validator("start_method")
1066
+ @field_validator("start_method", mode="after")
906
1067
  @classmethod
907
1068
  def validate_start_method(cls, value):
908
1069
  if value is None:
@@ -984,7 +1145,7 @@ class Settings(BaseModel, validate_assignment=True):
984
1145
 
985
1146
  @computed_field # type: ignore[prop-decorator]
986
1147
  @property
987
- def _args(self) -> list[str]:
1148
+ def _args(self) -> List[str]:
988
1149
  if not self._jupyter:
989
1150
  return sys.argv[1:]
990
1151
  return []
@@ -1006,7 +1167,7 @@ class Settings(BaseModel, validate_assignment=True):
1006
1167
 
1007
1168
  @computed_field # type: ignore[prop-decorator]
1008
1169
  @property
1009
- def _code_path_local(self) -> str | None:
1170
+ def _code_path_local(self) -> Optional[str]:
1010
1171
  """The relative path from the current working directory to the code path.
1011
1172
 
1012
1173
  For example, if the code path is /home/user/project/example.py, and the
@@ -1089,12 +1250,7 @@ class Settings(BaseModel, validate_assignment=True):
1089
1250
  @computed_field # type: ignore[prop-decorator]
1090
1251
  @property
1091
1252
  def _tmp_code_dir(self) -> str:
1092
- return _path_convert(
1093
- self.wandb_dir,
1094
- f"{self.run_mode}-{self.timespec}-{self.run_id}",
1095
- "tmp",
1096
- "code",
1097
- )
1253
+ return _path_convert(self.sync_dir, "tmp", "code")
1098
1254
 
1099
1255
  @computed_field # type: ignore[prop-decorator]
1100
1256
  @property
@@ -1103,7 +1259,7 @@ class Settings(BaseModel, validate_assignment=True):
1103
1259
 
1104
1260
  @computed_field # type: ignore[prop-decorator]
1105
1261
  @property
1106
- def colab_url(self) -> str | None:
1262
+ def colab_url(self) -> Optional[str]:
1107
1263
  """The URL to the Colab notebook, if running in Colab."""
1108
1264
  if not self._colab:
1109
1265
  return None
@@ -1121,11 +1277,7 @@ class Settings(BaseModel, validate_assignment=True):
1121
1277
  @property
1122
1278
  def files_dir(self) -> str:
1123
1279
  """Absolute path to the local directory where the run's files are stored."""
1124
- return self.x_files_dir or _path_convert(
1125
- self.wandb_dir,
1126
- f"{self.run_mode}-{self.timespec}-{self.run_id}",
1127
- "files",
1128
- )
1280
+ return self.x_files_dir or _path_convert(self.sync_dir, "files")
1129
1281
 
1130
1282
  @computed_field # type: ignore[prop-decorator]
1131
1283
  @property
@@ -1136,9 +1288,7 @@ class Settings(BaseModel, validate_assignment=True):
1136
1288
  @property
1137
1289
  def log_dir(self) -> str:
1138
1290
  """The directory for storing log files."""
1139
- return _path_convert(
1140
- self.wandb_dir, f"{self.run_mode}-{self.timespec}-{self.run_id}", "logs"
1141
- )
1291
+ return _path_convert(self.sync_dir, "logs")
1142
1292
 
1143
1293
  @computed_field # type: ignore[prop-decorator]
1144
1294
  @property
@@ -1219,7 +1369,8 @@ class Settings(BaseModel, validate_assignment=True):
1219
1369
  @property
1220
1370
  def sync_dir(self) -> str:
1221
1371
  return _path_convert(
1222
- self.wandb_dir, f"{self.run_mode}-{self.timespec}-{self.run_id}"
1372
+ self.wandb_dir,
1373
+ f"{self.run_mode}-{self.timespec}-{self.run_id}",
1223
1374
  )
1224
1375
 
1225
1376
  @computed_field # type: ignore[prop-decorator]
@@ -1241,29 +1392,13 @@ class Settings(BaseModel, validate_assignment=True):
1241
1392
  @computed_field # type: ignore[prop-decorator]
1242
1393
  @property
1243
1394
  def wandb_dir(self) -> str:
1244
- """Full path to the wandb directory.
1245
-
1246
- The setting exposed to users as `dir=` or `WANDB_DIR` is the `root_dir`.
1247
- We add the `__stage_dir__` to it to get the full `wandb_dir`
1248
- """
1249
- root_dir = self.root_dir or ""
1250
-
1251
- # We use the hidden version if it already exists, otherwise non-hidden.
1252
- if os.path.exists(os.path.join(root_dir, ".wandb")):
1253
- __stage_dir__ = ".wandb" + os.sep
1254
- else:
1255
- __stage_dir__ = "wandb" + os.sep
1256
-
1257
- path = os.path.join(root_dir, __stage_dir__)
1258
- if not os.access(root_dir or ".", os.W_OK):
1259
- termwarn(
1260
- f"Path {path} wasn't writable, using system temp directory.",
1261
- repeat=False,
1262
- )
1263
- path = os.path.join(
1264
- tempfile.gettempdir(), __stage_dir__ or ("wandb" + os.sep)
1265
- )
1266
-
1395
+ """Full path to the wandb directory."""
1396
+ stage_dir = (
1397
+ ".wandb" + os.sep
1398
+ if os.path.exists(os.path.join(self.root_dir, ".wandb"))
1399
+ else "wandb" + os.sep
1400
+ )
1401
+ path = os.path.join(self.root_dir, stage_dir)
1267
1402
  return os.path.expanduser(path)
1268
1403
 
1269
1404
  # Methods to collect and update settings from different sources.
@@ -1289,7 +1424,7 @@ class Settings(BaseModel, validate_assignment=True):
1289
1424
  if value is not None:
1290
1425
  setattr(self, key, value)
1291
1426
 
1292
- def update_from_env_vars(self, environ: dict[str, Any]):
1427
+ def update_from_env_vars(self, environ: Dict[str, Any]):
1293
1428
  """Update settings from environment variables."""
1294
1429
  env_prefix: str = "WANDB_"
1295
1430
  private_env_prefix: str = env_prefix + "_"
@@ -1407,7 +1542,7 @@ class Settings(BaseModel, validate_assignment=True):
1407
1542
 
1408
1543
  self.program = program
1409
1544
 
1410
- def update_from_dict(self, settings: dict[str, Any]) -> None:
1545
+ def update_from_dict(self, settings: Dict[str, Any]) -> None:
1411
1546
  """Update settings from a dictionary."""
1412
1547
  for key, value in dict(settings).items():
1413
1548
  if value is not None:
@@ -1425,7 +1560,11 @@ class Settings(BaseModel, validate_assignment=True):
1425
1560
  """Generate a protobuf representation of the settings."""
1426
1561
  settings_proto = wandb_settings_pb2.Settings()
1427
1562
  for k, v in self.model_dump(exclude_none=True).items():
1428
- # special case for x_stats_open_metrics_filters
1563
+ # Client-only settings that don't exist on the protobuf.
1564
+ if k in ("reinit",):
1565
+ continue
1566
+
1567
+ # Special case for x_stats_open_metrics_filters.
1429
1568
  if k == "x_stats_open_metrics_filters":
1430
1569
  if isinstance(v, (list, set, tuple)):
1431
1570
  setting = getattr(settings_proto, k)
@@ -1439,12 +1578,16 @@ class Settings(BaseModel, validate_assignment=True):
1439
1578
  raise TypeError(f"Unsupported type {type(v)} for setting {k}")
1440
1579
  continue
1441
1580
 
1442
- # special case for RunMoment fields
1581
+ # Special case for RunMoment fields.
1443
1582
  if k in ("fork_from", "resume_from"):
1444
- run_moment = RunMoment(
1445
- run=v.get("run"),
1446
- value=v.get("value"),
1447
- metric=v.get("metric"),
1583
+ run_moment = (
1584
+ v
1585
+ if isinstance(v, RunMoment)
1586
+ else RunMoment(
1587
+ run=v.get("run"),
1588
+ value=v.get("value"),
1589
+ metric=v.get("metric"),
1590
+ )
1448
1591
  )
1449
1592
  getattr(settings_proto, k).CopyFrom(
1450
1593
  wandb_settings_pb2.RunMoment(
@@ -1480,18 +1623,7 @@ class Settings(BaseModel, validate_assignment=True):
1480
1623
 
1481
1624
  return settings_proto
1482
1625
 
1483
- @staticmethod
1484
- def validate_url(url: str) -> None:
1485
- """Validate a URL string."""
1486
- url_validator = SchemaValidator(
1487
- core_schema.url_schema(
1488
- allowed_schemes=["http", "https"],
1489
- strict=True,
1490
- )
1491
- )
1492
- url_validator.validate_python(url)
1493
-
1494
- def _get_program(self) -> str | None:
1626
+ def _get_program(self) -> Optional[str]:
1495
1627
  """Get the program that started the current process."""
1496
1628
  if not self._jupyter:
1497
1629
  # If not in a notebook, try to get the program from the environment
@@ -1521,7 +1653,7 @@ class Settings(BaseModel, validate_assignment=True):
1521
1653
  return self.x_jupyter_path
1522
1654
 
1523
1655
  @staticmethod
1524
- def _get_program_relpath(program: str, root: str | None = None) -> str | None:
1656
+ def _get_program_relpath(program: str, root: Optional[str] = None) -> Optional[str]:
1525
1657
  """Get the relative path to the program from the root directory."""
1526
1658
  if not program:
1527
1659
  return None
@@ -1547,7 +1679,7 @@ class Settings(BaseModel, validate_assignment=True):
1547
1679
  parser = configparser.ConfigParser()
1548
1680
  parser.add_section(section)
1549
1681
  parser.read(file_name)
1550
- config: dict[str, Any] = dict()
1682
+ config: Dict[str, Any] = dict()
1551
1683
  for k in parser[section]:
1552
1684
  config[k] = parser[section][k]
1553
1685
  if k == "ignore_globs":
@@ -1573,9 +1705,79 @@ class Settings(BaseModel, validate_assignment=True):
1573
1705
  return f"?{urlencode({'apiKey': api_key})}"
1574
1706
 
1575
1707
  @staticmethod
1576
- def _runmoment_preprocessor(val: RunMoment | str | None) -> RunMoment | None:
1708
+ def _runmoment_preprocessor(
1709
+ val: Union[RunMoment, str, None],
1710
+ ) -> Optional[RunMoment]:
1577
1711
  """Preprocess the setting for forking or resuming a run."""
1578
1712
  if isinstance(val, RunMoment) or val is None:
1579
1713
  return val
1580
1714
  elif isinstance(val, str):
1581
1715
  return RunMoment.from_uri(val)
1716
+
1717
+ if not IS_PYDANTIC_V2:
1718
+
1719
+ def model_copy(self, *args, **kwargs):
1720
+ return self.copy(*args, **kwargs)
1721
+
1722
+ def model_dump(self, **kwargs):
1723
+ """Compatibility method for Pydantic v1 to mimic v2's model_dump.
1724
+
1725
+ In v1, this is equivalent to dict() but also includes computed properties.
1726
+
1727
+ Args:
1728
+ **kwargs: Options passed to the dict method
1729
+ - exclude_none: Whether to exclude fields with None values
1730
+
1731
+ Returns:
1732
+ A dictionary of the model's fields and computed properties
1733
+ """
1734
+ # Handle exclude_none separately since it's named differently in v1
1735
+ exclude_none = kwargs.pop("exclude_none", False)
1736
+
1737
+ # Start with regular fields from dict()
1738
+ result = self.dict(**kwargs)
1739
+
1740
+ # Get all computed properties
1741
+ for name in dir(self.__class__):
1742
+ attr = getattr(self.__class__, name, None)
1743
+ if isinstance(attr, property):
1744
+ try:
1745
+ # Only include properties that don't raise errors
1746
+ value = getattr(self, name)
1747
+ result[name] = value
1748
+ except (AttributeError, NotImplementedError, TypeError, ValueError):
1749
+ # Skip properties that can't be accessed or raise errors
1750
+ pass
1751
+ elif isinstance(attr, RunMoment):
1752
+ value = getattr(self, name)
1753
+ result[name] = value
1754
+
1755
+ # Special Pydantic attributes that should always be excluded
1756
+ exclude_fields = {
1757
+ "model_config",
1758
+ "model_fields",
1759
+ "model_fields_set",
1760
+ "__fields__",
1761
+ "__model_fields_set",
1762
+ "__pydantic_self__",
1763
+ "__pydantic_initialised__",
1764
+ }
1765
+
1766
+ # Remove special Pydantic attributes
1767
+ for field in exclude_fields:
1768
+ if field in result:
1769
+ del result[field]
1770
+
1771
+ if exclude_none:
1772
+ # Remove None values from the result
1773
+ return {k: v for k, v in result.items() if v is not None}
1774
+
1775
+ return result
1776
+
1777
+ @property
1778
+ def model_fields_set(self) -> set:
1779
+ """Return a set of fields that have been explicitly set.
1780
+
1781
+ This is a compatibility property for Pydantic v1 to mimic v2's model_fields_set.
1782
+ """
1783
+ return getattr(self, "__fields_set__", set())