Skip to content

Commit d484e9f

Browse files
authored
Auto activate uv venv with pip installed (#2666)
* Auto activate uv venv with pip installed * Fix tests * Bump base_image = "0.8" * Fix tests
1 parent f36ca9d commit d484e9f

File tree

10 files changed

+48
-13
lines changed

10 files changed

+48
-13
lines changed

src/dstack/_internal/core/backends/base/compute.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
DSTACK_RUNNER_SSH_PORT,
2020
DSTACK_SHIM_HTTP_PORT,
2121
)
22+
from dstack._internal.core.models.configurations import DEFAULT_REPO_DIR
2223
from dstack._internal.core.models.gateways import (
2324
GatewayComputeConfiguration,
2425
GatewayProvisioningData,
@@ -754,7 +755,7 @@ def get_docker_commands(
754755
f" --ssh-port {DSTACK_RUNNER_SSH_PORT}"
755756
" --temp-dir /tmp/runner"
756757
" --home-dir /root"
757-
" --working-dir /workflow"
758+
f" --working-dir {DEFAULT_REPO_DIR}"
758759
),
759760
]
760761

src/dstack/_internal/core/models/configurations.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
RUN_PRIOTIRY_MIN = 0
2727
RUN_PRIOTIRY_MAX = 100
2828
RUN_PRIORITY_DEFAULT = 0
29+
DEFAULT_REPO_DIR = "/workflow"
2930

3031

3132
class RunConfigurationType(str, Enum):
@@ -181,7 +182,7 @@ class BaseRunConfiguration(CoreModel):
181182
Field(
182183
description=(
183184
"The path to the working directory inside the container."
184-
" It's specified relative to the repository directory (`/workflow`) and should be inside it."
185+
f" It's specified relative to the repository directory (`{DEFAULT_REPO_DIR}`) and should be inside it."
185186
' Defaults to `"."` '
186187
)
187188
),

src/dstack/_internal/core/models/runs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from dstack._internal.core.models.backends.base import BackendType
99
from dstack._internal.core.models.common import ApplyAction, CoreModel, NetworkMode, RegistryAuth
1010
from dstack._internal.core.models.configurations import (
11+
DEFAULT_REPO_DIR,
1112
AnyRunConfiguration,
1213
RunConfiguration,
1314
)
@@ -338,7 +339,7 @@ class RunSpec(CoreModel):
338339
Field(
339340
description=(
340341
"The path to the working directory inside the container."
341-
" It's specified relative to the repository directory (`/workflow`) and should be inside it."
342+
f" It's specified relative to the repository directory (`{DEFAULT_REPO_DIR}`) and should be inside it."
342343
' Defaults to `"."`.'
343344
)
344345
),

