wandb 0.17.0rc2__py3-none-win32.whl → 0.17.2__py3-none-win32.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. wandb/__init__.py +4 -2
  2. wandb/apis/importers/internals/internal.py +0 -1
  3. wandb/apis/importers/wandb.py +12 -7
  4. wandb/apis/internal.py +0 -3
  5. wandb/apis/public/api.py +213 -79
  6. wandb/apis/public/artifacts.py +335 -100
  7. wandb/apis/public/files.py +9 -9
  8. wandb/apis/public/jobs.py +16 -4
  9. wandb/apis/public/projects.py +26 -28
  10. wandb/apis/public/query_generator.py +1 -1
  11. wandb/apis/public/runs.py +163 -65
  12. wandb/apis/public/sweeps.py +2 -2
  13. wandb/apis/reports/__init__.py +1 -7
  14. wandb/apis/reports/v1/__init__.py +5 -27
  15. wandb/apis/reports/v2/__init__.py +7 -19
  16. wandb/apis/workspaces/__init__.py +8 -0
  17. wandb/beta/workflows.py +8 -3
  18. wandb/bin/wandb-core +0 -0
  19. wandb/cli/cli.py +151 -59
  20. wandb/docker/__init__.py +1 -1
  21. wandb/errors/term.py +10 -2
  22. wandb/filesync/step_checksum.py +1 -4
  23. wandb/filesync/step_prepare.py +4 -24
  24. wandb/filesync/step_upload.py +5 -107
  25. wandb/filesync/upload_job.py +0 -76
  26. wandb/integration/gym/__init__.py +35 -15
  27. wandb/integration/openai/fine_tuning.py +21 -3
  28. wandb/integration/prodigy/prodigy.py +1 -1
  29. wandb/jupyter.py +16 -17
  30. wandb/old/summary.py +5 -0
  31. wandb/plot/pr_curve.py +2 -1
  32. wandb/plot/roc_curve.py +2 -1
  33. wandb/{plots → plot}/utils.py +13 -25
  34. wandb/proto/v3/wandb_internal_pb2.py +54 -54
  35. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  36. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  37. wandb/proto/v4/wandb_internal_pb2.py +54 -54
  38. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  39. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  40. wandb/proto/v5/wandb_base_pb2.py +30 -0
  41. wandb/proto/v5/wandb_internal_pb2.py +355 -0
  42. wandb/proto/v5/wandb_server_pb2.py +63 -0
  43. wandb/proto/v5/wandb_settings_pb2.py +45 -0
  44. wandb/proto/v5/wandb_telemetry_pb2.py +41 -0
  45. wandb/proto/wandb_base_pb2.py +2 -0
  46. wandb/proto/wandb_deprecated.py +9 -1
  47. wandb/proto/wandb_generate_deprecated.py +34 -0
  48. wandb/proto/{wandb_internal_codegen.py → wandb_generate_proto.py} +1 -35
  49. wandb/proto/wandb_internal_pb2.py +2 -0
  50. wandb/proto/wandb_server_pb2.py +2 -0
  51. wandb/proto/wandb_settings_pb2.py +2 -0
  52. wandb/proto/wandb_telemetry_pb2.py +2 -0
  53. wandb/sdk/artifacts/artifact.py +76 -23
  54. wandb/sdk/artifacts/artifact_manifest.py +1 -1
  55. wandb/sdk/artifacts/artifact_manifest_entry.py +6 -3
  56. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +1 -1
  57. wandb/sdk/artifacts/artifact_saver.py +1 -10
  58. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +6 -2
  59. wandb/sdk/artifacts/storage_handlers/multi_handler.py +1 -1
  60. wandb/sdk/artifacts/storage_handlers/tracking_handler.py +6 -4
  61. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +2 -42
  62. wandb/sdk/artifacts/storage_policy.py +1 -12
  63. wandb/sdk/data_types/_dtypes.py +5 -2
  64. wandb/sdk/data_types/html.py +1 -1
  65. wandb/sdk/data_types/image.py +1 -1
  66. wandb/sdk/data_types/object_3d.py +1 -1
  67. wandb/sdk/data_types/video.py +4 -2
  68. wandb/sdk/interface/interface.py +13 -0
  69. wandb/sdk/interface/interface_shared.py +1 -1
  70. wandb/sdk/internal/file_pusher.py +2 -5
  71. wandb/sdk/internal/file_stream.py +6 -19
  72. wandb/sdk/internal/internal_api.py +160 -138
  73. wandb/sdk/internal/job_builder.py +207 -135
  74. wandb/sdk/internal/progress.py +0 -28
  75. wandb/sdk/internal/sender.py +105 -42
  76. wandb/sdk/internal/settings_static.py +8 -1
  77. wandb/sdk/internal/system/assets/gpu.py +2 -0
  78. wandb/sdk/internal/system/assets/trainium.py +3 -3
  79. wandb/sdk/internal/system/system_info.py +4 -2
  80. wandb/sdk/internal/update.py +1 -1
  81. wandb/sdk/launch/__init__.py +9 -1
  82. wandb/sdk/launch/_launch.py +4 -24
  83. wandb/sdk/launch/_launch_add.py +1 -3
  84. wandb/sdk/launch/_project_spec.py +184 -224
  85. wandb/sdk/launch/agent/agent.py +58 -18
  86. wandb/sdk/launch/agent/config.py +0 -3
  87. wandb/sdk/launch/builder/abstract.py +67 -0
  88. wandb/sdk/launch/builder/build.py +165 -576
  89. wandb/sdk/launch/builder/context_manager.py +235 -0
  90. wandb/sdk/launch/builder/docker_builder.py +7 -23
  91. wandb/sdk/launch/builder/kaniko_builder.py +10 -23
  92. wandb/sdk/launch/builder/templates/dockerfile.py +92 -0
  93. wandb/sdk/launch/create_job.py +51 -45
  94. wandb/sdk/launch/environment/aws_environment.py +26 -1
  95. wandb/sdk/launch/inputs/files.py +148 -0
  96. wandb/sdk/launch/inputs/internal.py +224 -0
  97. wandb/sdk/launch/inputs/manage.py +95 -0
  98. wandb/sdk/launch/runner/abstract.py +2 -2
  99. wandb/sdk/launch/runner/kubernetes_monitor.py +45 -12
  100. wandb/sdk/launch/runner/kubernetes_runner.py +6 -8
  101. wandb/sdk/launch/runner/local_container.py +2 -3
  102. wandb/sdk/launch/runner/local_process.py +8 -29
  103. wandb/sdk/launch/runner/sagemaker_runner.py +20 -14
  104. wandb/sdk/launch/runner/vertex_runner.py +8 -7
  105. wandb/sdk/launch/sweeps/scheduler.py +2 -0
  106. wandb/sdk/launch/sweeps/utils.py +2 -2
  107. wandb/sdk/launch/utils.py +16 -138
  108. wandb/sdk/lib/_settings_toposort_generated.py +2 -5
  109. wandb/sdk/lib/apikey.py +4 -2
  110. wandb/sdk/lib/config_util.py +3 -3
  111. wandb/sdk/lib/proto_util.py +22 -1
  112. wandb/sdk/lib/redirect.py +1 -1
  113. wandb/sdk/service/service.py +2 -1
  114. wandb/sdk/service/streams.py +5 -5
  115. wandb/sdk/wandb_init.py +25 -59
  116. wandb/sdk/wandb_login.py +28 -25
  117. wandb/sdk/wandb_run.py +135 -70
  118. wandb/sdk/wandb_settings.py +33 -64
  119. wandb/sdk/wandb_watch.py +1 -1
  120. wandb/sklearn/plot/classifier.py +4 -6
  121. wandb/sync/sync.py +2 -2
  122. wandb/testing/relay.py +32 -17
  123. wandb/util.py +39 -37
  124. wandb/wandb_agent.py +3 -3
  125. wandb/wandb_controller.py +3 -2
  126. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/METADATA +7 -9
  127. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/RECORD +130 -152
  128. wandb/apis/reports/v1/_blocks.py +0 -1406
  129. wandb/apis/reports/v1/_helpers.py +0 -70
  130. wandb/apis/reports/v1/_panels.py +0 -1282
  131. wandb/apis/reports/v1/_templates.py +0 -478
  132. wandb/apis/reports/v1/blocks.py +0 -27
  133. wandb/apis/reports/v1/helpers.py +0 -2
  134. wandb/apis/reports/v1/mutations.py +0 -66
  135. wandb/apis/reports/v1/panels.py +0 -17
  136. wandb/apis/reports/v1/report.py +0 -268
  137. wandb/apis/reports/v1/runset.py +0 -144
  138. wandb/apis/reports/v1/templates.py +0 -7
  139. wandb/apis/reports/v1/util.py +0 -406
  140. wandb/apis/reports/v1/validators.py +0 -131
  141. wandb/apis/reports/v2/blocks.py +0 -25
  142. wandb/apis/reports/v2/expr_parsing.py +0 -257
  143. wandb/apis/reports/v2/gql.py +0 -68
  144. wandb/apis/reports/v2/interface.py +0 -1911
  145. wandb/apis/reports/v2/internal.py +0 -867
  146. wandb/apis/reports/v2/metrics.py +0 -6
  147. wandb/apis/reports/v2/panels.py +0 -15
  148. wandb/catboost/__init__.py +0 -9
  149. wandb/fastai/__init__.py +0 -9
  150. wandb/keras/__init__.py +0 -19
  151. wandb/lightgbm/__init__.py +0 -9
  152. wandb/plots/__init__.py +0 -6
  153. wandb/plots/explain_text.py +0 -36
  154. wandb/plots/heatmap.py +0 -81
  155. wandb/plots/named_entity.py +0 -43
  156. wandb/plots/part_of_speech.py +0 -50
  157. wandb/plots/plot_definitions.py +0 -768
  158. wandb/plots/precision_recall.py +0 -121
  159. wandb/plots/roc.py +0 -103
  160. wandb/sacred/__init__.py +0 -3
  161. wandb/xgboost/__init__.py +0 -9
  162. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/WHEEL +0 -0
  163. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/entry_points.txt +0 -0
  164. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/licenses/LICENSE +0 -0
