1111from fastapi .responses import StreamingResponse , FileResponse
1212from json import JSONDecodeError
1313from lab import Job , storage
14+ from lab .job_status import JobStatus
1415from sqlalchemy .ext .asyncio import AsyncSession
1516from werkzeug .utils import secure_filename
1617
@@ -69,6 +70,30 @@ def _eval_results_storage_path(stored_path: str, eval_dir: str) -> str:
6970 return p
7071
7172
73+ def _job_logs_not_ready_payload (
74+ job_data : dict ,
75+ tail_lines : int ,
76+ message : str ,
77+ * ,
78+ include_request_id : bool = False ,
79+ ) -> dict :
80+ payload = {
81+ "cluster_name" : job_data .get ("cluster_name" ),
82+ "provider_id" : job_data .get ("provider_id" ),
83+ "provider_job_id" : None ,
84+ "provider_name" : job_data .get ("provider_name" ),
85+ "tail_lines" : tail_lines ,
86+ "logs" : "" ,
87+ "job_candidates" : [],
88+ "message" : message ,
89+ "retryable" : True ,
90+ "retry_after_seconds" : 10 ,
91+ }
92+ if include_request_id :
93+ payload ["request_id" ] = None
94+ return payload
95+
96+
7297@router .get ("/list" )
7398async def jobs_get_all (
7499 experimentId : str ,
@@ -271,15 +296,25 @@ async def get_provider_job_logs(
271296 raise HTTPException (status_code = 404 , detail = "Job not found" )
272297
273298 job_data = job .get ("job_data" ) or {}
299+ job_status = str (job .get ("status" ) or "" ).upper ()
274300 if not isinstance (job_data , dict ):
275301 try :
276302 job_data = json .loads (job_data )
277303 except JSONDecodeError :
278304 job_data = {}
279305
306+ def _logs_not_ready_response () -> dict :
307+ return _job_logs_not_ready_payload (
308+ job_data ,
309+ tail_lines ,
310+ "Machine logs are not available yet. Please try again shortly." ,
311+ )
312+
280313 provider_id = job_data .get ("provider_id" )
281314 cluster_name = job_data .get ("cluster_name" )
282315 if not provider_id or not cluster_name :
316+ if job_status in {JobStatus .LAUNCHING .value , JobStatus .WAITING .value , "CREATED" }:
317+ return _logs_not_ready_response ()
283318 raise HTTPException (
284319 status_code = 400 , detail = "Job does not contain provider metadata (provider_id/cluster_name missing)"
285320 )
@@ -372,6 +407,8 @@ async def get_provider_job_logs(
372407 ]
373408
374409 if provider_job_id is None :
410+ if job_status in {JobStatus .LAUNCHING .value , JobStatus .WAITING .value , "CREATED" }:
411+ return _logs_not_ready_response ()
375412 raise HTTPException (status_code = 404 , detail = "Unable to determine provider job id for this job" )
376413
377414 # For local provider, set workspace_dir (job dir) so LocalProvider can read logs.
@@ -437,6 +474,7 @@ async def get_request_logs(
437474 raise HTTPException (status_code = 404 , detail = "Job not found" )
438475
439476 job_data = job .get ("job_data" ) or {}
477+ job_status = str (job .get ("status" ) or "" ).upper ()
440478 if not isinstance (job_data , dict ):
441479 try :
442480 job_data = json .loads (job_data )
@@ -445,6 +483,13 @@ async def get_request_logs(
445483
446484 provider_id = job_data .get ("provider_id" )
447485 if not provider_id :
486+ if job_status in {JobStatus .LAUNCHING .value , JobStatus .WAITING .value , "CREATED" }:
487+ return _job_logs_not_ready_payload (
488+ job_data ,
489+ tail_lines ,
490+ "Request logs are not available yet. Please try again shortly." ,
491+ include_request_id = True ,
492+ )
448493 raise HTTPException (status_code = 400 , detail = "Job does not contain provider metadata (provider_id missing)" )
449494
450495 provider_launch_result = job_data .get ("provider_launch_result" )
@@ -454,6 +499,13 @@ async def get_request_logs(
454499 if not request_id :
455500 request_id = job_data .get ("orchestrator_request_id" )
456501 if not request_id :
502+ if job_status in {JobStatus .LAUNCHING .value , JobStatus .WAITING .value , "CREATED" }:
503+ return _job_logs_not_ready_payload (
504+ job_data ,
505+ tail_lines ,
506+ "Request logs are not available yet. Please try again shortly." ,
507+ include_request_id = True ,
508+ )
457509 raise HTTPException (status_code = 400 , detail = "Job does not have a provider request ID" )
458510
459511 team_id = user_and_team ["team_id" ]
@@ -695,6 +747,7 @@ async def get_job_task_logs(
695747 raise HTTPException (status_code = 404 , detail = "Job not found" )
696748
697749 job_data = job .get ("job_data" ) or {}
750+ job_status = str (job .get ("status" ) or "" ).upper ()
698751 if not isinstance (job_data , dict ):
699752 try :
700753 job_data = json .loads (job_data )
@@ -711,9 +764,21 @@ async def get_job_task_logs(
711764 try :
712765 output_file_name = await shared .get_job_output_file_name (job_id , experiment_name = experimentId )
713766 except (ValueError , FileNotFoundError ):
767+ if job_status in {JobStatus .LAUNCHING .value , JobStatus .WAITING .value , "CREATED" }:
768+ return _job_logs_not_ready_payload (
769+ job_data ,
770+ tail_lines ,
771+ "Task logs are not available yet. Please try again shortly." ,
772+ )
714773 return {"logs" : "" , "tail_lines" : tail_lines }
715774
716775 if not await storage .exists (output_file_name ):
776+ if job_status in {JobStatus .LAUNCHING .value , JobStatus .WAITING .value , "CREATED" }:
777+ return _job_logs_not_ready_payload (
778+ job_data ,
779+ tail_lines ,
780+ "Task logs are not available yet. Please try again shortly." ,
781+ )
717782 return {"logs" : "" , "tail_lines" : tail_lines }
718783
719784 async with await storage .open (output_file_name , "r" ) as f :
0 commit comments