1010import argparse
1111import json
1212import os
13+ import shutil
1314import sqlite3
1415import sys
1516import threading
1819from pathlib import Path
1920from typing import Any , Dict , List , Optional
2021
21- from fastapi import BackgroundTasks , FastAPI , HTTPException , Query
22+ from fastapi import BackgroundTasks , FastAPI , HTTPException , Query , UploadFile , File , Form
2223from fastapi .responses import HTMLResponse , StreamingResponse
2324from fastapi .staticfiles import StaticFiles
2425from pydantic import BaseModel , Field
@@ -65,6 +66,7 @@ class RunRecord(BaseModel):
6566
6667RUN_DB_PATH = Path (os .environ .get ("WEBAPI_RUN_DB" , PROJECT_ROOT / "WEBAPI" / "runs.db" ))
6768ALLOWED_SCRIPT_ROOT = Path (os .environ .get ("WEBAPI_ALLOWED_ROOT" , PROJECT_ROOT )).resolve ()
69+ UPLOAD_DIR = PROJECT_ROOT / "WEBAPI" / "uploads"
6870
6971
7072class RunStore :
@@ -275,11 +277,8 @@ def _validate_payload(payload: RunRequest) -> RunRequest:
275277 return payload
276278
277279
278- @app .post ("/api/run" , status_code = 202 )
279- def trigger_run (payload : RunRequest , background_tasks : BackgroundTasks ) -> Dict [str , str ]:
280- """Queue a new script execution and return its identifier."""
281-
282- payload = _validate_payload (payload )
280+ def _queue_run (payload : RunRequest , background_tasks : BackgroundTasks ) -> Dict [str , str ]:
281+ """Helper to queue a run execution."""
283282 run_id = str (uuid .uuid4 ())
284283 now = datetime .utcnow ()
285284 record = RunRecord (
@@ -299,6 +298,51 @@ def trigger_run(payload: RunRequest, background_tasks: BackgroundTasks) -> Dict[
299298 return {"run_id" : run_id , "status" : "queued" }
300299
301300
301+ @app .post ("/api/run" , status_code = 202 )
302+ def trigger_run (payload : RunRequest , background_tasks : BackgroundTasks ) -> Dict [str , str ]:
303+ """Queue a new script execution and return its identifier."""
304+
305+ payload = _validate_payload (payload )
306+ return _queue_run (payload , background_tasks )
307+
308+
309+ @app .post ("/api/run/upload" , status_code = 202 )
310+ def trigger_run_upload (
311+ background_tasks : BackgroundTasks ,
312+ file : UploadFile = File (...),
313+ args : str = Form ("" ),
314+ timeout : Optional [int ] = Form (None ),
315+ log_level : str = Form ("INFO" ),
316+ retry_on_failure : bool = Form (False ),
317+ ) -> Dict [str , str ]:
318+ """Upload a script and queue execution."""
319+ if not file .filename .endswith (('.py' , '.pyw' )):
320+ raise HTTPException (status_code = 400 , detail = "Only Python files are allowed" )
321+
322+ # Ensure upload directory exists
323+ UPLOAD_DIR .mkdir (parents = True , exist_ok = True )
324+
325+ safe_filename = f"{ uuid .uuid4 ().hex } _{ file .filename } "
326+ file_path = UPLOAD_DIR / safe_filename
327+
328+ with open (file_path , "wb" ) as buffer :
329+ shutil .copyfileobj (file .file , buffer )
330+
331+ # Parse args (comma separated string)
332+ arg_list = [a .strip () for a in args .split (',' ) if a .strip ()]
333+
334+ payload = RunRequest (
335+ script_path = str (file_path .resolve ()),
336+ args = arg_list ,
337+ timeout = timeout ,
338+ log_level = log_level ,
339+ retry_on_failure = retry_on_failure
340+ )
341+
342+ payload = _validate_payload (payload )
343+ return _queue_run (payload , background_tasks )
344+
345+
302346@app .get ("/api/runs" )
303347def list_runs (
304348 limit : int = Query (50 , ge = 1 , le = 200 , description = "Maximum number of runs to return" ),
0 commit comments