From a756452587d0b0f92132fa7bc0502bb5085ad360 Mon Sep 17 00:00:00 2001 From: Cynthia Lo Date: Mon, 22 Jun 2026 08:47:36 -0700 Subject: [PATCH 1/3] Fix command prefix bug --- src/submit_aml/aml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/submit_aml/aml.py b/src/submit_aml/aml.py index c269bab..7a15d9d 100644 --- a/src/submit_aml/aml.py +++ b/src/submit_aml/aml.py @@ -487,7 +487,7 @@ def submit_to_aml( return None # Build command that will be run - if project_dir != source_dir: + if command_prefix.startswith("uv run") and project_dir != source_dir: relative_project_dir = project_dir.relative_to(source_dir) command_prefix += f" --project {relative_project_dir}" From 7ab74b3ea25b75eb53c1e246881f4e63f6097bcc Mon Sep 17 00:00:00 2001 From: Cynthia Lo Date: Mon, 22 Jun 2026 08:51:30 -0700 Subject: [PATCH 2/3] Simplify the command prefix if condition --- src/submit_aml/aml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/submit_aml/aml.py b/src/submit_aml/aml.py index 7a15d9d..3147695 100644 --- a/src/submit_aml/aml.py +++ b/src/submit_aml/aml.py @@ -487,7 +487,7 @@ def submit_to_aml( return None # Build command that will be run - if command_prefix.startswith("uv run") and project_dir != source_dir: + if command_prefix.startswith("uv") and project_dir != source_dir: relative_project_dir = project_dir.relative_to(source_dir) command_prefix += f" --project {relative_project_dir}" From 675e03881d4949fd59e1af0216703e8d2f7e089e Mon Sep 17 00:00:00 2001 From: Cynthia Lo Date: Wed, 24 Jun 2026 01:37:46 -0700 Subject: [PATCH 3/3] Narrow uv check --- src/submit_aml/aml.py | 10 ++++- tests/test_aml.py | 89 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/submit_aml/aml.py b/src/submit_aml/aml.py index 3147695..6f43887 100644 --- a/src/submit_aml/aml.py +++ b/src/submit_aml/aml.py @@ -487,8 +487,14 @@ def submit_to_aml( return None # Build command that will be run - if command_prefix.startswith("uv") and project_dir != source_dir: - relative_project_dir = project_dir.relative_to(source_dir) + if command_prefix.startswith("uv run") and project_dir != source_dir: + try: + relative_project_dir = project_dir.relative_to(source_dir) + except ValueError as exc: + raise ValueError( + f"The project directory '{project_dir}' must be inside the source" + f" directory '{source_dir}' to append the uv --project flag." + ) from exc command_prefix += f" --project {relative_project_dir}" if services is None: diff --git a/tests/test_aml.py b/tests/test_aml.py index 3685780..9847fa2 100644 --- a/tests/test_aml.py +++ b/tests/test_aml.py @@ -2,6 +2,7 @@ from __future__ import annotations +from pathlib import Path from unittest.mock import patch import pytest @@ -9,6 +10,7 @@ from submit_aml.aml import CredentialType from submit_aml.aml import _sanitize_experiment_name from submit_aml.aml import get_client +from submit_aml.aml import submit_to_aml def test_sanitize_none_returns_none() -> None: @@ -56,3 +58,90 @@ def test_get_client_msi_uses_managed_identity( """CredentialType.MANAGED_IDENTITY uses ManagedIdentityCredential.""" get_client("sub", "rg", "ws", credential_type=CredentialType.MANAGED_IDENTITY) mock_msi_cred.assert_called_once() # type: ignore[union-attr] + + +@pytest.mark.parametrize( + ("command_prefix", "expected_in_command"), + [ + ("uv run", "--project subproject"), + ("python", None), + ], + ids=["uv-run-appends-project", "non-uv-skips-project"], +) +@patch("submit_aml.aml._submit") +@patch("submit_aml.aml.instantiate_command") +@patch("submit_aml.aml.infer_environment") +@patch("submit_aml.aml.setup") +def test_project_flag_only_appended_for_uv_run( + mock_setup: object, + mock_infer_env: object, + mock_instantiate: object, + mock_submit: object, + command_prefix: str, + expected_in_command: str | None, +) -> None: + """``--project`` is appended only for ``uv run`` prefixes.""" + source_dir = Path("/repo") + project_dir = source_dir / "subproject" + mock_setup.return_value = ( # type: ignore[attr-defined] + source_dir, + project_dir, + "run.py", + object(), # ml_client + "description", + 1, # instance_count + None, # distribution + "experiment", + ) + + submit_to_aml( + command_prefix=command_prefix, + compute_target="cpu-cluster", + script_path="run.py", + subscription_id="sub", + resource_group="rg", + workspace_name="ws", + dry_run=True, + ) + + command = mock_instantiate.call_args.kwargs["command"] # type: ignore[attr-defined] + if expected_in_command is None: + assert "--project" not in command + else: + assert expected_in_command in command + + +@patch("submit_aml.aml._submit") +@patch("submit_aml.aml.instantiate_command") +@patch("submit_aml.aml.infer_environment") +@patch("submit_aml.aml.setup") +def test_uv_run_prefix_raises_when_project_not_under_source( + mock_setup: object, + mock_infer_env: object, + mock_instantiate: object, + mock_submit: object, +) -> None: + """A clear ValueError is raised when project_dir is not inside source_dir.""" + source_dir = Path("/repo") + project_dir = Path("/other/project") + mock_setup.return_value = ( # type: ignore[attr-defined] + source_dir, + project_dir, + "run.py", + object(), # ml_client + "description", + 1, # instance_count + None, # distribution + "experiment", + ) + + with pytest.raises(ValueError, match="must be inside the source directory"): + submit_to_aml( + command_prefix="uv run", + compute_target="cpu-cluster", + script_path="run.py", + subscription_id="sub", + resource_group="rg", + workspace_name="ws", + dry_run=True, + )