Skip to content

Commit e53501e

Browse files
authored
Merge pull request #1854 from transformerlab/fix/cli-job-artifacts-experiment-prefix
Fix lab job artifacts/download CLI commands
2 parents 53c16c9 + f9d8306 commit e53501e

6 files changed

Lines changed: 32 additions & 23 deletions

File tree

api/api.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555

5656

5757
from transformerlab.routers.experiment import experiment # noqa: E402
58-
from transformerlab.routers.experiment import jobs # noqa: E402
5958
from transformerlab.shared import shared # noqa: E402
6059
from transformerlab.shared import dirs # noqa: E402
6160
from lab.dirs import set_organization_id as lab_set_org_id # noqa: E402
@@ -306,7 +305,6 @@ async def validation_exception_handler(request, exc):
306305
app.include_router(serverinfo.router, dependencies=[Depends(get_user_and_team)])
307306
app.include_router(data.router, dependencies=[Depends(get_user_and_team)])
308307
app.include_router(experiment.router, dependencies=[Depends(get_user_and_team)])
309-
app.include_router(jobs.router, dependencies=[Depends(get_user_and_team)])
310308
app.include_router(config.router, dependencies=[Depends(get_user_and_team)])
311309
app.include_router(teams.router, dependencies=[Depends(get_user_and_team)])
312310
app.include_router(compute_provider.router)

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.20"
7+
version = "0.0.21"
88
description = "Transformer Lab CLI"
99
requires-python = ">=3.10"
1010
authors = [{ name = "Transformer Lab", email = "hello@transformerlab.ai" }]

cli/src/transformerlab_cli/commands/job.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -371,13 +371,14 @@ def info_job(job_id: str, experiment_id: str):
371371
console.print(f"[error]Error:[/error] Job with ID {job_id} not found.")
372372

373373

374-
def list_artifacts(job_id: str, output_format: str = "pretty") -> list[dict]:
374+
def list_artifacts(job_id: str, experiment_id: str, output_format: str = "pretty") -> list[dict]:
375375
"""List artifacts for a job by ID. Returns list of artifact dicts."""
376+
artifacts_url = f"/experiment/{experiment_id}/jobs/{job_id}/artifacts"
376377
if output_format != "json":
377378
with console.status(f"[bold success]Fetching artifacts for job {job_id}...[/bold success]", spinner="dots"):
378-
response = api.get(f"/jobs/{job_id}/artifacts")
379+
response = api.get(artifacts_url)
379380
else:
380-
response = api.get(f"/jobs/{job_id}/artifacts")
381+
response = api.get(artifacts_url)
381382

382383
if response.status_code != 200:
383384
if output_format == "json":
@@ -410,7 +411,7 @@ def list_artifacts(job_id: str, output_format: str = "pretty") -> list[dict]:
410411
return artifacts
411412

412413

413-
def download_artifacts(job_id: str, output_dir: str = None) -> None:
414+
def download_artifacts(job_id: str, experiment_id: str, output_dir: str | None = None) -> None:
414415
"""Download all artifacts for a job as a zip file."""
415416
if output_dir is None:
416417
output_dir = os.getcwd()
@@ -426,9 +427,10 @@ def download_artifacts(job_id: str, output_dir: str = None) -> None:
426427
if os.path.exists(output_path):
427428
console.print(f"[warning]Warning:[/warning] File {output_path} already exists. It will be overwritten.")
428429

430+
download_all_url = f"/experiment/{experiment_id}/jobs/{job_id}/artifacts/download_all"
429431
try:
430432
with console.status(f"[bold success]Downloading artifacts for job {job_id}...[/bold success]", spinner="dots"):
431-
response = api.get(f"/jobs/{job_id}/artifacts/download_all", timeout=300.0)
433+
response = api.get(download_all_url, timeout=300.0)
432434

433435
if response.status_code == 200:
434436
# Get filename from Content-Disposition header if available
@@ -485,7 +487,8 @@ def command_job_artifacts(
485487
):
486488
"""List artifacts for a job."""
487489
check_configs(output_format=cli_state.output_format)
488-
list_artifacts(job_id, output_format=cli_state.output_format)
490+
current_experiment = require_current_experiment()
491+
list_artifacts(job_id, current_experiment, output_format=cli_state.output_format)
489492

490493

491494
@app.command("download")
@@ -502,14 +505,15 @@ def command_job_download(
502505
):
503506
"""Download artifacts for a job. Use --file to download specific files."""
504507
check_configs(output_format=cli_state.output_format)
508+
current_experiment = require_current_experiment()
505509
out = os.path.abspath(output_dir) if output_dir else os.getcwd()
506510
output_format = cli_state.output_format
507511

508512
if not file:
509-
download_artifacts(job_id, output_dir)
513+
download_artifacts(job_id, current_experiment, output_dir)
510514
return
511515

