@@ -325,56 +325,45 @@ def duration(self) -> timedelta:
325325 end_time = self .finished_at
326326 return end_time - self .submitted_at
327327
328- @root_validator
329- def _status_message (cls , values ) -> Dict :
330- try :
331- status = values ["status" ]
332- termination_reason = values ["termination_reason" ]
333- exit_code = values ["exit_status" ]
334- except KeyError :
335- return values
336- values ["status_message" ] = JobSubmission ._get_status_message (
337- status = status ,
338- termination_reason = termination_reason ,
339- exit_status = exit_code ,
340- )
341- return values
328+ def dict (self , * args , ** kwargs ) -> Dict :
329+ status_message = self ._get_status_message ()
330+ error = self ._get_error ()
331+ # super() does not work with pydantic-duality
332+ res = CoreModel .dict (self , * args , ** kwargs )
333+ res ["status_message" ] = status_message
334+ res ["error" ] = error
335+ return res
342336
343- @staticmethod
344- def _get_status_message (
345- status : JobStatus ,
346- termination_reason : Optional [JobTerminationReason ],
347- exit_status : Optional [int ],
348- ) -> str :
349- if status == JobStatus .DONE :
337+ def _get_status_message (self ) -> Optional [str ]:
338+ if self .status == JobStatus .DONE :
350339 return "exited (0)"
351- elif status == JobStatus .FAILED :
352- if termination_reason == JobTerminationReason .CONTAINER_EXITED_WITH_ERROR :
353- return f"exited ({ exit_status } )"
354- elif termination_reason == JobTerminationReason .FAILED_TO_START_DUE_TO_NO_CAPACITY :
340+ elif self .status == JobStatus .FAILED :
341+ if self .termination_reason == JobTerminationReason .CONTAINER_EXITED_WITH_ERROR :
342+ return f"exited ({ self .exit_status } )"
343+ elif (
344+ self .termination_reason == JobTerminationReason .FAILED_TO_START_DUE_TO_NO_CAPACITY
345+ ):
355346 return "no offers"
356- elif termination_reason == JobTerminationReason .INTERRUPTED_BY_NO_CAPACITY :
347+ elif self . termination_reason == JobTerminationReason .INTERRUPTED_BY_NO_CAPACITY :
357348 return "interrupted"
358349 else :
359350 return "error"
360- elif status == JobStatus .TERMINATED :
361- if termination_reason == JobTerminationReason .TERMINATED_BY_USER :
351+ elif self . status == JobStatus .TERMINATED :
352+ if self . termination_reason == JobTerminationReason .TERMINATED_BY_USER :
362353 return "stopped"
363- elif termination_reason == JobTerminationReason .ABORTED_BY_USER :
354+ elif self . termination_reason == JobTerminationReason .ABORTED_BY_USER :
364355 return "aborted"
365- return status .value
356+ return self . status .value
366357
367- @root_validator
368- def _error (cls , values ) -> Dict :
369- try :
370- termination_reason = values ["termination_reason" ]
371- except KeyError :
372- return values
373- values ["error" ] = JobSubmission ._get_error (termination_reason = termination_reason )
374- return values
358+ def _get_error (self ) -> Optional [str ]:
359+ return JobSubmission ._termination_reason_to_error (
360+ termination_reason = self .termination_reason
361+ )
375362
376363 @staticmethod
377- def _get_error (termination_reason : Optional [JobTerminationReason ]) -> Optional [str ]:
364+ def _termination_reason_to_error (
365+ termination_reason : Optional [JobTerminationReason ],
366+ ) -> Optional [str ]:
378367 error_mapping = {
379368 JobTerminationReason .INSTANCE_UNREACHABLE : "instance unreachable" ,
380369 JobTerminationReason .WAITING_INSTANCE_LIMIT_EXCEEDED : "waiting instance limit exceeded" ,
@@ -395,6 +384,12 @@ class Job(CoreModel):
395384 job_spec : JobSpec
396385 job_submissions : List [JobSubmission ]
397386
387+ def get_last_termination_reason (self ) -> Optional [JobTerminationReason ]:
388+ for submission in reversed (self .job_submissions ):
389+ if submission .termination_reason is not None :
390+ return submission .termination_reason
391+ return None
392+
398393
399394class RunSpec (CoreModel ):
400395 # TODO: run_name, working_dir are redundant here since they already passed in configuration
@@ -525,83 +520,66 @@ class Run(CoreModel):
525520 last_processed_at : datetime
526521 status : RunStatus
527522 status_message : Optional [str ] = None
528- termination_reason : Optional [RunTerminationReason ]
523+ termination_reason : Optional [RunTerminationReason ] = None
529524 run_spec : RunSpec
530525 jobs : List [Job ]
531- latest_job_submission : Optional [JobSubmission ]
526+ latest_job_submission : Optional [JobSubmission ] = None
532527 cost : float = 0
533528 service : Optional [ServiceSpec ] = None
534529 deployment_num : int = 0 # default for compatibility with pre-0.19.14 servers
535530 # TODO: make error a computed field after migrating to pydanticV2
536531 error : Optional [str ] = None
537532 deleted : Optional [bool ] = None
538533
539- @root_validator
540- def _error (cls , values ) -> Dict :
541- try :
542- termination_reason = values ["termination_reason" ]
543- except KeyError :
544- return values
545- values ["error" ] = Run ._get_error (termination_reason = termination_reason )
546- return values
534+ def dict (self , * args , ** kwargs ) -> Dict :
535+ status_message = self ._get_status_message ()
536+ error = self ._get_error ()
537+ # super() does not work with pydantic-duality
538+ res = CoreModel .dict (self , * args , ** kwargs )
539+ res ["status_message" ] = status_message
540+ res ["error" ] = error
541+ return res
542+
543+ def _get_error (self ) -> Optional [str ]:
544+ return Run ._termination_reason_to_error (termination_reason = self .termination_reason )
547545
548546 @staticmethod
549- def _get_error (termination_reason : Optional [RunTerminationReason ]) -> Optional [str ]:
547+ def _termination_reason_to_error (
548+ termination_reason : Optional [RunTerminationReason ],
549+ ) -> Optional [str ]:
550550 if termination_reason == RunTerminationReason .RETRY_LIMIT_EXCEEDED :
551551 return "retry limit exceeded"
552552 elif termination_reason == RunTerminationReason .SERVER_ERROR :
553553 return "server error"
554554 else :
555555 return None
556556
557- @root_validator
558- def _status_message (cls , values ) -> Dict :
559- try :
560- status = values ["status" ]
561- jobs : List [Job ] = values ["jobs" ]
562- retry_on_events = (
563- jobs [0 ].job_spec .retry .on_events if jobs and jobs [0 ].job_spec .retry else []
564- )
565- job_status = (
566- jobs [0 ].job_submissions [- 1 ].status
567- if len (jobs ) == 1 and jobs [0 ].job_submissions
568- else None
569- )
570- termination_reason = Run .get_last_termination_reason (jobs [0 ]) if jobs else None
571- except KeyError :
572- return values
573- values ["status_message" ] = Run ._get_status_message (
574- status = status ,
575- job_status = job_status ,
576- retry_on_events = retry_on_events ,
577- termination_reason = termination_reason ,
578- )
579- return values
557+ def _get_status_message (self ) -> Optional [str ]:
558+ if len (self .jobs ) == 0 :
559+ return self .status .value
580560
581- @staticmethod
582- def get_last_termination_reason (job : "Job" ) -> Optional [JobTerminationReason ]:
583- for submission in reversed (job .job_submissions ):
584- if submission .termination_reason is not None :
585- return submission .termination_reason
586- return None
561+ last_job = self .jobs [0 ]
562+ last_job_termination_reason = last_job .get_last_termination_reason ()
587563
588- @ staticmethod
589- def _get_status_message (
590- status : RunStatus ,
591- job_status : Optional [ JobStatus ],
592- retry_on_events : List [ RetryEvent ],
593- termination_reason : Optional [ JobTerminationReason ],
594- ) -> str :
595- if job_status == JobStatus . PULLING :
596- return "pulling"
564+ if len ( self . jobs ) == 1 :
565+ # FIXME: Clarify why show "pulling" only in case of one job
566+ if (
567+ last_job . job_submissions
568+ and last_job . job_submissions [ - 1 ]. status == JobStatus . PULLING
569+ ):
570+ return "pulling"
571+
572+ retry_on_events = last_job . job_spec . retry . on_events if last_job . job_spec . retry else []
597573 # Currently, `retrying` is shown only for `no-capacity` events
598574 if (
599- status in [RunStatus .SUBMITTED , RunStatus .PENDING ]
600- and termination_reason == JobTerminationReason .FAILED_TO_START_DUE_TO_NO_CAPACITY
575+ self .status in [RunStatus .SUBMITTED , RunStatus .PENDING ]
576+ and last_job_termination_reason
577+ == JobTerminationReason .FAILED_TO_START_DUE_TO_NO_CAPACITY
601578 and RetryEvent .NO_CAPACITY in retry_on_events
602579 ):
603580 return "retrying"
604- return status .value
581+
582+ return self .status .value
605583
606584 def is_deployment_in_progress (self ) -> bool :
607585 return any (
0 commit comments