wandb 0.16.3__py3-none-any.whl → 0.16.5__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. wandb/__init__.py +2 -2
  2. wandb/agents/pyagent.py +1 -1
  3. wandb/apis/importers/__init__.py +1 -4
  4. wandb/apis/importers/internals/internal.py +386 -0
  5. wandb/apis/importers/internals/protocols.py +125 -0
  6. wandb/apis/importers/internals/util.py +78 -0
  7. wandb/apis/importers/mlflow.py +125 -88
  8. wandb/apis/importers/validation.py +108 -0
  9. wandb/apis/importers/wandb.py +1604 -0
  10. wandb/apis/public/api.py +7 -10
  11. wandb/apis/public/artifacts.py +38 -0
  12. wandb/apis/public/files.py +11 -2
  13. wandb/apis/reports/v2/__init__.py +0 -19
  14. wandb/apis/reports/v2/expr_parsing.py +0 -1
  15. wandb/apis/reports/v2/interface.py +15 -18
  16. wandb/apis/reports/v2/internal.py +12 -45
  17. wandb/cli/cli.py +52 -55
  18. wandb/integration/gym/__init__.py +2 -1
  19. wandb/integration/keras/callbacks/model_checkpoint.py +1 -1
  20. wandb/integration/keras/keras.py +6 -4
  21. wandb/integration/kfp/kfp_patch.py +2 -2
  22. wandb/integration/openai/fine_tuning.py +1 -2
  23. wandb/integration/ultralytics/callback.py +0 -1
  24. wandb/proto/v3/wandb_internal_pb2.py +332 -312
  25. wandb/proto/v3/wandb_settings_pb2.py +13 -3
  26. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  27. wandb/proto/v4/wandb_internal_pb2.py +316 -312
  28. wandb/proto/v4/wandb_settings_pb2.py +5 -3
  29. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  30. wandb/sdk/artifacts/artifact.py +75 -31
  31. wandb/sdk/artifacts/artifact_manifest.py +5 -2
  32. wandb/sdk/artifacts/artifact_manifest_entry.py +6 -1
  33. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +8 -2
  34. wandb/sdk/artifacts/artifact_saver.py +19 -47
  35. wandb/sdk/artifacts/storage_handler.py +2 -1
  36. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +22 -9
  37. wandb/sdk/artifacts/storage_policy.py +4 -1
  38. wandb/sdk/data_types/base_types/wb_value.py +1 -1
  39. wandb/sdk/data_types/image.py +2 -2
  40. wandb/sdk/interface/interface.py +49 -13
  41. wandb/sdk/interface/interface_shared.py +17 -11
  42. wandb/sdk/internal/file_stream.py +20 -1
  43. wandb/sdk/internal/handler.py +1 -4
  44. wandb/sdk/internal/internal_api.py +3 -1
  45. wandb/sdk/internal/job_builder.py +49 -19
  46. wandb/sdk/internal/profiler.py +1 -1
  47. wandb/sdk/internal/sender.py +96 -124
  48. wandb/sdk/internal/sender_config.py +197 -0
  49. wandb/sdk/internal/settings_static.py +9 -0
  50. wandb/sdk/internal/system/system_info.py +5 -3
  51. wandb/sdk/internal/update.py +1 -1
  52. wandb/sdk/launch/_launch.py +3 -3
  53. wandb/sdk/launch/_launch_add.py +28 -29
  54. wandb/sdk/launch/_project_spec.py +148 -136
  55. wandb/sdk/launch/agent/agent.py +3 -7
  56. wandb/sdk/launch/agent/config.py +0 -27
  57. wandb/sdk/launch/builder/build.py +54 -28
  58. wandb/sdk/launch/builder/docker_builder.py +4 -15
  59. wandb/sdk/launch/builder/kaniko_builder.py +72 -45
  60. wandb/sdk/launch/create_job.py +6 -40
  61. wandb/sdk/launch/loader.py +10 -0
  62. wandb/sdk/launch/registry/anon.py +29 -0
  63. wandb/sdk/launch/registry/local_registry.py +4 -1
  64. wandb/sdk/launch/runner/kubernetes_runner.py +20 -2
  65. wandb/sdk/launch/runner/local_container.py +15 -10
  66. wandb/sdk/launch/runner/sagemaker_runner.py +1 -1
  67. wandb/sdk/launch/sweeps/scheduler.py +11 -3
  68. wandb/sdk/launch/utils.py +14 -0
  69. wandb/sdk/lib/__init__.py +2 -5
  70. wandb/sdk/lib/_settings_toposort_generated.py +4 -1
  71. wandb/sdk/lib/apikey.py +0 -5
  72. wandb/sdk/lib/config_util.py +0 -31
  73. wandb/sdk/lib/filesystem.py +11 -1
  74. wandb/sdk/lib/run_moment.py +72 -0
  75. wandb/sdk/service/service.py +7 -2
  76. wandb/sdk/service/streams.py +1 -6
  77. wandb/sdk/verify/verify.py +2 -1
  78. wandb/sdk/wandb_init.py +12 -1
  79. wandb/sdk/wandb_login.py +43 -26
  80. wandb/sdk/wandb_run.py +164 -110
  81. wandb/sdk/wandb_settings.py +58 -16
  82. wandb/testing/relay.py +5 -6
  83. wandb/util.py +50 -7
  84. {wandb-0.16.3.dist-info → wandb-0.16.5.dist-info}/METADATA +8 -1
  85. {wandb-0.16.3.dist-info → wandb-0.16.5.dist-info}/RECORD +89 -82
  86. {wandb-0.16.3.dist-info → wandb-0.16.5.dist-info}/WHEEL +1 -1
  87. wandb/apis/importers/base.py +0 -400
  88. {wandb-0.16.3.dist-info → wandb-0.16.5.dist-info}/LICENSE +0 -0
  89. {wandb-0.16.3.dist-info → wandb-0.16.5.dist-info}/entry_points.txt +0 -0
  90. {wandb-0.16.3.dist-info → wandb-0.16.5.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,6 @@ import wandb
6
6
  import wandb.apis.public as public
7
7
  from wandb.apis.internal import Api
8
8
  from wandb.errors import CommError
9
- from wandb.sdk.launch._project_spec import create_project_from_spec
10
9
  from wandb.sdk.launch.builder.build import build_image_from_project
11
10
  from wandb.sdk.launch.errors import LaunchError
12
11
  from wandb.sdk.launch.utils import (
@@ -16,6 +15,8 @@ from wandb.sdk.launch.utils import (
16
15
  validate_launch_spec_source,
17
16
  )
18
17
 
18
+ from ._project_spec import LaunchProject
19
+
19
20
 
20
21
  def push_to_queue(
21
22
  api: Api,
@@ -106,34 +107,32 @@ def launch_add(
106
107
  """
107
108
  api = Api()
108
109
 
109
- return asyncio.run(
110
- _launch_add(
111
- api,
112
- uri,
113
- job,
114
- config,
115
- template_variables,
116
- project,
117
- entity,
118
- queue_name,
119
- resource,
120
- entry_point,
121
- name,
122
- version,
123
- docker_image,
124
- project_queue,
125
- resource_args,
126
- run_id=run_id,
127
- build=build,
128
- repository=repository,
129
- sweep_id=sweep_id,
130
- author=author,
131
- priority=priority,
132
- )
110
+ return _launch_add(
111
+ api,
112
+ uri,
113
+ job,
114
+ config,
115
+ template_variables,
116
+ project,
117
+ entity,
118
+ queue_name,
119
+ resource,
120
+ entry_point,
121
+ name,
122
+ version,
123
+ docker_image,
124
+ project_queue,
125
+ resource_args,
126
+ run_id=run_id,
127
+ build=build,
128
+ repository=repository,
129
+ sweep_id=sweep_id,
130
+ author=author,
131
+ priority=priority,
133
132
  )
134
133
 
135
134
 
136
- async def _launch_add(
135
+ def _launch_add(
137
136
  api: Api,
138
137
  uri: Optional[str],
139
138
  job: Optional[str],
@@ -185,9 +184,9 @@ async def _launch_add(
185
184
  wandb.termwarn("Build doesn't support setting a job. Overwriting job.")
186
185
  launch_spec["job"] = None
187
186
 
188
- launch_project = create_project_from_spec(launch_spec, api)
189
- docker_image_uri = await build_image_from_project(
190
- launch_project, api, config or {}
187
+ launch_project = LaunchProject.from_spec(launch_spec, api)
188
+ docker_image_uri = asyncio.run(
189
+ build_image_from_project(launch_project, api, config or {})
191
190
  )
192
191
  run = wandb.run or wandb.init(
193
192
  project=launch_spec["project"],
@@ -3,7 +3,6 @@
3
3
  Arguments can come from a launch spec or call to wandb launch.
4
4
  """
5
5
  import enum
6
- import json
7
6
  import logging
8
7
  import os
9
8
  import tempfile
@@ -25,7 +24,6 @@ if TYPE_CHECKING:
25
24
 
26
25
  _logger = logging.getLogger(__name__)
27
26
 
28
- DEFAULT_LAUNCH_METADATA_PATH = "launch_metadata.json"
29
27
 
30
28
  # need to make user root for sagemaker, so users have access to /opt/ml directories
31
29
  # that let users create artifacts and access input data
@@ -46,7 +44,22 @@ class EntrypointDefaults(List[str]):
46
44
 
47
45
 
48
46
  class LaunchProject:
49
- """A launch project specification."""
47
+ """A launch project specification.
48
+
49
+ The LaunchProject is initialized from a raw launch spec an internal API
50
+ object. The project encapsulates logic for taking a launch spec and converting
51
+ it into the executable code.
52
+
53
+ The LaunchProject needs to ultimately produce a full container spec for
54
+ execution in docker, k8s, sagemaker, or vertex. This container spec includes:
55
+ - container image uri
56
+ - environment variables for configuring wandb etc.
57
+ - entrypoint command and arguments
58
+ - additional arguments specific to the target resource (e.g. instance type, node selector)
59
+
60
+ This class is stateful and certain methods can only be called after
61
+ `LaunchProject.fetch_and_validate_project()` has been called.
62
+ """
50
63
 
51
64
  def __init__(
52
65
  self,
@@ -122,7 +135,7 @@ class LaunchProject:
122
135
  if override_entrypoint:
123
136
  _logger.info("Adding override entry point")
124
137
  self.override_entrypoint = EntryPoint(
125
- name=self._get_entrypoint_file(override_entrypoint),
138
+ name=_get_entrypoint_file(override_entrypoint),
126
139
  command=override_entrypoint,
127
140
  )
128
141
 
@@ -160,22 +173,42 @@ class LaunchProject:
160
173
  self.source = LaunchSource.LOCAL
161
174
  self.project_dir = self.uri
162
175
 
163
- self.aux_dir = tempfile.mkdtemp()
176
+ def __repr__(self) -> str:
177
+ """String representation of LaunchProject."""
178
+ if self.source == LaunchSource.JOB:
179
+ return f"{self.job}"
180
+ return f"{self.uri}"
164
181
 
165
- @property
166
- def base_image(self) -> str:
167
- """Returns {PROJECT}_base:{PYTHON_VERSION}."""
168
- # TODO: this should likely be source_project when we have it...
182
+ @classmethod
183
+ def from_spec(cls, launch_spec: Dict[str, Any], api: Api) -> "LaunchProject":
184
+ """Constructs a LaunchProject instance using a launch spec.
169
185
 
170
- # don't make up a separate base image name if user provides a docker image
171
- if self.docker_image is not None:
172
- return self.docker_image
186
+ Arguments:
187
+ launch_spec: Dictionary representation of launch spec
188
+ api: Instance of wandb.apis.internal Api
173
189
 
174
- python_version = (self.python_version or "3").replace("+", "dev")
175
- generated_name = "{}_base:{}".format(
176
- self.target_project.replace(" ", "-"), python_version
190
+ Returns:
191
+ An initialized `LaunchProject` object
192
+ """
193
+ name: Optional[str] = None
194
+ if launch_spec.get("name"):
195
+ name = launch_spec["name"]
196
+ return LaunchProject(
197
+ launch_spec.get("uri"),
198
+ launch_spec.get("job"),
199
+ api,
200
+ launch_spec,
201
+ launch_spec["entity"],
202
+ launch_spec["project"],
203
+ name,
204
+ launch_spec.get("docker", {}),
205
+ launch_spec.get("git", {}),
206
+ launch_spec.get("overrides", {}),
207
+ launch_spec.get("resource", None),
208
+ launch_spec.get("resource_args", {}),
209
+ launch_spec.get("run_id", None),
210
+ launch_spec.get("sweep_id", {}),
177
211
  )
178
- return self._base_image or generated_name
179
212
 
180
213
  @property
181
214
  def image_name(self) -> str:
@@ -215,15 +248,6 @@ class LaunchProject:
215
248
  def run_queue_item_id(self, value: str) -> None:
216
249
  self._run_queue_item_id = value
217
250
 
218
- def _get_entrypoint_file(self, entrypoint: List[str]) -> Optional[str]:
219
- if not entrypoint:
220
- return None
221
- if entrypoint[0].endswith(".py") or entrypoint[0].endswith(".sh"):
222
- return entrypoint[0]
223
- if len(entrypoint) < 2:
224
- return None
225
- return entrypoint[1]
226
-
227
251
  def fill_macros(self, image: str) -> Dict[str, Any]:
228
252
  """Substitute values for macros in resource arguments.
229
253
 
@@ -277,10 +301,25 @@ class LaunchProject:
277
301
 
278
302
  @property
279
303
  def docker_image(self) -> Optional[str]:
304
+ """Returns the Docker image associated with this LaunchProject.
305
+
306
+ This will only be set if an image_uri is being run outside a job.
307
+
308
+ Returns:
309
+ Optional[str]: The Docker image or None if not specified.
310
+ """
280
311
  return self._docker_image
281
312
 
282
313
  @docker_image.setter
283
314
  def docker_image(self, value: str) -> None:
315
+ """Sets the Docker image for the project.
316
+
317
+ Args:
318
+ value (str): The Docker image to set.
319
+
320
+ Returns:
321
+ None
322
+ """
284
323
  self._docker_image = value
285
324
  self._ensure_not_docker_image_and_local_process()
286
325
 
@@ -305,24 +344,39 @@ class LaunchProject:
305
344
  self._entry_point = new_entrypoint
306
345
  return new_entrypoint
307
346
 
308
- def _ensure_not_docker_image_and_local_process(self) -> None:
309
- if self.docker_image is not None and self.resource == "local-process":
310
- raise LaunchError(
311
- "Cannot specify docker image with local-process resource runner"
312
- )
347
+ def fetch_and_validate_project(self) -> None:
348
+ """Fetches a project into a local directory, adds the config values to the directory, and validates the first entrypoint for the project.
313
349
 
314
- def _fetch_job(self) -> None:
315
- public_api = wandb.apis.public.Api()
316
- job_dir = tempfile.mkdtemp()
317
- try:
318
- job = public_api.job(self.job, path=job_dir)
319
- except CommError as e:
320
- msg = e.message
321
- raise LaunchError(
322
- f"Error accessing job {self.job}: {msg} on {public_api.settings.get('base_url')}"
323
- )
324
- job.configure_launch_project(self)
325
- self._job_artifact = job._job_artifact
350
+ Arguments:
351
+ launch_project: LaunchProject to fetch and validate.
352
+ api: Instance of wandb.apis.internal Api
353
+
354
+ Returns:
355
+ A validated `LaunchProject` object.
356
+
357
+ """
358
+ if self.source == LaunchSource.DOCKER:
359
+ return
360
+ if self.source == LaunchSource.LOCAL:
361
+ if not self._entry_point:
362
+ wandb.termlog(
363
+ f"{LOG_PREFIX}Entry point for repo not specified, defaulting to `python main.py`"
364
+ )
365
+ self.set_entry_point(EntrypointDefaults.PYTHON)
366
+ elif self.source == LaunchSource.JOB:
367
+ self._fetch_job()
368
+ else:
369
+ self._fetch_project_local(internal_api=self.api)
370
+
371
+ assert self.project_dir is not None
372
+ # this prioritizes pip, and we don't support any cases where both are present conda projects when uploaded to
373
+ # wandb become pip projects via requirements.frozen.txt, wandb doesn't preserve conda envs
374
+ if os.path.exists(
375
+ os.path.join(self.project_dir, "requirements.txt")
376
+ ) or os.path.exists(os.path.join(self.project_dir, "requirements.frozen.txt")):
377
+ self.deps_type = "pip"
378
+ elif os.path.exists(os.path.join(self.project_dir, "environment.yml")):
379
+ self.deps_type = "conda"
326
380
 
327
381
  def get_image_source_string(self) -> str:
328
382
  """Returns a unique string identifying the source of an image."""
@@ -349,6 +403,35 @@ class LaunchProject:
349
403
  else:
350
404
  raise LaunchError("Unknown source type when determing image source string")
351
405
 
406
+ def _ensure_not_docker_image_and_local_process(self) -> None:
407
+ """Ensure that docker image is not specified with local-process resource runner.
408
+
409
+ Raises:
410
+ LaunchError: If docker image is specified with local-process resource runner.
411
+ """
412
+ if self.docker_image is not None and self.resource == "local-process":
413
+ raise LaunchError(
414
+ "Cannot specify docker image with local-process resource runner"
415
+ )
416
+
417
+ def _fetch_job(self) -> None:
418
+ """Fetches the job details from the public API and configures the launch project.
419
+
420
+ Raises:
421
+ LaunchError: If there is an error accessing the job.
422
+ """
423
+ public_api = wandb.apis.public.Api()
424
+ job_dir = tempfile.mkdtemp()
425
+ try:
426
+ job = public_api.job(self.job, path=job_dir)
427
+ except CommError as e:
428
+ msg = e.message
429
+ raise LaunchError(
430
+ f"Error accessing job {self.job}: {msg} on {public_api.settings.get('base_url')}"
431
+ )
432
+ job.configure_launch_project(self)
433
+ self._job_artifact = job._job_artifact
434
+
352
435
  def _fetch_project_local(self, internal_api: Api) -> None:
353
436
  """Fetch a project (either wandb run or git repo) into a local directory, returning the path to the local project directory."""
354
437
  # these asserts are all guaranteed to pass, but are required by mypy
@@ -453,6 +536,24 @@ class LaunchProject:
453
536
  self.git_version = branch_name
454
537
 
455
538
 
539
+ def _get_entrypoint_file(entrypoint: List[str]) -> Optional[str]:
540
+ """Get the entrypoint file from the given command.
541
+
542
+ Args:
543
+ entrypoint (List[str]): List of command and arguments.
544
+
545
+ Returns:
546
+ Optional[str]: The entrypoint file if found, otherwise None.
547
+ """
548
+ if not entrypoint:
549
+ return None
550
+ if entrypoint[0].endswith(".py") or entrypoint[0].endswith(".sh"):
551
+ return entrypoint[0]
552
+ if len(entrypoint) < 2:
553
+ return None
554
+ return entrypoint[1]
555
+
556
+
456
557
  class EntryPoint:
457
558
  """An entry point into a wandb launch specification."""
458
559
 
@@ -467,6 +568,11 @@ class EntryPoint:
467
568
  return ret + user_parameters
468
569
  return ret
469
570
 
571
+ def update_entrypoint_path(self, new_path: str) -> None:
572
+ """Updates the entrypoint path to a new path."""
573
+ if len(self.command) == 2 and self.command[0] in ["python", "bash"]:
574
+ self.command[1] = new_path
575
+
470
576
 
471
577
  def get_entry_point_command(
472
578
  entry_point: Optional["EntryPoint"], parameters: List[str]
@@ -483,97 +589,3 @@ def get_entry_point_command(
483
589
  if entry_point is None:
484
590
  return []
485
591
  return entry_point.compute_command(parameters)
486
-
487
-
488
- def create_project_from_spec(launch_spec: Dict[str, Any], api: Api) -> LaunchProject:
489
- """Constructs a LaunchProject instance using a launch spec.
490
-
491
- Arguments:
492
- launch_spec: Dictionary representation of launch spec
493
- api: Instance of wandb.apis.internal Api
494
-
495
- Returns:
496
- An initialized `LaunchProject` object
497
- """
498
- name: Optional[str] = None
499
- if launch_spec.get("name"):
500
- name = launch_spec["name"]
501
- return LaunchProject(
502
- launch_spec.get("uri"),
503
- launch_spec.get("job"),
504
- api,
505
- launch_spec,
506
- launch_spec["entity"],
507
- launch_spec["project"],
508
- name,
509
- launch_spec.get("docker", {}),
510
- launch_spec.get("git", {}),
511
- launch_spec.get("overrides", {}),
512
- launch_spec.get("resource", None),
513
- launch_spec.get("resource_args", {}),
514
- launch_spec.get("run_id", None),
515
- launch_spec.get("sweep_id", {}),
516
- )
517
-
518
-
519
- def fetch_and_validate_project(
520
- launch_project: LaunchProject, api: Api
521
- ) -> LaunchProject:
522
- """Fetches a project into a local directory, adds the config values to the directory, and validates the first entrypoint for the project.
523
-
524
- Arguments:
525
- launch_project: LaunchProject to fetch and validate.
526
- api: Instance of wandb.apis.internal Api
527
-
528
- Returns:
529
- A validated `LaunchProject` object.
530
-
531
- """
532
- if launch_project.source == LaunchSource.DOCKER:
533
- return launch_project
534
- if launch_project.source == LaunchSource.LOCAL:
535
- if not launch_project._entry_point:
536
- wandb.termlog(
537
- f"{LOG_PREFIX}Entry point for repo not specified, defaulting to `python main.py`"
538
- )
539
- launch_project.set_entry_point(EntrypointDefaults.PYTHON)
540
- elif launch_project.source == LaunchSource.JOB:
541
- launch_project._fetch_job()
542
- else:
543
- launch_project._fetch_project_local(internal_api=api)
544
-
545
- assert launch_project.project_dir is not None
546
- # this prioritizes pip, and we don't support any cases where both are present conda projects when uploaded to
547
- # wandb become pip projects via requirements.frozen.txt, wandb doesn't preserve conda envs
548
- if os.path.exists(
549
- os.path.join(launch_project.project_dir, "requirements.txt")
550
- ) or os.path.exists(
551
- os.path.join(launch_project.project_dir, "requirements.frozen.txt")
552
- ):
553
- launch_project.deps_type = "pip"
554
- elif os.path.exists(os.path.join(launch_project.project_dir, "environment.yml")):
555
- launch_project.deps_type = "conda"
556
-
557
- return launch_project
558
-
559
-
560
- def create_metadata_file(
561
- launch_project: LaunchProject,
562
- image_uri: str,
563
- sanitized_entrypoint_str: str,
564
- sanitized_dockerfile_contents: str,
565
- ) -> None:
566
- assert launch_project.project_dir is not None
567
- with open(
568
- os.path.join(launch_project.project_dir, DEFAULT_LAUNCH_METADATA_PATH),
569
- "w",
570
- ) as f:
571
- json.dump(
572
- {
573
- **launch_project.launch_spec,
574
- "image_uri": image_uri,
575
- "command": sanitized_entrypoint_str,
576
- "dockerfile_contents": sanitized_dockerfile_contents,
577
- },
578
- f,
579
- )
@@ -20,11 +20,7 @@ from wandb.sdk.launch.sweeps.scheduler import Scheduler
20
20
  from wandb.sdk.lib import runid
21
21
 
22
22
  from .. import loader
23
- from .._project_spec import (
24
- LaunchProject,
25
- create_project_from_spec,
26
- fetch_and_validate_project,
27
- )
23
+ from .._project_spec import LaunchProject
28
24
  from ..builder.build import construct_agent_configs
29
25
  from ..errors import LaunchDockerError, LaunchError
30
26
  from ..utils import (
@@ -630,7 +626,7 @@ class LaunchAgent:
630
626
  thread_id: int,
631
627
  job_tracker: JobAndRunStatusTracker,
632
628
  ) -> None:
633
- project = create_project_from_spec(launch_spec, api)
629
+ project = LaunchProject.from_spec(launch_spec, api)
634
630
  self._set_queue_and_rqi_in_project(project, job, job_tracker.queue)
635
631
  ack = event_loop_thread_exec(api.ack_run_queue_item)
636
632
  await ack(job["runQueueItemId"], project.run_id)
@@ -639,7 +635,7 @@ class LaunchAgent:
639
635
 
640
636
  job_tracker.update_run_info(project)
641
637
  _logger.info("Fetching and validating project...")
642
- project = fetch_and_validate_project(project, api)
638
+ project.fetch_and_validate_project()
643
639
  _logger.info("Fetching resource...")
644
640
  resource = launch_spec.get("resource") or "local-container"
645
641
  backend_config: Dict[str, Any] = {
@@ -128,22 +128,6 @@ class BuilderConfig(BaseModel):
128
128
  "the image will be pushed to the registry.",
129
129
  )
130
130
 
131
- @validator("destination") # type: ignore
132
- @classmethod
133
- def validate_destination(cls, destination: str) -> str:
134
- """Validate that the destination is a valid container registry URI."""
135
- for regex in [
136
- GCP_ARTIFACT_REGISTRY_URI_REGEX,
137
- AZURE_CONTAINER_REGISTRY_URI_REGEX,
138
- ELASTIC_CONTAINER_REGISTRY_URI_REGEX,
139
- ]:
140
- if regex.match(destination):
141
- return destination
142
- raise ValueError(
143
- "Invalid destination. Destination must be a repository URI for an "
144
- "ECR, ACR, or GCP Artifact Registry."
145
- )
146
-
147
131
  platform: Optional[TargetPlatform] = Field(
148
132
  None,
149
133
  description="The platform to use for the built image. If not provided, "
@@ -196,17 +180,6 @@ class BuilderConfig(BaseModel):
196
180
  "S3 bucket, GCS bucket, or Azure blob."
197
181
  )
198
182
 
199
- @root_validator(pre=True) # type: ignore
200
- @classmethod
201
- def validate_kaniko(cls, values: dict) -> dict:
202
- """Validate that kaniko is configured correctly."""
203
- if values.get("type") == BuilderType.kaniko:
204
- if values.get("build-context-store") is None:
205
- raise ValueError(
206
- "builder.build-context-store is required if builder.type is set to kaniko."
207
- )
208
- return values
209
-
210
183
  @root_validator(pre=True) # type: ignore
211
184
  @classmethod
212
185
  def validate_docker(cls, values: dict) -> dict:
@@ -2,13 +2,13 @@ import hashlib
2
2
  import json
3
3
  import logging
4
4
  import os
5
+ import pathlib
5
6
  import shlex
6
7
  import shutil
7
8
  import sys
8
9
  import tempfile
9
10
  from typing import Any, Dict, List, Optional, Tuple
10
11
 
11
- import pkg_resources
12
12
  import yaml
13
13
  from dockerpycreds.utils import find_executable # type: ignore
14
14
  from six.moves import shlex_quote
@@ -22,15 +22,12 @@ from wandb.sdk.launch.loader import (
22
22
  environment_from_config,
23
23
  registry_from_config,
24
24
  )
25
+ from wandb.util import get_module
25
26
 
26
- from .._project_spec import (
27
- EntryPoint,
28
- EntrypointDefaults,
29
- LaunchProject,
30
- fetch_and_validate_project,
31
- )
27
+ from .._project_spec import EntryPoint, EntrypointDefaults, LaunchProject
32
28
  from ..errors import ExecutionError, LaunchError
33
29
  from ..registry.abstract import AbstractRegistry
30
+ from ..registry.anon import AnonynmousRegistry
34
31
  from ..utils import (
35
32
  AZURE_CONTAINER_REGISTRY_URI_REGEX,
36
33
  ELASTIC_CONTAINER_REGISTRY_URI_REGEX,
@@ -105,8 +102,7 @@ def registry_from_uri(uri: str) -> AbstractRegistry:
105
102
 
106
103
  return ElasticContainerRegistry(uri=uri)
107
104
 
108
- else:
109
- raise LaunchError(f"Unsupported registry URI: {uri}. Unable to load helper.")
105
+ return AnonynmousRegistry(uri=uri)
110
106
 
111
107
 
112
108
  async def validate_docker_installation() -> None:
@@ -315,7 +311,6 @@ def get_env_vars_dict(
315
311
  _inject_wandb_config_env_vars(
316
312
  launch_project.override_config, env_vars, max_env_length
317
313
  )
318
- # env_vars["WANDB_CONFIG"] = json.dumps(launch_project.override_config)
319
314
  artifacts = {}
320
315
  # if we're spinning up a launch process from a job
321
316
  # we should tell the run to use that artifact
@@ -340,27 +335,56 @@ def get_requirements_section(launch_project: LaunchProject, builder_type: str) -
340
335
  buildx_installed = False
341
336
  if launch_project.deps_type == "pip":
342
337
  requirements_files = []
343
- if launch_project.project_dir is not None and os.path.exists(
344
- os.path.join(launch_project.project_dir, "requirements.txt")
345
- ):
338
+ deps_install_line = None
339
+ assert launch_project.project_dir is not None
340
+ base_path = pathlib.Path(launch_project.project_dir)
341
+ # If there is a requirements.txt at root of build context, use that.
342
+ if (base_path / "requirements.txt").exists():
346
343
  requirements_files += ["src/requirements.txt"]
347
- pip_install_line = "pip install -r requirements.txt"
348
- elif launch_project.project_dir is not None and os.path.exists(
349
- os.path.join(launch_project.project_dir, "requirements.frozen.txt")
350
- ):
351
- # if we have frozen requirements stored, copy those over and have them take precedence
352
- requirements_files += ["src/requirements.frozen.txt", "_wandb_bootstrap.py"]
353
- pip_install_line = (
344
+ deps_install_line = "pip install -r requirements.txt"
345
+ # Elif there is pyproject.toml at build context, convert the dependencies
346
+ # section to a requirements.txt and use that.
347
+ elif (base_path / "pyproject.toml").exists():
348
+ tomli = get_module("tomli")
349
+ if tomli is None:
350
+ wandb.termwarn(
351
+ "pyproject.toml found but tomli could not be loaded. To "
352
+ "install dependencies from pyproject.toml please run "
353
+ "`pip install tomli` and try again."
354
+ )
355
+ else:
356
+ # First try to read deps from standard pyproject format.
357
+ with open(base_path / "pyproject.toml", "rb") as f:
358
+ contents = tomli.load(f)
359
+ project_deps = [
360
+ str(d) for d in contents.get("project", {}).get("dependencies", [])
361
+ ]
362
+ if project_deps:
363
+ with open(base_path / "requirements.txt", "w") as f:
364
+ f.write("\n".join(project_deps))
365
+ requirements_files += ["src/requirements.txt"]
366
+ deps_install_line = "pip install -r requirements.txt"
367
+ # Else use frozen requirements from wandb run.
368
+ if not deps_install_line and (base_path / "requirements.frozen.txt").exists():
369
+ requirements_files += [
370
+ "src/requirements.frozen.txt",
371
+ "_wandb_bootstrap.py",
372
+ ]
373
+ deps_install_line = (
354
374
  _parse_existing_requirements(launch_project)
355
375
  + "python _wandb_bootstrap.py"
356
376
  )
377
+
378
+ if not deps_install_line:
379
+ raise LaunchError(f"No dependency sources found for {launch_project}")
380
+
357
381
  if buildx_installed:
358
382
  prefix = "RUN --mount=type=cache,mode=0777,target=/root/.cache/pip"
359
383
 
360
384
  requirements_line = PIP_TEMPLATE.format(
361
385
  buildx_optional_prefix=prefix,
362
386
  requirements_files=" ".join(requirements_files),
363
- pip_install=pip_install_line,
387
+ pip_install=deps_install_line,
364
388
  )
365
389
  elif launch_project.deps_type == "conda":
366
390
  if buildx_installed:
@@ -446,13 +470,9 @@ def generate_dockerfile(
446
470
  return dockerfile_contents
447
471
 
448
472
 
449
- def construct_gcp_registry_uri(
450
- gcp_repo: str, gcp_project: str, gcp_registry: str
451
- ) -> str:
452
- return "/".join([gcp_registry, gcp_project, gcp_repo])
453
-
454
-
455
473
  def _parse_existing_requirements(launch_project: LaunchProject) -> str:
474
+ import pkg_resources
475
+
456
476
  requirements_line = ""
457
477
  assert launch_project.project_dir is not None
458
478
  base_requirements = os.path.join(launch_project.project_dir, "requirements.txt")
@@ -506,6 +526,12 @@ def _create_docker_build_ctx(
506
526
  dirs_exist_ok=True,
507
527
  ignore=shutil.ignore_patterns("fsmonitor--daemon.ipc"),
508
528
  )
529
+ # TODO: remove this once we make things more explicit for users
530
+ if entrypoint_dir:
531
+ new_path = os.path.basename(entrypoint.name)
532
+ entrypoint = launch_project.get_single_entry_point()
533
+ if entrypoint is not None:
534
+ entrypoint.update_entrypoint_path(new_path)
509
535
  return directory
510
536
 
511
537
  dst_path = os.path.join(directory, "src")
@@ -613,7 +639,7 @@ async def build_image_from_project(
613
639
  if not builder:
614
640
  raise LaunchError("Unable to build image. No builder found.")
615
641
 
616
- launch_project = fetch_and_validate_project(launch_project, api)
642
+ launch_project.fetch_and_validate_project()
617
643
 
618
644
  entry_point: EntryPoint = launch_project.get_single_entry_point() or EntryPoint(
619
645
  name=EntrypointDefaults.PYTHON[-1],