512-
raw_resp = api.get(f"/jobs/{job_id}/artifacts")
516+
raw_resp = api.get(f"/experiment/{current_experiment}/jobs/{job_id}/artifacts")
513517
if raw_resp.status_code != 200:
514518
if output_format == "json":
515519
print(json.dumps({"error": f"Failed to fetch artifacts. Status code: {raw_resp.status_code}"}))
@@ -532,7 +536,7 @@ def command_job_download(
532536
for filename in matched:
533537
if output_format != "json":
534538
console.print(f"Downloading [cyan]{filename}[/cyan]...")
535-
path = download_single_artifact(job_id, filename, out)
539+
path = download_single_artifact(job_id, current_experiment, filename, out)
536540
if path:
537541
downloaded.append({"filename": filename, "path": path})
538542
elif output_format != "json":
@@ -544,17 +548,18 @@ def command_job_download(
544548
console.print(f"[green]✓[/green] Downloaded {len(downloaded)}/{len(matched)} file(s) to {out}")
545549

546550

547-
def download_single_artifact(job_id: str, filename: str, output_dir: str) -> str | None:
551+
def download_single_artifact(job_id: str, experiment_id: str, filename: str, output_dir: str) -> str | None:
548552
"""Download a single artifact by filename. Returns local path or None on failure."""
549553
output_path = os.path.join(output_dir, filename)
554+
base_url = f"/experiment/{experiment_id}/jobs/{job_id}"
550555
try:
551-
response = api.get(f"/jobs/{job_id}/artifact/{filename}?task=download", timeout=300.0)
556+
response = api.get(f"{base_url}/artifact/{filename}?task=download", timeout=300.0)
552557
if response.status_code == 200:
553558
with open(output_path, "wb") as f:
554559
f.write(response.content)
555560
return output_path
556561
if response.status_code in (404, 405):
557-
zip_response = api.get(f"/jobs/{job_id}/artifacts/download_all", timeout=300.0)
562+
zip_response = api.get(f"{base_url}/artifacts/download_all", timeout=300.0)
558563
if zip_response.status_code == 200:
559564
with zipfile.ZipFile(io.BytesIO(zip_response.content)) as zf:
560565
names = zf.namelist()

cli/src/transformerlab_cli/commands/job_monitor/JobDetails.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,9 @@ def download_artifacts(self, job_id: str) -> None:
210210
output_path = os.path.join(output_dir, filename)
211211

212212
# Make the API request
213+
experiment_id = get_current_experiment() or "alpha"
213214
try:
214-
response = api.get(f"/jobs/{job_id}/artifacts/download_all", timeout=300.0)
215+
response = api.get(f"/experiment/{experiment_id}/jobs/{job_id}/artifacts/download_all", timeout=300.0)
215216
except httpx.HTTPError as e:
216217
self.notify(f"Failed to connect to server: {str(e)}", severity="error")
217218
return

cli/tests/integration/test_live_cli_routes.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -343,19 +343,24 @@ def test_cli_job_and_artifact_routes_live_server(live_context: dict[str, str]) -
343343
"GET /experiment/{id}/jobs/{job_id}/tunnel_info",
344344
)
345345
_assert_status_in(
346-
client.get(f"{BASE_URL}/jobs/{fake_job_id}/artifacts", headers=headers),
346+
client.get(f"{BASE_URL}/experiment/{experiment_id}/jobs/{fake_job_id}/artifacts", headers=headers),
347347
{200, 400, 404},
348-
"GET /jobs/{job_id}/artifacts",
348+
"GET /experiment/{id}/jobs/{job_id}/artifacts",
349349
)
350350
_assert_status_in(
351-
client.get(f"{BASE_URL}/jobs/{fake_job_id}/artifact/does-not-exist.txt?task=download", headers=headers),
351+
client.get(
352+
f"{BASE_URL}/experiment/{experiment_id}/jobs/{fake_job_id}/artifact/does-not-exist.txt?task=download",
353+
headers=headers,
354+
),
352355
{400, 404, 405},
353-
"GET /jobs/{job_id}/artifact/{filename}",
356+
"GET /experiment/{id}/jobs/{job_id}/artifact/{filename}",
354357
)
355358
_assert_status_in(
356-
client.get(f"{BASE_URL}/jobs/{fake_job_id}/artifacts/download_all", headers=headers),
359+
client.get(
360+
f"{BASE_URL}/experiment/{experiment_id}/jobs/{fake_job_id}/artifacts/download_all", headers=headers
361+
),
357362
{400, 404},
358-
"GET /jobs/{job_id}/artifacts/download_all",
363+
"GET /experiment/{id}/jobs/{job_id}/artifacts/download_all",
359364
)
360365
_assert_status_in(
361366
client.post(

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)