src/dstack/_internal/server/services/jobs/configurators/base.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from dstack._internal.core.errors import DockerRegistryError, ServerClientError
1111
from dstack._internal.core.models.common import RegistryAuth
1212
from dstack._internal.core.models.configurations import (
13+
DEFAULT_REPO_DIR,
1314
PortMapping,
1415
PythonVersion,
1516
RunConfigurationType,
@@ -149,7 +150,8 @@ async def _commands(self) -> List[str]:
149150
commands = self.run_spec.configuration.commands
150151
elif shell_commands := self._shell_commands():
151152
entrypoint = [self._shell(), "-i", "-c"]
152-
commands = [_join_shell_commands(shell_commands)]
153+
dstack_image_commands = self._dstack_image_commands()
154+
commands = [_join_shell_commands(dstack_image_commands + shell_commands)]
153155
else: # custom docker image without commands
154156
image_config = await self._get_image_config()
155157
entrypoint = image_config.entrypoint or []
@@ -164,6 +166,18 @@ async def _commands(self) -> List[str]:
164166

165167
return result
166168

169+
def _dstack_image_commands(self) -> List[str]:
170+
if (
171+
self.run_spec.configuration.image is not None
172+
or self.run_spec.configuration.entrypoint is not None
173+
):
174+
return []
175+
return [
176+
f"uv venv --prompt workflow --seed {DEFAULT_REPO_DIR}/.venv > /dev/null 2>&1",
177+
f"echo 'source {DEFAULT_REPO_DIR}/.venv/bin/activate' >> ~/.bashrc",
178+
f"source {DEFAULT_REPO_DIR}/.venv/bin/activate",
179+
]
180+
167181
def _app_specs(self) -> List[AppSpec]:
168182
specs = []
169183
for i, pm in enumerate(filter_reserved_ports(self._ports())):

src/dstack/_internal/server/services/jobs/configurators/extensions/cursor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import List
22

3+
from dstack._internal.core.models.configurations import DEFAULT_REPO_DIR
4+
35

46
class CursorDesktop:
57
def __init__(
@@ -37,6 +39,6 @@ def get_print_readme_commands(self) -> List[str]:
3739
return [
3840
"echo To open in Cursor, use link below:",
3941
"echo ''",
40-
f"echo ' cursor://vscode-remote/ssh-remote+{self.run_name}/workflow'", # TODO use $REPO_DIR
42+
f"echo ' cursor://vscode-remote/ssh-remote+{self.run_name}{DEFAULT_REPO_DIR}'", # TODO use $REPO_DIR
4143
"echo ''",
4244
]

src/dstack/_internal/server/services/jobs/configurators/extensions/vscode.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import List
22

3+
from dstack._internal.core.models.configurations import DEFAULT_REPO_DIR
4+
35

46
class VSCodeDesktop:
57
def __init__(
@@ -37,6 +39,6 @@ def get_print_readme_commands(self) -> List[str]:
3739
return [
3840
"echo To open in VS Code Desktop, use link below:",
3941
"echo ''",
40-
f"echo ' vscode://vscode-remote/ssh-remote+{self.run_name}/workflow'", # TODO use $REPO_DIR
42+
f"echo ' vscode://vscode-remote/ssh-remote+{self.run_name}{DEFAULT_REPO_DIR}'", # TODO use $REPO_DIR
4143
"echo ''",
4244
]

src/dstack/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__version__ = "0.0.0"
22
__is_release__ = False
3-
base_image = "0.7"
3+
base_image = "0.8"

src/tests/_internal/server/background/tasks/test_process_running_jobs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ async def test_provisioning_shim_with_volumes(
330330
name="test-run-0-0",
331331
registry_username="",
332332
registry_password="",
333-
image_name="dstackai/base:py3.13-0.7-cuda-12.1",
333+
image_name="dstackai/base:py3.13-0.8-cuda-12.1",
334334
container_user="root",
335335
privileged=privileged,
336336
gpu=None,

src/tests/_internal/server/routers/test_runs.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ def get_dev_env_run_plan_dict(
166166
"/bin/bash",
167167
"-i",
168168
"-c",
169-
"(echo pip install ipykernel... && "
169+
"uv venv --prompt workflow --seed /workflow/.venv > /dev/null 2>&1"
170+
" && echo 'source /workflow/.venv/bin/activate' >> ~/.bashrc"
171+
" && source /workflow/.venv/bin/activate"
172+
" && (echo pip install ipykernel... && "
170173
"pip install -q --no-cache-dir "
171174
'ipykernel 2> /dev/null) || echo "no '
172175
'pip, ipykernel was not installed" '
@@ -181,7 +184,7 @@ def get_dev_env_run_plan_dict(
181184
],
182185
"env": {},
183186
"home_dir": "/root",
184-
"image_name": "dstackai/base:py3.13-0.7-cuda-12.1",
187+
"image_name": "dstackai/base:py3.13-0.8-cuda-12.1",
185188
"user": None,
186189
"privileged": privileged,
187190
"job_name": f"{run_name}-0-0",
@@ -322,7 +325,10 @@ def get_dev_env_run_dict(
322325
"/bin/bash",
323326
"-i",
324327
"-c",
325-
"(echo pip install ipykernel... && "
328+
"uv venv --prompt workflow --seed /workflow/.venv > /dev/null 2>&1"
329+
" && echo 'source /workflow/.venv/bin/activate' >> ~/.bashrc"
330+
" && source /workflow/.venv/bin/activate"
331+
" && (echo pip install ipykernel... && "
326332
"pip install -q --no-cache-dir "
327333
'ipykernel 2> /dev/null) || echo "no '
328334
'pip, ipykernel was not installed" '
@@ -337,7 +343,7 @@ def get_dev_env_run_dict(
337343
],
338344
"env": {},
339345
"home_dir": "/root",
340-
"image_name": "dstackai/base:py3.13-0.7-cuda-12.1",
346+
"image_name": "dstackai/base:py3.13-0.8-cuda-12.1",
341347
"user": None,
342348
"privileged": privileged,
343349
"job_name": f"{run_name}-0-0",

src/tests/_internal/server/services/jobs/configurators/test_task.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,15 @@ async def test_with_commands_no_image(self, shell: Optional[str], expected_shell
9494

9595
job_specs = await configurator.get_job_specs(replica_num=0)
9696

97-
assert job_specs[0].commands == [expected_shell, "-i", "-c", "sleep inf"]
97+
assert job_specs[0].commands == [
98+
expected_shell,
99+
"-i",
100+
"-c",
101+
"uv venv --prompt workflow --seed /workflow/.venv > /dev/null 2>&1"
102+
" && echo 'source /workflow/.venv/bin/activate' >> ~/.bashrc"
103+
" && source /workflow/.venv/bin/activate"
104+
" && sleep inf",
105+
]
98106

99107
async def test_no_commands(self, image_config_mock: ImageConfig):
100108
image_config_mock.entrypoint = ["/entrypoint.sh"]

0 commit comments

Comments
 (0)