Skip to content

Commit 062df22

Browse files
authored
Merge pull request #2265 from transformerlab/add/task-queue-provider-flag
Add --provider flag to lab task queue
2 parents da7c4ac + f42d18c commit 062df22

4 files changed

Lines changed: 66 additions & 3 deletions

File tree

cli/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "transformerlab-cli"
7-
version = "0.0.64"
7+
version = "0.0.65"
88
description = "Transformer Lab CLI"
99
requires-python = ">=3.10"
1010
authors = [{ name = "Transformer Lab", email = "hello@transformerlab.ai" }]

cli/src/transformerlab_cli/commands/task.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,20 @@ def _prompt_provider(providers: list[dict]) -> dict:
10451045
console.print("[error]Please enter a valid number[/error]")
10461046

10471047

1048+
def _resolve_provider(providers: list[dict], identifier: str) -> dict:
1049+
"""Resolve a provider by name (case-insensitive) or exact id.
1050+
1051+
Raises typer.BadParameter listing available providers if no match.
1052+
"""
1053+
match = next((p for p in providers if str(p.get("name", "")).lower() == identifier.lower()), None)
1054+
if match is None:
1055+
match = next((p for p in providers if str(p.get("id", "")) == identifier), None)
1056+
if match is None:
1057+
available = ", ".join(p.get("name", p.get("id", "?")) for p in providers) or "(none)"
1058+
raise typer.BadParameter(f"No provider matching {identifier!r}. Available providers: {available}")
1059+
return match
1060+
1061+
10481062
def _prompt_parameters(parameters: dict) -> dict:
10491063
"""Prompt user for each parameter value, showing defaults."""
10501064
if not parameters:
@@ -1112,6 +1126,7 @@ def queue_task(
11121126
enable_profiling_torch: bool = False,
11131127
use_spot: bool = False,
11141128
image: str | None = None,
1129+
provider_name: str | None = None,
11151130
) -> None:
11161131
"""Queue a task on a compute provider."""
11171132
with console.status("[bold success]Fetching task...[/bold success]", spinner="dots"):
@@ -1137,7 +1152,10 @@ def queue_task(
11371152
console.print("[error]Error:[/error] No compute providers available. Add one in team settings first.")
11381153
raise typer.Exit(1)
11391154

1140-
if interactive:
1155+
if provider_name:
1156+
provider = _resolve_provider(providers, provider_name)
1157+
console.print(f"[dim]Using provider: {provider.get('name')}[/dim]")
1158+
elif interactive:
11411159
provider = _prompt_provider(providers)
11421160
else:
11431161
task_provider_id = task.get("provider_id")
@@ -1199,6 +1217,14 @@ def queue_task(
11991217
def command_task_queue(
12001218
task_id: str = typer.Argument(..., help="Task ID to queue"),
12011219
no_interactive: bool = typer.Option(False, "--no-interactive", help="Skip interactive prompts, use defaults"),
1220+
provider: str | None = typer.Option(
1221+
None,
1222+
"--provider",
1223+
help=(
1224+
"Provider name (as shown in `lab provider list`) or id to run on. Overrides the task's "
1225+
"compute_provider and skips the interactive provider prompt. Errors if no provider matches."
1226+
),
1227+
),
12021228
description: str | None = typer.Option(
12031229
None,
12041230
"--description",
@@ -1265,6 +1291,7 @@ def command_task_queue(
12651291
enable_profiling_torch=enable_profiling_torch,
12661292
use_spot=spot,
12671293
image=image,
1294+
provider_name=provider,
12681295
)
12691296

12701297

cli/tests/commands/test_task.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,42 @@ def test_task_queue_no_interactive_picks_is_default_provider(_mock_exp, _mock_ge
231231
assert "p_default" in path, f"expected launch on default provider, got path: {path}"
232232

233233

234+
@patch("transformerlab_cli.commands.task.api.post_json", return_value=_mock_resp({"job_id": "j1"}))
235+
@patch("transformerlab_cli.commands.task.fetch_providers", return_value=SAMPLE_PROVIDERS_MULTI)
236+
@patch("transformerlab_cli.commands.task.api.get", return_value=_mock_resp(SAMPLE_TASK))
237+
@patch("transformerlab_cli.util.config.require_current_experiment", return_value="exp1")
238+
def test_task_queue_provider_flag_selects_by_name(_mock_exp, _mock_get, _mock_providers, mock_post):
239+
"""`--provider <name>` (case-insensitive) overrides the default and launches on that provider."""
240+
result = runner.invoke(app, ["task", "queue", "t1", "--no-interactive", "--provider", "runpod", "-m", "x"])
241+
assert result.exit_code == 0, result.output
242+
path, body = mock_post.call_args.args
243+
assert "p_other" in path, f"expected launch on named provider, got path: {path}"
244+
assert body["provider_name"] == "Runpod"
245+
246+
247+
@patch("transformerlab_cli.commands.task.api.post_json", return_value=_mock_resp({"job_id": "j1"}))
248+
@patch("transformerlab_cli.commands.task.fetch_providers", return_value=SAMPLE_PROVIDERS_MULTI)
249+
@patch("transformerlab_cli.commands.task.api.get", return_value=_mock_resp(SAMPLE_TASK))
250+
@patch("transformerlab_cli.util.config.require_current_experiment", return_value="exp1")
251+
def test_task_queue_provider_flag_skips_prompt_in_interactive(_mock_exp, _mock_get, _mock_providers, mock_post):
252+
"""`--provider` resolves without prompting even when interactive (no --no-interactive)."""
253+
result = runner.invoke(app, ["task", "queue", "t1", "--provider", "p_other", "-m", "x"], input="y\n")
254+
assert result.exit_code == 0, result.output
255+
path, _body = mock_post.call_args.args
256+
assert "p_other" in path, f"expected launch on named provider, got path: {path}"
257+
258+
259+
@patch("transformerlab_cli.commands.task.api.post_json", return_value=_mock_resp({"job_id": "j1"}))
260+
@patch("transformerlab_cli.commands.task.fetch_providers", return_value=SAMPLE_PROVIDERS_MULTI)
261+
@patch("transformerlab_cli.commands.task.api.get", return_value=_mock_resp(SAMPLE_TASK))
262+
@patch("transformerlab_cli.util.config.require_current_experiment", return_value="exp1")
263+
def test_task_queue_provider_flag_unknown_errors(_mock_exp, _mock_get, _mock_providers, mock_post):
264+
"""`--provider <unknown>` exits non-zero and does not launch anything."""
265+
result = runner.invoke(app, ["task", "queue", "t1", "--no-interactive", "--provider", "nope", "-m", "x"])
266+
assert result.exit_code != 0
267+
mock_post.assert_not_called()
268+
269+
234270
SAMPLE_TASK_WITH_PARAMS = {
235271
"id": "t1",
236272
"name": "finetune",

cli/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)