@@ -4,18 +4,13 @@ import logging
4
4
  import os
5
5
  import pathlib
6
6
  import shlex
7
- import shutil
8
- import sys
9
- import tempfile
10
- from typing import Any, Dict, List, Optional, Tuple
7
+ from typing import Any, Dict, List, Tuple
11
8
 
12
- import yaml
13
9
  from dockerpycreds.utils import find_executable # type: ignore
14
- from six.moves import shlex_quote
15
10
 
16
11
  import wandb
17
- import wandb.docker as docker
18
12
  import wandb.env
13
+ from wandb import docker
19
14
  from wandb.apis.internal import Api
20
15
  from wandb.sdk.launch.loader import (
21
16
  builder_from_config,
@@ -24,18 +19,15 @@ from wandb.sdk.launch.loader import (
24
19
  )
25
20
  from wandb.util import get_module
26
21
 
27
- from .._project_spec import EntryPoint, EntrypointDefaults, LaunchProject
22
+ from .._project_spec import EntryPoint, LaunchProject
28
23
  from ..errors import ExecutionError, LaunchError
29
- from ..registry.abstract import AbstractRegistry
30
- from ..registry.anon import AnonynmousRegistry
31
- from ..utils import (
32
- AZURE_CONTAINER_REGISTRY_URI_REGEX,
33
- ELASTIC_CONTAINER_REGISTRY_URI_REGEX,
34
- GCP_ARTIFACT_REGISTRY_URI_REGEX,
35
- LAUNCH_CONFIG_FILE,
36
- LOG_PREFIX,
37
- event_loop_thread_exec,
38
- resolve_build_and_registry_config,
24
+ from ..utils import LOG_PREFIX, event_loop_thread_exec
25
+ from .templates.dockerfile import (
26
+ ACCELERATOR_SETUP_TEMPLATE,
27
+ ENTRYPOINT_TEMPLATE,
28
+ PIP_TEMPLATE,
29
+ PYTHON_SETUP_TEMPLATE,
30
+ USER_CREATE_TEMPLATE,
39
31
  )
40
32
 
41
33
  _logger = logging.getLogger(__name__)
@@ -44,76 +36,95 @@ _logger = logging.getLogger(__name__)
44
36
  _WANDB_DOCKERFILE_NAME = "Dockerfile.wandb"
45
37
 
46
38
 
47
- def registry_from_uri(uri: str) -> AbstractRegistry:
48
- """Create a registry helper object from a uri.
39
+ async def validate_docker_installation() -> None:
40
+ """Verify if Docker is installed on host machine."""
41
+ find_exec = event_loop_thread_exec(find_executable)
42
+ if not await find_exec("docker"):
43
+ raise ExecutionError(
44
+ "Could not find Docker executable. "
45
+ "Ensure Docker is installed as per the instructions "
46
+ "at https://docs.docker.com/install/overview/."
47
+ )
49
48
 
50
- This function parses the URI and determines which supported registry it
51
- belongs to. It then creates a registry helper object for that registry.
52
- The supported remote registry types are:
53
- - Azure Container Registry
54
- - Google Container Registry
55
- - AWS Elastic Container Registry
56
49
 
57
- The format of the URI is as follows:
58
- - Azure Container Registry: <registry-name>.azurecr.io/<repo-name>/<image-name>
59
- - Google Container Registry: <location>-docker.pkg.dev/<project-id>/<repo-name>/<image-name>
60
- - AWS Elastic Container Registry: <account-id>.dkr.ecr.<region>.amazonaws.com/<repo-name>/<image-name>
50
+ def join(split_command: List[str]) -> str:
51
+ """Return a shell-escaped string from *split_command*.
61
52
 
62
- Our classification of the registry is based on the domain name. For example,
63
- if the uri contains `.azurecr.io`, we classify it as an Azure
64
- Container Registry. If the uri contains `.dkr.ecr`, we classify
65
- it as an AWS Elastic Container Registry. If the uri contains
66
- `-docker.pkg.dev`, we classify it as a Google Artifact Registry.
53
+ Also remove quotes from double quoted strings. Ex:
54
+ "'local container queue'" --> "local container queue"
55
+ """
56
+ return " ".join(shlex.quote(arg.replace("'", "")) for arg in split_command)
67
57
 
68
- This function will attempt to load the appropriate cloud helpers for the
69
58
 
70
- `https://` prefix is optional for all of the above.
59
+ async def build_image_from_project(
60
+ launch_project: LaunchProject,
61
+ api: Api,
62
+ launch_config: Dict[str, Any],
63
+ ) -> str:
64
+ """Construct a docker image from a project and returns the URI of the image.
71
65
 
72
66
  Arguments:
73
- uri: The uri to create a registry from.
67
+ launch_project: The project to build an image from.
68
+ api: The API object to use for fetching the project.
69
+ launch_config: The launch config to use for building the image.
74
70
 
75
71
  Returns:
76
- The registry.
77
-
78
- Raises:
79
- LaunchError: If the registry helper cannot be loaded for the given URI.
72
+ The URI of the built image.
80
73
  """
81
- if uri.startswith("https://"):
82
- uri = uri[len("https://") :]
83
-
84
- if AZURE_CONTAINER_REGISTRY_URI_REGEX.match(uri) is not None:
85
- from wandb.sdk.launch.registry.azure_container_registry import (
86
- AzureContainerRegistry,
74
+ assert launch_project.uri, "To build an image on queue a URI must be set."
75
+ launch_config = launch_config or {}
76
+ env_config = launch_config.get("environment", {})
77
+ if not isinstance(env_config, dict):
78
+ wrong_type = type(env_config).__name__
79
+ raise LaunchError(
80
+ f"Invalid environment config: {env_config} of type {wrong_type} "
81
+ "loaded from launch config. Expected dict."
87
82
  )
83
+ environment = environment_from_config(env_config)
88
84
 
89
- return AzureContainerRegistry(uri=uri)
90
-
91
- elif GCP_ARTIFACT_REGISTRY_URI_REGEX.match(uri) is not None:
92
- from wandb.sdk.launch.registry.google_artifact_registry import (
93
- GoogleArtifactRegistry,
85
+ registry_config = launch_config.get("registry", {})
86
+ if not isinstance(registry_config, dict):
87
+ wrong_type = type(registry_config).__name__
88
+ raise LaunchError(
89
+ f"Invalid registry config: {registry_config} of type {wrong_type}"
90
+ " loaded from launch config. Expected dict."
94
91
  )
92
+ registry = registry_from_config(registry_config, environment)
95
93
 
96
- return GoogleArtifactRegistry(uri=uri)
97
-
98
- elif ELASTIC_CONTAINER_REGISTRY_URI_REGEX.match(uri) is not None:
99
- from wandb.sdk.launch.registry.elastic_container_registry import (
100
- ElasticContainerRegistry,
94
+ builder_config = launch_config.get("builder", {})
95
+ if not isinstance(builder_config, dict):
96
+ wrong_type = type(builder_config).__name__
97
+ raise LaunchError(
98
+ f"Invalid builder config: {builder_config} of type {wrong_type} "
99
+ "loaded from launch config. Expected dict."
101
100
  )
101
+ builder = builder_from_config(builder_config, environment, registry)
102
102
 
103
- return ElasticContainerRegistry(uri=uri)
103
+ if not builder:
104
+ raise LaunchError("Unable to build image. No builder found.")
105
+
106
+ launch_project.fetch_and_validate_project()
104
107
 
105
- return AnonynmousRegistry(uri=uri)
108
+ entry_point = (
109
+ launch_project.get_job_entry_point() or launch_project.override_entrypoint
110
+ )
111
+ assert entry_point is not None
112
+ wandb.termlog(f"{LOG_PREFIX}Building docker image from uri source")
113
+ image_uri = await builder.build_image(launch_project, entry_point)
114
+ if not image_uri:
115
+ raise LaunchError("Error building image uri")
116
+ else:
117
+ return image_uri
106
118
 
107
119
 
108
- async def validate_docker_installation() -> None:
109
- """Verify if Docker is installed on host machine."""
110
- find_exec = event_loop_thread_exec(find_executable)
111
- if not await find_exec("docker"):
112
- raise ExecutionError(
113
- "Could not find Docker executable. "
114
- "Ensure Docker is installed as per the instructions "
115
- "at https://docs.docker.com/install/overview/."
116
- )
120
+ def image_tag_from_dockerfile_and_source(
121
+ launch_project: LaunchProject, dockerfile_contents: str
122
+ ) -> str:
123
+ """Hashes the source and dockerfile contents into a unique tag."""
124
+ image_source_string = launch_project.get_image_source_string()
125
+ unique_id_string = image_source_string + dockerfile_contents
126
+ image_tag = hashlib.sha256(unique_id_string.encode("utf-8")).hexdigest()[:8]
127
+ return image_tag
117
128
 
118
129
 
119
130
  def get_docker_user(launch_project: LaunchProject, runner_type: str) -> Tuple[str, int]:
@@ -129,107 +140,6 @@ def get_docker_user(launch_project: LaunchProject, runner_type: str) -> Tuple[st
129
140
  return username, userid
130
141
 
131
142
 
132
- DOCKERFILE_TEMPLATE = """
133
- # ----- stage 1: build -----
134
- FROM {py_build_image} as build
135
-
136
- # requirements section depends on pip vs conda, and presence of buildx
137
- ENV PIP_PROGRESS_BAR off
138
- {requirements_section}
139
-
140
- # ----- stage 2: base -----
141
- {base_setup}
142
-
143
- COPY --from=build /env /env
144
- ENV PATH="/env/bin:$PATH"
145
-
146
- ENV SHELL /bin/bash
147
-
148
- # some resources (eg sagemaker) must run on root
149
- {user_setup}
150
-
151
- WORKDIR {workdir}
152
- RUN chown -R {uid} {workdir}
153
-
154
- # make artifacts cache dir unrelated to build
155
- RUN mkdir -p {workdir}/.cache && chown -R {uid} {workdir}/.cache
156
-
157
- # copy code/etc
158
- COPY --chown={uid} src/ {workdir}
159
-
160
- ENV PYTHONUNBUFFERED=1
161
-
162
- {entrypoint_section}
163
- """
164
-
165
- # this goes into base_setup in TEMPLATE
166
- PYTHON_SETUP_TEMPLATE = """
167
- FROM {py_base_image} as base
168
- """
169
-
170
- # this goes into base_setup in TEMPLATE
171
- ACCELERATOR_SETUP_TEMPLATE = """
172
- FROM {accelerator_base_image} as base
173
-
174
- # make non-interactive so build doesn't block on questions
175
- ENV DEBIAN_FRONTEND=noninteractive
176
-
177
- # install python
178
- RUN apt-get update -qq && apt-get install --no-install-recommends -y \
179
- {python_packages} \
180
- && apt-get -qq purge && apt-get -qq clean \
181
- && rm -rf /var/lib/apt/lists/*
182
-
183
- # make sure `python` points at the right version
184
- RUN update-alternatives --install /usr/bin/python python /usr/bin/python{py_version} 1 \
185
- && update-alternatives --install /usr/local/bin/python python /usr/bin/python{py_version} 1
186
- """
187
-
188
- # this goes into requirements_section in TEMPLATE
189
- PIP_TEMPLATE = """
190
- RUN python -m venv /env
191
- # make sure we install into the env
192
- ENV PATH="/env/bin:$PATH"
193
-
194
- COPY {requirements_files} ./
195
- {buildx_optional_prefix} {pip_install}
196
- """
197
-
198
- # this goes into requirements_section in TEMPLATE
199
- CONDA_TEMPLATE = """
200
- COPY src/environment.yml .
201
- {buildx_optional_prefix} conda env create -f environment.yml -n env
202
-
203
- # pack the environment so that we can transfer to the base image
204
- RUN conda install -c conda-forge conda-pack
205
- RUN conda pack -n env -o /tmp/env.tar && \
206
- mkdir /env && cd /env && tar xf /tmp/env.tar && \
207
- rm /tmp/env.tar
208
- RUN /env/bin/conda-unpack
209
- """
210
-
211
- USER_CREATE_TEMPLATE = """
212
- RUN useradd \
213
- --create-home \
214
- --no-log-init \
215
- --shell /bin/bash \
216
- --gid 0 \
217
- --uid {uid} \
218
- {user} || echo ""
219
- """
220
-
221
- ENTRYPOINT_TEMPLATE = """
222
- ENTRYPOINT {entrypoint}
223
- """
224
-
225
-
226
- def get_current_python_version() -> Tuple[str, str]:
227
- full_version = sys.version.split()[0].split(".")
228
- major = full_version[0]
229
- version = ".".join(full_version[:2]) if len(full_version) >= 2 else major + ".0"
230
- return version, major
231
-
232
-
233
143
  def get_base_setup(
234
144
  launch_project: LaunchProject, py_version: str, py_major: str
235
145
  ) -> str:
@@ -246,21 +156,12 @@ def get_base_setup(
246
156
  _logger.info(
247
157
  f"Using accelerator base image: {launch_project.accelerator_base_image}"
248
158
  )
249
- # accelerator base images doesn't come with python tooling
250
- if py_major == "2":
251
- python_packages = [
252
- f"python{py_version}",
253
- f"libpython{py_version}",
254
- "python-pip",
255
- "python-setuptools",
256
- ]
257
- else:
258
- python_packages = [
259
- f"python{py_version}",
260
- f"libpython{py_version}",
261
- "python3-pip",
262
- "python3-setuptools",
263
- ]
159
+ python_packages = [
160
+ f"python{py_version}",
161
+ f"libpython{py_version}",
162
+ "python3-pip",
163
+ "python3-setuptools",
164
+ ]
264
165
  base_setup = ACCELERATOR_SETUP_TEMPLATE.format(
265
166
  accelerator_base_image=launch_project.accelerator_base_image,
266
167
  python_packages=" \\\n".join(python_packages),
@@ -268,70 +169,17 @@ def get_base_setup(
268
169
  )
269
170
  else:
270
171
  python_packages = [
271
- "python3-dev" if py_major == "3" else "python-dev",
172
+ "python3-dev",
272
173
  "gcc",
273
174
  ] # gcc required for python < 3.7 for some reason
274
175
  base_setup = PYTHON_SETUP_TEMPLATE.format(py_base_image=python_base_image)
275
176
  return base_setup
276
177
 
277
178
 
278
- def get_env_vars_dict(
279
- launch_project: LaunchProject, api: Api, max_env_length: int
280
- ) -> Dict[str, str]:
281
- """Generate environment variables for the project.
282
-
283
- Arguments:
284
- launch_project: LaunchProject to generate environment variables for.
285
-
286
- Returns:
287
- Dictionary of environment variables.
288
- """
289
- env_vars = {}
290
- env_vars["WANDB_BASE_URL"] = api.settings("base_url")
291
- override_api_key = launch_project.launch_spec.get("_wandb_api_key")
292
- env_vars["WANDB_API_KEY"] = override_api_key or api.api_key
293
- if launch_project.target_project:
294
- env_vars["WANDB_PROJECT"] = launch_project.target_project
295
- env_vars["WANDB_ENTITY"] = launch_project.target_entity
296
- env_vars["WANDB_LAUNCH"] = "True"
297
- env_vars["WANDB_RUN_ID"] = launch_project.run_id
298
- if launch_project.docker_image:
299
- env_vars["WANDB_DOCKER"] = launch_project.docker_image
300
- if launch_project.name is not None:
301
- env_vars["WANDB_NAME"] = launch_project.name
302
- if "author" in launch_project.launch_spec and not override_api_key:
303
- env_vars["WANDB_USERNAME"] = launch_project.launch_spec["author"]
304
- if launch_project.sweep_id:
305
- env_vars["WANDB_SWEEP_ID"] = launch_project.sweep_id
306
- if launch_project.launch_spec.get("_resume_count", 0) > 0:
307
- env_vars["WANDB_RESUME"] = "allow"
308
- if launch_project.queue_name:
309
- env_vars[wandb.env.LAUNCH_QUEUE_NAME] = launch_project.queue_name
310
- if launch_project.queue_entity:
311
- env_vars[wandb.env.LAUNCH_QUEUE_ENTITY] = launch_project.queue_entity
312
- if launch_project.run_queue_item_id:
313
- env_vars[wandb.env.LAUNCH_TRACE_ID] = launch_project.run_queue_item_id
314
-
315
- _inject_wandb_config_env_vars(
316
- launch_project.override_config, env_vars, max_env_length
317
- )
318
-
319
- _inject_file_overrides_env_vars(
320
- launch_project.override_files, env_vars, max_env_length
321
- )
322
-
323
- artifacts = {}
324
- # if we're spinning up a launch process from a job
325
- # we should tell the run to use that artifact
326
- if launch_project.job:
327
- artifacts = {wandb.util.LAUNCH_JOB_ARTIFACT_SLOT_NAME: launch_project.job}
328
- env_vars["WANDB_ARTIFACTS"] = json.dumps(
329
- {**artifacts, **launch_project.override_artifacts}
330
- )
331
- return env_vars
332
-
333
-
334
- def get_requirements_section(launch_project: LaunchProject, builder_type: str) -> str:
179
+ # Move this into the build context manager.
180
+ def get_requirements_section(
181
+ launch_project: LaunchProject, build_context_dir: str, builder_type: str
182
+ ) -> str:
335
183
  if builder_type == "docker":
336
184
  buildx_installed = docker.is_buildx_installed()
337
185
  if not buildx_installed:
@@ -342,69 +190,94 @@ def get_requirements_section(launch_project: LaunchProject, builder_type: str) -
342
190
  elif builder_type == "kaniko":
343
191
  prefix = "RUN WANDB_DISABLE_CACHE=true"
344
192
  buildx_installed = False
345
- if launch_project.deps_type == "pip":
346
- requirements_files = []
347
- deps_install_line = None
348
- assert launch_project.project_dir is not None
349
- base_path = pathlib.Path(launch_project.project_dir)
350
- # If there is a requirements.txt at root of build context, use that.
351
- if (base_path / "requirements.txt").exists():
352
- requirements_files += ["src/requirements.txt"]
353
- deps_install_line = "pip install -r requirements.txt"
354
- # Elif there is pyproject.toml at build context, convert the dependencies
355
- # section to a requirements.txt and use that.
356
- elif (base_path / "pyproject.toml").exists():
357
- tomli = get_module("tomli")
358
- if tomli is None:
359
- wandb.termwarn(
360
- "pyproject.toml found but tomli could not be loaded. To "
361
- "install dependencies from pyproject.toml please run "
362
- "`pip install tomli` and try again."
363
- )
364
- else:
365
- # First try to read deps from standard pyproject format.
366
- with open(base_path / "pyproject.toml", "rb") as f:
367
- contents = tomli.load(f)
368
- project_deps = [
369
- str(d) for d in contents.get("project", {}).get("dependencies", [])
370
- ]
371
- if project_deps:
372
- with open(base_path / "requirements.txt", "w") as f:
373
- f.write("\n".join(project_deps))
374
- requirements_files += ["src/requirements.txt"]
375
- deps_install_line = "pip install -r requirements.txt"
376
- # Else use frozen requirements from wandb run.
377
- if not deps_install_line and (base_path / "requirements.frozen.txt").exists():
378
- requirements_files += [
379
- "src/requirements.frozen.txt",
380
- "_wandb_bootstrap.py",
381
- ]
382
- deps_install_line = (
383
- _parse_existing_requirements(launch_project)
384
- + "python _wandb_bootstrap.py"
193
+
194
+ if buildx_installed:
195
+ prefix = "RUN --mount=type=cache,mode=0777,target=/root/.cache/pip"
196
+
197
+ requirements_files = []
198
+ deps_install_line = None
199
+
200
+ base_path = pathlib.Path(build_context_dir)
201
+ # If there is a requirements.txt at root of build context, use that.
202
+ if (base_path / "src" / "requirements.txt").exists():
203
+ requirements_files += ["src/requirements.txt"]
204
+ deps_install_line = "pip install -r requirements.txt"
205
+ with open(base_path / "src" / "requirements.txt") as f:
206
+ requirements = f.readlines()
207
+ if not any(["wandb" in r for r in requirements]):
208
+ wandb.termwarn(f"{LOG_PREFIX}wandb is not present in requirements.txt.")
209
+ return PIP_TEMPLATE.format(
210
+ buildx_optional_prefix=prefix,
211
+ requirements_files=" ".join(requirements_files),
212
+ pip_install=deps_install_line,
213
+ )
214
+
215
+ # Elif there is pyproject.toml at build context, convert the dependencies
216
+ # section to a requirements.txt and use that.
217
+ elif (base_path / "src" / "pyproject.toml").exists():
218
+ tomli = get_module("tomli")
219
+ if tomli is None:
220
+ wandb.termwarn(
221
+ "pyproject.toml found but tomli could not be loaded. To "
222
+ "install dependencies from pyproject.toml please run "
223
+ "`pip install tomli` and try again."
385
224
  )
225
+ else:
226
+ # First try to read deps from standard pyproject format.
227
+ with open(base_path / "src" / "pyproject.toml", "rb") as f:
228
+ contents = tomli.load(f)
229
+ project_deps = [
230
+ str(d) for d in contents.get("project", {}).get("dependencies", [])
231
+ ]
232
+ if project_deps:
233
+ if not any(["wandb" in d for d in project_deps]):
234
+ wandb.termwarn(
235
+ f"{LOG_PREFIX}wandb is not present as a dependency in pyproject.toml."
236
+ )
237
+ with open(base_path / "src" / "requirements.txt", "w") as f:
238
+ f.write("\n".join(project_deps))
239
+ requirements_files += ["src/requirements.txt"]
240
+ deps_install_line = "pip install -r requirements.txt"
241
+ return PIP_TEMPLATE.format(
242
+ buildx_optional_prefix=prefix,
243
+ requirements_files=" ".join(requirements_files),
244
+ pip_install=deps_install_line,
245
+ )
246
+
247
+ # Else use frozen requirements from wandb run.
248
+ if (
249
+ not deps_install_line
250
+ and (base_path / "src" / "requirements.frozen.txt").exists()
251
+ ):
252
+ requirements_files += [
253
+ "src/requirements.frozen.txt",
254
+ "_wandb_bootstrap.py",
255
+ ]
256
+ deps_install_line = (
257
+ launch_project.parse_existing_requirements() + "python _wandb_bootstrap.py"
258
+ )
386
259
 
387
260
  if not deps_install_line:
388
261
  raise LaunchError(f"No dependency sources found for {launch_project}")
389
262
 
390
- if buildx_installed:
391
- prefix = "RUN --mount=type=cache,mode=0777,target=/root/.cache/pip"
263
+ with open(base_path / "src" / "requirements.frozen.txt") as f:
264
+ requirements = f.readlines()
265
+ if not any(["wandb" in r for r in requirements]):
266
+ wandb.termwarn(
267
+ f"{LOG_PREFIX}wandb is not present in requirements.frozen.txt."
268
+ )
392
269
 
393
- requirements_line = PIP_TEMPLATE.format(
270
+ return PIP_TEMPLATE.format(
394
271
  buildx_optional_prefix=prefix,
395
272
  requirements_files=" ".join(requirements_files),
396
273
  pip_install=deps_install_line,
397
274
  )
398
- elif launch_project.deps_type == "conda":
399
- if buildx_installed:
400
- prefix = "RUN --mount=type=cache,mode=0777,target=/opt/conda/pkgs"
401
- requirements_line = CONDA_TEMPLATE.format(buildx_optional_prefix=prefix)
275
+
402
276
  else:
403
277
  # this means no deps file was found
404
278
  requirements_line = "RUN mkdir -p env/" # Docker fails otherwise
405
279
  wandb.termwarn("No requirements file found. No packages will be installed.")
406
-
407
- return requirements_line
280
+ return requirements_line
408
281
 
409
282
 
410
283
  def get_user_setup(username: str, userid: int, runner_type: str) -> str:
@@ -420,287 +293,3 @@ def get_entrypoint_setup(
420
293
  entry_point: EntryPoint,
421
294
  ) -> str:
422
295
  return ENTRYPOINT_TEMPLATE.format(entrypoint=json.dumps(entry_point.command))
423
-
424
-
425
- def generate_dockerfile(
426
- launch_project: LaunchProject,
427
- entry_point: EntryPoint,
428
- runner_type: str,
429
- builder_type: str,
430
- dockerfile: Optional[str] = None,
431
- ) -> str:
432
- if launch_project.project_dir is not None and dockerfile:
433
- path = os.path.join(launch_project.project_dir, dockerfile)
434
- if not os.path.exists(path):
435
- raise LaunchError(f"Dockerfile does not exist at {path}")
436
- launch_project.project_dir = os.path.dirname(path)
437
- wandb.termlog(f"Using dockerfile: {dockerfile}")
438
- return open(path).read()
439
-
440
- # get python versions truncated to major.minor to ensure image availability
441
- if launch_project.python_version:
442
- spl = launch_project.python_version.split(".")[:2]
443
- py_version, py_major = (".".join(spl), spl[0])
444
- else:
445
- py_version, py_major = get_current_python_version()
446
-
447
- # ----- stage 1: build -----
448
- if launch_project.deps_type == "pip" or launch_project.deps_type is None:
449
- python_build_image = (
450
- f"python:{py_version}" # use full python image for package installation
451
- )
452
- elif launch_project.deps_type == "conda":
453
- # neither of these images are receiving regular updates, latest should be pretty stable
454
- python_build_image = (
455
- "continuumio/miniconda3:latest"
456
- if py_major == "3"
457
- else "continuumio/miniconda:latest"
458
- )
459
- requirements_section = get_requirements_section(launch_project, builder_type)
460
- # ----- stage 2: base -----
461
- python_base_setup = get_base_setup(launch_project, py_version, py_major)
462
-
463
- # set up user info
464
- username, userid = get_docker_user(launch_project, runner_type)
465
- user_setup = get_user_setup(username, userid, runner_type)
466
- workdir = f"/home/{username}"
467
-
468
- entrypoint_section = get_entrypoint_setup(entry_point)
469
-
470
- dockerfile_contents = DOCKERFILE_TEMPLATE.format(
471
- py_build_image=python_build_image,
472
- requirements_section=requirements_section,
473
- base_setup=python_base_setup,
474
- uid=userid,
475
- user_setup=user_setup,
476
- workdir=workdir,
477
- entrypoint_section=entrypoint_section,
478
- )
479
- return dockerfile_contents
480
-
481
-
482
- def _parse_existing_requirements(launch_project: LaunchProject) -> str:
483
- import pkg_resources
484
-
485
- requirements_line = ""
486
- assert launch_project.project_dir is not None
487
- base_requirements = os.path.join(launch_project.project_dir, "requirements.txt")
488
- if os.path.exists(base_requirements):
489
- include_only = set()
490
- with open(base_requirements) as f:
491
- iter = pkg_resources.parse_requirements(f)
492
- while True:
493
- try:
494
- pkg = next(iter)
495
- if hasattr(pkg, "name"):
496
- name = pkg.name.lower()
497
- else:
498
- name = str(pkg)
499
- include_only.add(shlex_quote(name))
500
- except StopIteration:
501
- break
502
- # Different versions of pkg_resources throw different errors
503
- # just catch them all and ignore packages we can't parse
504
- except Exception as e:
505
- _logger.warn(f"Unable to parse requirements.txt: {e}")
506
- continue
507
- requirements_line += "WANDB_ONLY_INCLUDE={} ".format(",".join(include_only))
508
- return requirements_line
509
-
510
-
511
- def _create_docker_build_ctx(
512
- launch_project: LaunchProject,
513
- dockerfile_contents: str,
514
- ) -> str:
515
- """Create a build context temp dir for a Dockerfile and project code."""
516
- assert launch_project.project_dir is not None
517
- directory = tempfile.mkdtemp()
518
- entrypoint = launch_project.get_single_entry_point()
519
- if entrypoint is not None:
520
- assert entrypoint.name is not None
521
- entrypoint_dir = os.path.dirname(entrypoint.name)
522
- if entrypoint_dir:
523
- path = os.path.join(
524
- launch_project.project_dir, entrypoint_dir, _WANDB_DOCKERFILE_NAME
525
- )
526
- else:
527
- path = os.path.join(launch_project.project_dir, _WANDB_DOCKERFILE_NAME)
528
- if os.path.exists(
529
- path
530
- ): # We found a Dockerfile.wandb adjacent to the entrypoint.
531
- shutil.copytree(
532
- os.path.dirname(path),
533
- directory,
534
- symlinks=True,
535
- dirs_exist_ok=True,
536
- ignore=shutil.ignore_patterns("fsmonitor--daemon.ipc"),
537
- )
538
- # TODO: remove this once we make things more explicit for users
539
- if entrypoint_dir:
540
- new_path = os.path.basename(entrypoint.name)
541
- entrypoint = launch_project.get_single_entry_point()
542
- if entrypoint is not None:
543
- entrypoint.update_entrypoint_path(new_path)
544
- return directory
545
-
546
- dst_path = os.path.join(directory, "src")
547
- assert launch_project.project_dir is not None
548
- shutil.copytree(
549
- src=launch_project.project_dir,
550
- dst=dst_path,
551
- symlinks=True,
552
- ignore=shutil.ignore_patterns("fsmonitor--daemon.ipc"),
553
- )
554
- shutil.copy(
555
- os.path.join(os.path.dirname(__file__), "templates", "_wandb_bootstrap.py"),
556
- os.path.join(directory),
557
- )
558
- if launch_project.python_version:
559
- runtime_path = os.path.join(dst_path, "runtime.txt")
560
- with open(runtime_path, "w") as fp:
561
- fp.write(f"python-{launch_project.python_version}")
562
- # TODO: we likely don't need to pass the whole git repo into the container
563
- # with open(os.path.join(directory, ".dockerignore"), "w") as f:
564
- # f.write("**/.git")
565
- with open(os.path.join(directory, _WANDB_DOCKERFILE_NAME), "w") as handle:
566
- handle.write(dockerfile_contents)
567
- return directory
568
-
569
-
570
- def join(split_command: List[str]) -> str:
571
- """Return a shell-escaped string from *split_command*.
572
-
573
- Also remove quotes from double quoted strings. Ex:
574
- "'local container queue'" --> "local container queue"
575
- """
576
- return " ".join(shlex.quote(arg.replace("'", "")) for arg in split_command)
577
-
578
-
579
- def construct_agent_configs(
580
- launch_config: Optional[Dict] = None,
581
- build_config: Optional[Dict] = None,
582
- ) -> Tuple[Optional[Dict[str, Any]], Dict[str, Any], Dict[str, Any]]:
583
- registry_config = None
584
- environment_config = None
585
- if launch_config is not None:
586
- build_config = launch_config.get("builder")
587
- registry_config = launch_config.get("registry")
588
-
589
- default_launch_config = None
590
- if os.path.exists(os.path.expanduser(LAUNCH_CONFIG_FILE)):
591
- with open(os.path.expanduser(LAUNCH_CONFIG_FILE)) as f:
592
- default_launch_config = (
593
- yaml.safe_load(f) or {}
594
- ) # In case the config is empty, we want it to be {} instead of None.
595
- environment_config = default_launch_config.get("environment")
596
-
597
- build_config, registry_config = resolve_build_and_registry_config(
598
- default_launch_config, build_config, registry_config
599
- )
600
-
601
- return environment_config, build_config, registry_config
602
-
603
-
604
- async def build_image_from_project(
605
- launch_project: LaunchProject,
606
- api: Api,
607
- launch_config: Dict[str, Any],
608
- ) -> str:
609
- """Construct a docker image from a project and returns the URI of the image.
610
-
611
- Arguments:
612
- launch_project: The project to build an image from.
613
- api: The API object to use for fetching the project.
614
- launch_config: The launch config to use for building the image.
615
-
616
- Returns:
617
- The URI of the built image.
618
- """
619
- assert launch_project.uri, "To build an image on queue a URI must be set."
620
- launch_config = launch_config or {}
621
- env_config = launch_config.get("environment", {})
622
- if not isinstance(env_config, dict):
623
- wrong_type = type(env_config).__name__
624
- raise LaunchError(
625
- f"Invalid environment config: {env_config} of type {wrong_type} "
626
- "loaded from launch config. Expected dict."
627
- )
628
- environment = environment_from_config(env_config)
629
-
630
- registry_config = launch_config.get("registry", {})
631
- if not isinstance(registry_config, dict):
632
- wrong_type = type(registry_config).__name__
633
- raise LaunchError(
634
- f"Invalid registry config: {registry_config} of type {wrong_type}"
635
- " loaded from launch config. Expected dict."
636
- )
637
- registry = registry_from_config(registry_config, environment)
638
-
639
- builder_config = launch_config.get("builder", {})
640
- if not isinstance(builder_config, dict):
641
- wrong_type = type(builder_config).__name__
642
- raise LaunchError(
643
- f"Invalid builder config: {builder_config} of type {wrong_type} "
644
- "loaded from launch config. Expected dict."
645
- )
646
- builder = builder_from_config(builder_config, environment, registry)
647
-
648
- if not builder:
649
- raise LaunchError("Unable to build image. No builder found.")
650
-
651
- launch_project.fetch_and_validate_project()
652
-
653
- entry_point: EntryPoint = launch_project.get_single_entry_point() or EntryPoint(
654
- name=EntrypointDefaults.PYTHON[-1],
655
- command=EntrypointDefaults.PYTHON,
656
- )
657
- wandb.termlog(f"{LOG_PREFIX}Building docker image from uri source")
658
- image_uri = await builder.build_image(launch_project, entry_point)
659
- if not image_uri:
660
- raise LaunchError("Error building image uri")
661
- else:
662
- return image_uri
663
-
664
-
665
- def image_tag_from_dockerfile_and_source(
666
- launch_project: LaunchProject, dockerfile_contents: str
667
- ) -> str:
668
- """Hashes the source and dockerfile contents into a unique tag."""
669
- image_source_string = launch_project.get_image_source_string()
670
- unique_id_string = image_source_string + dockerfile_contents
671
- image_tag = hashlib.sha256(unique_id_string.encode("utf-8")).hexdigest()[:8]
672
- return image_tag
673
-
674
-
675
- def _inject_wandb_config_env_vars(
676
- config: Dict[str, Any], env_dict: Dict[str, Any], maximum_env_length: int
677
- ) -> None:
678
- str_config = json.dumps(config)
679
- if len(str_config) <= maximum_env_length:
680
- env_dict["WANDB_CONFIG"] = str_config
681
- return
682
-
683
- chunks = [
684
- str_config[i : i + maximum_env_length]
685
- for i in range(0, len(str_config), maximum_env_length)
686
- ]
687
- config_chunks_dict = {f"WANDB_CONFIG_{i}": chunk for i, chunk in enumerate(chunks)}
688
- env_dict.update(config_chunks_dict)
689
-
690
-
691
- def _inject_file_overrides_env_vars(
692
- overrides: Dict[str, Any], env_dict: Dict[str, Any], maximum_env_length: int
693
- ) -> None:
694
- str_overrides = json.dumps(overrides)
695
- if len(str_overrides) <= maximum_env_length:
696
- env_dict["WANDB_LAUNCH_FILE_OVERRIDES"] = str_overrides
697
- return
698
-
699
- chunks = [
700
- str_overrides[i : i + maximum_env_length]
701
- for i in range(0, len(str_overrides), maximum_env_length)
702
- ]
703
- overrides_chunks_dict = {
704
- f"WANDB_LAUNCH_FILE_OVERRIDES_{i}": chunk for i, chunk in enumerate(chunks)
705
- }
706
- env_dict.update(overrides_chunks_dict)