feat: multi-file upload with zip extraction

This commit is contained in:
2026-04-26 00:55:33 +00:00
parent b4c54142ff
commit 145ac11023

View File

@@ -4,7 +4,7 @@ import os
import tempfile import tempfile
import threading import threading
from pathlib import Path from pathlib import Path
from typing import Optional from typing import List, Optional
import httpx import httpx
import yaml import yaml
@@ -195,20 +195,34 @@ async def preview_file(stage: str, filename: str, lines: int = 120):
@app.post("/api/upload") @app.post("/api/upload")
async def upload_file(file: UploadFile = File(...)): async def upload_files(files: List[UploadFile] = File(...)):
_require_ssh() _require_ssh()
results = []
suffix = Path(file.filename).suffix for file in files:
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp: suffix = Path(file.filename).suffix.lower()
tmp.write(await file.read()) with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
tmp_path = tmp.name tmp.write(await file.read())
tmp_path = tmp.name
try: try:
remote_path = f"{STAGE_DIRS['input']}/{file.filename}" if suffix == ".zip":
ssh_manager.upload_file(tmp_path, remote_path) remote_tmp = f"/tmp/{file.filename}"
return {"uploaded": file.filename, "remote_path": remote_path} ssh_manager.upload_file(tmp_path, remote_tmp)
finally: input_dir = STAGE_DIRS["input"]
os.unlink(tmp_path) out, err, code = ssh_manager.execute(
f"unzip -o '{remote_tmp}' -d '{input_dir}' "
f"&& rm '{remote_tmp}'",
use_conda=False,
)
if code != 0:
raise HTTPException(status_code=500, detail=f"Unzip failed for {file.filename}: {err}")
results.append({"file": file.filename, "action": "extracted"})
else:
remote_path = f"{STAGE_DIRS['input']}/{file.filename}"
ssh_manager.upload_file(tmp_path, remote_path)
results.append({"file": file.filename, "action": "uploaded", "remote_path": remote_path})
finally:
os.unlink(tmp_path)
return {"results": results}
# ────────────────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────────────────