wandb 0.22.0__py3-none-win32.whl → 0.22.2__py3-none-win32.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 (114) hide show
  1. wandb/__init__.py +1 -1
  2. wandb/__init__.pyi +8 -5
  3. wandb/_pydantic/__init__.py +12 -11
  4. wandb/_pydantic/base.py +49 -19
  5. wandb/apis/__init__.py +2 -0
  6. wandb/apis/attrs.py +2 -0
  7. wandb/apis/importers/internals/internal.py +16 -23
  8. wandb/apis/internal.py +2 -0
  9. wandb/apis/normalize.py +2 -0
  10. wandb/apis/public/__init__.py +3 -2
  11. wandb/apis/public/api.py +215 -164
  12. wandb/apis/public/artifacts.py +23 -20
  13. wandb/apis/public/const.py +2 -0
  14. wandb/apis/public/files.py +33 -24
  15. wandb/apis/public/history.py +2 -0
  16. wandb/apis/public/jobs.py +20 -18
  17. wandb/apis/public/projects.py +4 -2
  18. wandb/apis/public/query_generator.py +3 -0
  19. wandb/apis/public/registries/__init__.py +7 -0
  20. wandb/apis/public/registries/_freezable_list.py +9 -12
  21. wandb/apis/public/registries/registries_search.py +8 -6
  22. wandb/apis/public/registries/registry.py +22 -17
  23. wandb/apis/public/reports.py +2 -0
  24. wandb/apis/public/runs.py +261 -57
  25. wandb/apis/public/sweeps.py +10 -9
  26. wandb/apis/public/teams.py +2 -0
  27. wandb/apis/public/users.py +2 -0
  28. wandb/apis/public/utils.py +16 -15
  29. wandb/automations/_generated/__init__.py +54 -127
  30. wandb/automations/_generated/create_generic_webhook_integration.py +1 -7
  31. wandb/automations/_generated/fragments.py +26 -91
  32. wandb/bin/gpu_stats.exe +0 -0
  33. wandb/bin/wandb-core +0 -0
  34. wandb/cli/beta.py +16 -2
  35. wandb/cli/beta_leet.py +74 -0
  36. wandb/cli/beta_sync.py +9 -11
  37. wandb/cli/cli.py +34 -7
  38. wandb/errors/errors.py +3 -3
  39. wandb/proto/v3/wandb_api_pb2.py +86 -0
  40. wandb/proto/v3/wandb_internal_pb2.py +352 -351
  41. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  42. wandb/proto/v3/wandb_sync_pb2.py +19 -6
  43. wandb/proto/v4/wandb_api_pb2.py +37 -0
  44. wandb/proto/v4/wandb_internal_pb2.py +352 -351
  45. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  46. wandb/proto/v4/wandb_sync_pb2.py +10 -6
  47. wandb/proto/v5/wandb_api_pb2.py +38 -0
  48. wandb/proto/v5/wandb_internal_pb2.py +352 -351
  49. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  50. wandb/proto/v5/wandb_sync_pb2.py +10 -6
  51. wandb/proto/v6/wandb_api_pb2.py +48 -0
  52. wandb/proto/v6/wandb_internal_pb2.py +352 -351
  53. wandb/proto/v6/wandb_settings_pb2.py +2 -2
  54. wandb/proto/v6/wandb_sync_pb2.py +10 -6
  55. wandb/proto/wandb_api_pb2.py +18 -0
  56. wandb/proto/wandb_generate_proto.py +1 -0
  57. wandb/sdk/artifacts/_factories.py +7 -2
  58. wandb/sdk/artifacts/_generated/__init__.py +112 -412
  59. wandb/sdk/artifacts/_generated/fragments.py +65 -0
  60. wandb/sdk/artifacts/_generated/operations.py +52 -22
  61. wandb/sdk/artifacts/_generated/run_input_artifacts.py +3 -23
  62. wandb/sdk/artifacts/_generated/run_output_artifacts.py +3 -23
  63. wandb/sdk/artifacts/_generated/type_info.py +19 -0
  64. wandb/sdk/artifacts/_gqlutils.py +47 -0
  65. wandb/sdk/artifacts/_models/__init__.py +4 -0
  66. wandb/sdk/artifacts/_models/base_model.py +20 -0
  67. wandb/sdk/artifacts/_validators.py +40 -12
  68. wandb/sdk/artifacts/artifact.py +99 -118
  69. wandb/sdk/artifacts/artifact_file_cache.py +6 -1
  70. wandb/sdk/artifacts/artifact_manifest_entry.py +67 -14
  71. wandb/sdk/artifacts/storage_handler.py +18 -12
  72. wandb/sdk/artifacts/storage_handlers/azure_handler.py +11 -6
  73. wandb/sdk/artifacts/storage_handlers/gcs_handler.py +9 -6
  74. wandb/sdk/artifacts/storage_handlers/http_handler.py +9 -4
  75. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +10 -6
  76. wandb/sdk/artifacts/storage_handlers/multi_handler.py +5 -4
  77. wandb/sdk/artifacts/storage_handlers/s3_handler.py +10 -8
  78. wandb/sdk/artifacts/storage_handlers/tracking_handler.py +6 -4
  79. wandb/sdk/artifacts/storage_handlers/wb_artifact_handler.py +24 -21
  80. wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +4 -2
  81. wandb/sdk/artifacts/storage_policies/_multipart.py +187 -0
  82. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +71 -242
  83. wandb/sdk/artifacts/storage_policy.py +25 -12
  84. wandb/sdk/data_types/bokeh.py +5 -1
  85. wandb/sdk/data_types/image.py +17 -6
  86. wandb/sdk/data_types/object_3d.py +67 -2
  87. wandb/sdk/interface/interface.py +31 -4
  88. wandb/sdk/interface/interface_queue.py +10 -0
  89. wandb/sdk/interface/interface_shared.py +0 -7
  90. wandb/sdk/interface/interface_sock.py +9 -3
  91. wandb/sdk/internal/_generated/__init__.py +2 -12
  92. wandb/sdk/internal/job_builder.py +27 -10
  93. wandb/sdk/internal/sender.py +5 -2
  94. wandb/sdk/internal/settings_static.py +2 -82
  95. wandb/sdk/launch/create_job.py +2 -1
  96. wandb/sdk/launch/runner/kubernetes_runner.py +25 -20
  97. wandb/sdk/launch/utils.py +82 -1
  98. wandb/sdk/lib/progress.py +8 -74
  99. wandb/sdk/lib/service/service_client.py +5 -9
  100. wandb/sdk/lib/service/service_connection.py +39 -23
  101. wandb/sdk/mailbox/mailbox_handle.py +2 -0
  102. wandb/sdk/projects/_generated/__init__.py +12 -33
  103. wandb/sdk/wandb_init.py +23 -3
  104. wandb/sdk/wandb_login.py +53 -27
  105. wandb/sdk/wandb_run.py +10 -5
  106. wandb/sdk/wandb_settings.py +63 -25
  107. wandb/sync/sync.py +7 -2
  108. wandb/util.py +1 -1
  109. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/METADATA +1 -1
  110. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/RECORD +113 -103
  111. wandb/sdk/artifacts/_graphql_fragments.py +0 -19
  112. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/WHEEL +0 -0
  113. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/entry_points.txt +0 -0
  114. {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/licenses/LICENSE +0 -0
@@ -138,7 +138,7 @@ def box3d(
138
138
  label: "Optional[str]" = None,
139
139
  score: "Optional[numeric]" = None,
140
140
  ) -> "Box3D":
141
- """Returns a Box3D.
141
+ """A 3D bounding box. The box is specified by its center, size and orientation.
142
142
 
143
143
  Args:
144
144
  center: The center point of the box as a length-3 ndarray.
@@ -149,7 +149,72 @@ def box3d(
149
149
  r + xi + yj + zk.
150
150
  color: The box's color as an (r, g, b) tuple with 0 <= r,g,b <= 1.
151
151
  label: An optional label for the box.
152
- score: An optional score for the box.
152
+ score: An optional score for the box. Typically used to indicate
153
+ the confidence of a detection.
154
+
155
+ Returns:
156
+ A Box3D object.
157
+
158
+ Example:
159
+ The following example creates a point cloud with 60 boxes rotating
160
+ around the X, Y and Z axes.
161
+
162
+ ```python
163
+ import wandb
164
+
165
+ import math
166
+ import numpy as np
167
+ from scipy.spatial.transform import Rotation
168
+
169
+
170
+ with wandb.init() as run:
171
+ run.log(
172
+ {
173
+ "points": wandb.Object3D.from_point_cloud(
174
+ points=np.random.uniform(-5, 5, size=(100, 3)),
175
+ boxes=[
176
+ wandb.box3d(
177
+ center=(0.3 * t - 3, 0, 0),
178
+ size=(0.1, 0.1, 0.1),
179
+ orientation=Rotation.from_euler(
180
+ "xyz", [t * math.pi / 10, 0, 0]
181
+ ).as_quat(),
182
+ color=(0.5 + t / 40, 0.5, 0.5),
183
+ label=f"box {t}",
184
+ score=0.9,
185
+ )
186
+ for t in range(20)
187
+ ]
188
+ + [
189
+ wandb.box3d(
190
+ center=(0, 0.3 * t - 3, 0.3),
191
+ size=(0.1, 0.1, 0.1),
192
+ orientation=Rotation.from_euler(
193
+ "xyz", [0, t * math.pi / 10, 0]
194
+ ).as_quat(),
195
+ color=(0.5, 0.5 + t / 40, 0.5),
196
+ label=f"box {t}",
197
+ score=0.9,
198
+ )
199
+ for t in range(20)
200
+ ]
201
+ + [
202
+ wandb.box3d(
203
+ center=(0.3, 0.3, 0.3 * t - 3),
204
+ size=(0.1, 0.1, 0.1),
205
+ orientation=Rotation.from_euler(
206
+ "xyz", [0, 0, t * math.pi / 10]
207
+ ).as_quat(),
208
+ color=(0.5, 0.5, 0.5 + t / 40),
209
+ label=f"box {t}",
210
+ score=0.9,
211
+ )
212
+ for t in range(20)
213
+ ],
214
+ ),
215
+ }
216
+ )
217
+ ```
153
218
  """
154
219
  try:
155
220
  import numpy as np
@@ -87,11 +87,38 @@ def file_enum_to_policy(enum: "pb.FilesItem.PolicyType.V") -> "PolicyName":
87
87
 
88
88
 
89
89
  class InterfaceBase:
90
+ """Methods for sending different types of Records to the service.
91
+
92
+ None of the methods may be called from an asyncio context other than
93
+ deliver_async().
94
+ """
95
+
90
96
  _drop: bool
91
97
 
92
98
  def __init__(self) -> None:
93
99
  self._drop = False
94
100
 
101
+ @abstractmethod
102
+ async def deliver_async(
103
+ self,
104
+ record: pb.Record,
105
+ ) -> MailboxHandle[pb.Result]:
106
+ """Send a record and create a handle to wait for the response.
107
+
108
+ The synchronous publish and deliver methods on this class cannot be
109
+ called in the asyncio thread because they block. Instead of having
110
+ an async copy of every method, this is a general method for sending
111
+ any kind of record in the asyncio thread.
112
+
113
+ Args:
114
+ record: The record to send. This method takes ownership of the
115
+ record and it must not be used afterward.
116
+
117
+ Returns:
118
+ A handle to wait for a response to the record.
119
+ """
120
+ raise NotImplementedError
121
+
95
122
  def publish_header(self) -> None:
96
123
  header = pb.HeaderRecord()
97
124
  self._publish_header(header)
@@ -392,9 +419,13 @@ class InterfaceBase:
392
419
  proto_manifest.manifest_file_path = path
393
420
  return proto_manifest
394
421
 
422
+ # Set storage policy on storageLayout (always V2) and storageRegion, only allow coreweave-us on wandb.ai for now.
423
+ # NOTE: the decode logic is NewManifestFromProto in core/pkg/artifacts/manifest.go
424
+ # The creation logic is in artifacts/_factories.py make_storage_policy
395
425
  for k, v in artifact_manifest.storage_policy.config().items() or {}.items():
396
426
  cfg = proto_manifest.storage_policy_config.add()
397
427
  cfg.key = k
428
+ # TODO: Why json.dumps when existing values are plain string? We want to send complex structure without defining the proto?
398
429
  cfg.value_json = json.dumps(v)
399
430
 
400
431
  for entry in sorted(artifact_manifest.entries.values(), key=lambda k: k.path):
@@ -1020,10 +1051,6 @@ class InterfaceBase:
1020
1051
  ) -> MailboxHandle[pb.Result]:
1021
1052
  raise NotImplementedError
1022
1053
 
1023
- @abstractmethod
1024
- def deliver_operation_stats(self) -> MailboxHandle[pb.Result]:
1025
- raise NotImplementedError
1026
-
1027
1054
  def deliver_poll_exit(self) -> MailboxHandle[pb.Result]:
1028
1055
  poll_exit = pb.PollExitRequest()
1029
1056
  return self._deliver_poll_exit(poll_exit)
@@ -8,12 +8,15 @@ import logging
8
8
  from multiprocessing.process import BaseProcess
9
9
  from typing import TYPE_CHECKING, Optional
10
10
 
11
+ from typing_extensions import override
12
+
11
13
  from .interface_shared import InterfaceShared
12
14
 
13
15
  if TYPE_CHECKING:
14
16
  from queue import Queue
15
17
 
16
18
  from wandb.proto import wandb_internal_pb2 as pb
19
+ from wandb.sdk.mailbox.mailbox_handle import MailboxHandle
17
20
 
18
21
 
19
22
  logger = logging.getLogger("wandb")
@@ -31,6 +34,13 @@ class InterfaceQueue(InterfaceShared):
31
34
  self._process = process
32
35
  super().__init__()
33
36
 
37
+ @override
38
+ async def deliver_async(
39
+ self,
40
+ record: "pb.Record",
41
+ ) -> "MailboxHandle[pb.Result]":
42
+ raise NotImplementedError
43
+
34
44
  def _publish(self, record: "pb.Record", local: Optional[bool] = None) -> None:
35
45
  if self._process and not self._process.is_alive():
36
46
  raise Exception("The wandb backend process has shutdown")
@@ -87,7 +87,6 @@ class InterfaceShared(InterfaceBase):
87
87
  stop_status: Optional[pb.StopStatusRequest] = None,
88
88
  internal_messages: Optional[pb.InternalMessagesRequest] = None,
89
89
  network_status: Optional[pb.NetworkStatusRequest] = None,
90
- operation_stats: Optional[pb.OperationStatsRequest] = None,
91
90
  poll_exit: Optional[pb.PollExitRequest] = None,
92
91
  partial_history: Optional[pb.PartialHistoryRequest] = None,
93
92
  sampled_history: Optional[pb.SampledHistoryRequest] = None,
@@ -129,8 +128,6 @@ class InterfaceShared(InterfaceBase):
129
128
  request.internal_messages.CopyFrom(internal_messages)
130
129
  elif network_status:
131
130
  request.network_status.CopyFrom(network_status)
132
- elif operation_stats:
133
- request.operations.CopyFrom(operation_stats)
134
131
  elif poll_exit:
135
132
  request.poll_exit.CopyFrom(poll_exit)
136
133
  elif partial_history:
@@ -424,10 +421,6 @@ class InterfaceShared(InterfaceBase):
424
421
  record = self._make_record(exit=exit_data)
425
422
  return self._deliver(record)
426
423
 
427
- def deliver_operation_stats(self):
428
- record = self._make_request(operation_stats=pb.OperationStatsRequest())
429
- return self._deliver(record)
430
-
431
424
  def _deliver_poll_exit(
432
425
  self,
433
426
  poll_exit: pb.PollExitRequest,
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
6
6
  from typing_extensions import override
7
7
 
8
8
  from wandb.proto import wandb_server_pb2 as spb
9
+ from wandb.sdk.lib import asyncio_manager
9
10
 
10
11
  from .interface_shared import InterfaceShared
11
12
 
@@ -21,10 +22,12 @@ logger = logging.getLogger("wandb")
21
22
  class InterfaceSock(InterfaceShared):
22
23
  def __init__(
23
24
  self,
25
+ asyncer: asyncio_manager.AsyncioManager,
24
26
  client: ServiceClient,
25
27
  stream_id: str,
26
28
  ) -> None:
27
29
  super().__init__()
30
+ self._asyncer = asyncer
28
31
  self._client = client
29
32
  self._stream_id = stream_id
30
33
 
@@ -37,13 +40,16 @@ class InterfaceSock(InterfaceShared):
37
40
  self._assign(record)
38
41
  request = spb.ServerRequest()
39
42
  request.record_publish.CopyFrom(record)
40
- self._client.publish(request)
43
+ self._asyncer.run(lambda: self._client.publish(request))
41
44
 
42
- @override
43
45
  def _deliver(self, record: pb.Record) -> MailboxHandle[pb.Result]:
46
+ return self._asyncer.run(lambda: self.deliver_async(record))
47
+
48
+ @override
49
+ async def deliver_async(self, record: pb.Record) -> MailboxHandle[pb.Result]:
44
50
  self._assign(record)
45
51
  request = spb.ServerRequest()
46
52
  request.record_publish.CopyFrom(record)
47
53
 
48
- handle = self._client.deliver(request)
54
+ handle = await self._client.deliver(request)
49
55
  return handle.map(lambda response: response.result_communicate)
@@ -1,15 +1,5 @@
1
1
  # Generated by ariadne-codegen
2
2
 
3
+ __all__ = ["SERVER_FEATURES_QUERY_GQL", "ServerFeaturesQuery"]
3
4
  from .operations import SERVER_FEATURES_QUERY_GQL
4
- from .server_features_query import (
5
- ServerFeaturesQuery,
6
- ServerFeaturesQueryServerInfo,
7
- ServerFeaturesQueryServerInfoFeatures,
8
- )
9
-
10
- __all__ = [
11
- "SERVER_FEATURES_QUERY_GQL",
12
- "ServerFeaturesQuery",
13
- "ServerFeaturesQueryServerInfo",
14
- "ServerFeaturesQueryServerInfoFeatures",
15
- ]
5
+ from .server_features_query import ServerFeaturesQuery
@@ -132,6 +132,7 @@ def get_min_supported_for_source_dict(
132
132
 
133
133
  class JobBuilder:
134
134
  _settings: SettingsStatic
135
+ _files_dir: str
135
136
  _metadatafile_path: Optional[str]
136
137
  _requirements_path: Optional[str]
137
138
  _config: Optional[Dict[str, Any]]
@@ -146,8 +147,26 @@ class JobBuilder:
146
147
  _verbose: bool
147
148
  _services: Dict[str, str]
148
149
 
149
- def __init__(self, settings: SettingsStatic, verbose: bool = False):
150
+ def __init__(
151
+ self,
152
+ settings: SettingsStatic,
153
+ verbose: bool = False,
154
+ *,
155
+ files_dir: str,
156
+ ):
157
+ """Instantiate a JobBuilder.
158
+
159
+ Args:
160
+ settings: Parameters for the job builder.
161
+ In a run, this is the run's settings.
162
+ Otherwise, this is a set of undocumented parameters,
163
+ all of which should be made explicit like files_dir.
164
+ files_dir: The directory where to write files.
165
+ In a run, this should be the run's files directory.
166
+ """
150
167
  self._settings = settings
168
+ self._files_dir = files_dir
169
+
151
170
  self._metadatafile_path = None
152
171
  self._requirements_path = None
153
172
  self._config = None
@@ -460,9 +479,7 @@ class JobBuilder:
460
479
  )
461
480
  return None
462
481
 
463
- if not os.path.exists(
464
- os.path.join(self._settings.files_dir, REQUIREMENTS_FNAME)
465
- ):
482
+ if not os.path.exists(os.path.join(self._files_dir, REQUIREMENTS_FNAME)):
466
483
  self._log_if_verbose(
467
484
  "No requirements.txt found, not creating job artifact. See https://docs.wandb.ai/guides/launch/create-job",
468
485
  "warn",
@@ -471,7 +488,7 @@ class JobBuilder:
471
488
  metadata = self._handle_metadata_file()
472
489
  if metadata is None:
473
490
  self._log_if_verbose(
474
- f"Ensure read and write access to run files dir: {self._settings.files_dir}, control this via the WANDB_DIR env var. See https://docs.wandb.ai/guides/track/environment-variables",
491
+ f"Ensure read and write access to run files dir: {self._files_dir}, control this via the WANDB_DIR env var. See https://docs.wandb.ai/guides/track/environment-variables",
475
492
  "warn",
476
493
  )
477
494
  return None
@@ -560,15 +577,15 @@ class JobBuilder:
560
577
  f.write(json.dumps(source_info, indent=4))
561
578
 
562
579
  artifact.add_file(
563
- os.path.join(self._settings.files_dir, REQUIREMENTS_FNAME),
580
+ os.path.join(self._files_dir, REQUIREMENTS_FNAME),
564
581
  name=FROZEN_REQUIREMENTS_FNAME,
565
582
  )
566
583
 
567
584
  if source_type == "repo":
568
585
  # add diff
569
- if os.path.exists(os.path.join(self._settings.files_dir, DIFF_FNAME)):
586
+ if os.path.exists(os.path.join(self._files_dir, DIFF_FNAME)):
570
587
  artifact.add_file(
571
- os.path.join(self._settings.files_dir, DIFF_FNAME),
588
+ os.path.join(self._files_dir, DIFF_FNAME),
572
589
  name=DIFF_FNAME,
573
590
  )
574
591
 
@@ -619,8 +636,8 @@ class JobBuilder:
619
636
  def _handle_metadata_file(
620
637
  self,
621
638
  ) -> Optional[Dict]:
622
- if os.path.exists(os.path.join(self._settings.files_dir, METADATA_FNAME)):
623
- with open(os.path.join(self._settings.files_dir, METADATA_FNAME)) as f:
639
+ if os.path.exists(os.path.join(self._files_dir, METADATA_FNAME)):
640
+ with open(os.path.join(self._files_dir, METADATA_FNAME)) as f:
624
641
  metadata: Dict = json.load(f)
625
642
  return metadata
626
643
 
@@ -311,7 +311,10 @@ class SendManager:
311
311
  self._output_raw_file = None
312
312
 
313
313
  # job builder
314
- self._job_builder = JobBuilder(settings)
314
+ self._job_builder = JobBuilder(
315
+ settings,
316
+ files_dir=settings.files_dir,
317
+ )
315
318
 
316
319
  time_now = time.monotonic()
317
320
  self._debounce_config_time = time_now
@@ -343,7 +346,7 @@ class SendManager:
343
346
  publish_interface = InterfaceQueue(record_q=record_q)
344
347
  context_keeper = context.ContextKeeper()
345
348
  return SendManager(
346
- settings=SettingsStatic(settings.to_proto()),
349
+ settings=SettingsStatic(dict(settings)),
347
350
  record_q=record_q,
348
351
  result_q=result_q,
349
352
  interface=publish_interface,
@@ -2,9 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any, Iterable
4
4
 
5
- from wandb.proto import wandb_settings_pb2
6
- from wandb.sdk.lib import RunMoment
7
- from wandb.sdk.wandb_settings import CLIENT_ONLY_SETTINGS, Settings
5
+ from wandb.sdk.wandb_settings import Settings
8
6
 
9
7
 
10
8
  class SettingsStatic(Settings):
@@ -14,87 +12,9 @@ class SettingsStatic(Settings):
14
12
  attributes or items.
15
13
  """
16
14
 
17
- def __init__(self, proto: wandb_settings_pb2.Settings) -> None:
18
- data = self._proto_to_dict(proto)
15
+ def __init__(self, data: dict[str, Any]) -> None:
19
16
  super().__init__(**data)
20
17
 
21
- def _proto_to_dict(self, proto: wandb_settings_pb2.Settings) -> dict:
22
- data = {}
23
-
24
- exclude_fields = {
25
- "model_config",
26
- "model_fields",
27
- "model_fields_set",
28
- "__fields__",
29
- "__model_fields_set",
30
- "__pydantic_self__",
31
- "__pydantic_initialised__",
32
- }
33
-
34
- fields = (
35
- Settings.model_fields
36
- if hasattr(Settings, "model_fields")
37
- else Settings.__fields__
38
- ) # type: ignore [attr-defined]
39
-
40
- fields = {k: v for k, v in fields.items() if k not in exclude_fields} # type: ignore [union-attr]
41
-
42
- forks_specified: list[str] = []
43
- for key in fields:
44
- if key in CLIENT_ONLY_SETTINGS:
45
- continue
46
-
47
- value: Any = None
48
-
49
- field_info = fields[key]
50
- annotation = str(field_info.annotation)
51
-
52
- if key == "_stats_open_metrics_filters":
53
- # todo: it's an underscored field, refactor into
54
- # something more elegant?
55
- # I'm really about this. It's ugly, but it works.
56
- # Do not try to repeat this at home.
57
- value_type = getattr(proto, key).WhichOneof("value")
58
- if value_type == "sequence":
59
- value = list(getattr(proto, key).sequence.value)
60
- elif value_type == "mapping":
61
- unpacked_mapping = {}
62
- for outer_key, outer_value in getattr(
63
- proto, key
64
- ).mapping.value.items():
65
- unpacked_inner = {}
66
- for inner_key, inner_value in outer_value.value.items():
67
- unpacked_inner[inner_key] = inner_value
68
- unpacked_mapping[outer_key] = unpacked_inner
69
- value = unpacked_mapping
70
- elif key == "fork_from" or key == "resume_from":
71
- value = getattr(proto, key)
72
- if value.run:
73
- value = RunMoment(
74
- run=value.run, value=value.value, metric=value.metric
75
- )
76
- forks_specified.append(key)
77
- else:
78
- value = None
79
- else:
80
- if proto.HasField(key): # type: ignore [arg-type]
81
- value = getattr(proto, key).value
82
- # Convert to list if the field is a sequence
83
- if any(t in annotation for t in ("tuple", "Sequence", "list")):
84
- value = list(value)
85
- else:
86
- value = None
87
-
88
- if value is not None:
89
- data[key] = value
90
-
91
- if len(forks_specified) > 1:
92
- raise ValueError(
93
- "Only one of fork_from or resume_from can be specified, not both"
94
- )
95
-
96
- return data
97
-
98
18
  def __setattr__(self, name: str, value: object) -> None:
99
19
  raise AttributeError("Error: SettingsStatic is a readonly object")
100
20
 
@@ -417,10 +417,11 @@ def _configure_job_builder_for_partial(tmpdir: str, job_source: str) -> JobBuild
417
417
  if job_source == "code":
418
418
  job_source = "artifact"
419
419
 
420
- settings = wandb.Settings(x_files_dir=tmpdir, job_source=job_source)
420
+ settings = wandb.Settings(job_source=job_source)
421
421
  job_builder = JobBuilder(
422
422
  settings=settings, # type: ignore
423
423
  verbose=True,
424
+ files_dir=tmpdir,
424
425
  )
425
426
  job_builder._partial = True
426
427
  # never allow notebook runs
@@ -27,7 +27,11 @@ from wandb.sdk.launch.runner.kubernetes_monitor import (
27
27
  CustomResource,
28
28
  LaunchKubernetesMonitor,
29
29
  )
30
- from wandb.sdk.launch.utils import recursive_macro_sub
30
+ from wandb.sdk.launch.utils import (
31
+ recursive_macro_sub,
32
+ sanitize_identifiers_for_k8s,
33
+ yield_containers,
34
+ )
31
35
  from wandb.sdk.lib.retry import ExponentialBackoff, retry_async
32
36
  from wandb.util import get_module
33
37
 
@@ -39,6 +43,7 @@ from ..utils import (
39
43
  MAX_ENV_LENGTHS,
40
44
  PROJECT_SYNCHRONOUS,
41
45
  get_kube_context_and_api_client,
46
+ make_k8s_label_safe,
42
47
  make_name_dns_safe,
43
48
  )
44
49
  from .abstract import AbstractRun, AbstractRunner
@@ -708,6 +713,7 @@ class KubernetesRunner(AbstractRunner):
708
713
  api_key_secret: Optional["V1Secret"] = None,
709
714
  wait_for_ready: bool = True,
710
715
  wait_timeout: int = 300,
716
+ auxiliary_resource_label_value: Optional[str] = None,
711
717
  ) -> None:
712
718
  """Prepare a service for launch.
713
719
 
@@ -725,6 +731,10 @@ class KubernetesRunner(AbstractRunner):
725
731
  config["metadata"].setdefault("labels", {})
726
732
  config["metadata"]["labels"][WANDB_K8S_RUN_ID] = run_id
727
733
  config["metadata"]["labels"]["wandb.ai/created-by"] = "launch-agent"
734
+ if auxiliary_resource_label_value:
735
+ config["metadata"]["labels"][WANDB_K8S_LABEL_AUXILIARY_RESOURCE] = (
736
+ auxiliary_resource_label_value
737
+ )
728
738
 
729
739
  env_vars = launch_project.get_env_vars_dict(
730
740
  self._api, MAX_ENV_LENGTHS[self.__class__.__name__]
@@ -734,6 +744,13 @@ class KubernetesRunner(AbstractRunner):
734
744
  }
735
745
  add_wandb_env(config, wandb_config_env)
736
746
 
747
+ if auxiliary_resource_label_value:
748
+ add_label_to_pods(
749
+ config,
750
+ WANDB_K8S_LABEL_AUXILIARY_RESOURCE,
751
+ auxiliary_resource_label_value,
752
+ )
753
+
737
754
  if api_key_secret:
738
755
  for cont in yield_containers(config):
739
756
  env = cont.setdefault("env", [])
@@ -751,6 +768,8 @@ class KubernetesRunner(AbstractRunner):
751
768
  cont["env"] = env
752
769
 
753
770
  try:
771
+ sanitize_identifiers_for_k8s(config)
772
+
754
773
  await kubernetes_asyncio.utils.create_from_dict(
755
774
  api_client, config, namespace=namespace
756
775
  )
@@ -924,6 +943,9 @@ class KubernetesRunner(AbstractRunner):
924
943
  additional_services: List[Dict[str, Any]] = recursive_macro_sub(
925
944
  launch_project.launch_spec.get("additional_services", []), update_dict
926
945
  )
946
+ auxiliary_resource_label_value = make_k8s_label_safe(
947
+ f"aux-{launch_project.target_entity}-{launch_project.target_project}-{launch_project.run_id}"
948
+ )
927
949
  if additional_services:
928
950
  wandb.termlog(
929
951
  f"{LOG_PREFIX}Creating additional services: {additional_services}"
@@ -943,6 +965,7 @@ class KubernetesRunner(AbstractRunner):
943
965
  secret,
944
966
  wait_for_ready,
945
967
  wait_timeout,
968
+ auxiliary_resource_label_value,
946
969
  )
947
970
  for resource in additional_services
948
971
  if resource.get("config", {})
@@ -980,7 +1003,7 @@ class KubernetesRunner(AbstractRunner):
980
1003
  job_name,
981
1004
  namespace,
982
1005
  secret,
983
- f"aux-{launch_project.target_entity}-{launch_project.target_project}-{launch_project.run_id}",
1006
+ auxiliary_resource_label_value,
984
1007
  )
985
1008
  if self.backend_config[PROJECT_SYNCHRONOUS]:
986
1009
  await submitted_job.wait()
@@ -1131,24 +1154,6 @@ async def maybe_create_imagepull_secret(
1131
1154
  raise LaunchError(f"Exception when creating Kubernetes secret: {str(e)}\n")
1132
1155
 
1133
1156
 
1134
- def yield_containers(root: Any) -> Iterator[dict]:
1135
- """Yield all container specs in a manifest.
1136
-
1137
- Recursively traverses the manifest and yields all container specs. Container
1138
- specs are identified by the presence of a "containers" key in the value.
1139
- """
1140
- if isinstance(root, dict):
1141
- for k, v in root.items():
1142
- if k == "containers":
1143
- if isinstance(v, list):
1144
- yield from v
1145
- elif isinstance(v, (dict, list)):
1146
- yield from yield_containers(v)
1147
- elif isinstance(root, list):
1148
- for item in root:
1149
- yield from yield_containers(item)
1150
-
1151
-
1152
1157
  def add_wandb_env(root: Union[dict, list], env_vars: Dict[str, str]) -> None:
1153
1158
  """Injects wandb environment variables into specs.
1154
1159