2020from contextlib import suppress
2121from math import ceil
2222from operator import attrgetter
23- from typing import List
23+ from typing import List , TYPE_CHECKING
2424
2525from bscearth .utils .date import sum_str_hours
2626
3131from autosubmit .job .template import Language
3232from autosubmit .log .log import Log , AutosubmitCritical
3333
34+ if TYPE_CHECKING :
35+ from autosubmit .config .configcommon import AutosubmitConfig
36+ from autosubmit .job .job_list import JobList
37+ from autosubmit .platforms .paramiko_platform import ParamikoPlatform
38+
3439
3540class JobPackager (object ):
3641 """
@@ -44,8 +49,7 @@ class JobPackager(object):
4449 :type jobs_list: JobList object.
4550 """
4651
47-
48- def __init__ (self , as_config , platform , jobs_list , hold = False ):
52+ def __init__ (self , as_config : 'AutosubmitConfig' , platform : 'ParamikoPlatform' , jobs_list : 'JobList' , hold = False ):
4953 self .current_wrapper_section = "WRAPPERS"
5054 self ._as_config = as_config
5155 self ._platform = platform
@@ -65,9 +69,8 @@ def __init__(self, as_config, platform, jobs_list, hold=False):
6569 self .special_variables = dict ()
6670 self .wrappers_with_error = {}
6771
68-
69- #todo add default values
70- #Wrapper building starts here
72+ # TODO: Add default values
73+ # Wrapper building starts here
7174 for wrapper_section ,wrapper_data in self ._as_config .experiment_data .get ("WRAPPERS" ,{}).items ():
7275 if isinstance (wrapper_data ,collections .abc .Mapping ):
7376 self .wrapper_type [wrapper_section ] = self ._as_config .get_wrapper_type (wrapper_data )
@@ -127,7 +130,6 @@ def compute_weight(self, job_list):
127130 job .distance_weight = job .distance_weight - 1
128131
129132 def calculate_wrapper_bounds (self , section_list ):
130-
131133 """
132134 Returns the minimum and maximum number of jobs that can be wrapped
133135
@@ -433,56 +435,62 @@ def error_message_policy(self, min_h: int, min_v: int, wrapper_limits: dict, bal
433435 message += "\n This message is activated when only jobs_in_wrappers are in active(Ready+) status.\n "
434436 return message
435437
436- def check_if_packages_are_ready_to_build (self ):
437- """
438- Check if the packages are ready to be built
439- :return: List of jobs ready to be built, boolean indicating if packages can't be built for other reasons ( max_total_jobs...)
438+ def check_if_packages_are_ready_to_build (self ) -> tuple [list [Job ], bool ]:
439+ """Check if the packages are ready to be built.
440+
441+ Returns a tuple with two elements. First contains the list of jobs ready to be built.
442+ The second element in the tuple is a boolean indicating if it can be built or not.
443+
444+ :return: list of jobs ready to be built, boolean indicating if there are underlying blocking errors.
440445 """
441446 Log .info ("Calculating possible ready jobs for {0}" .format (self ._platform .name ))
442- jobs_ready = list ()
447+ jobs_ready = []
443448 if len (self ._jobs_list .jobs_to_run_first ) > 0 :
444- jobs_ready = [job for job in self ._jobs_list .jobs_to_run_first if
445- ( self ._platform is None or job .platform .name .upper () == self ._platform .name .upper ()) and
446- job .status == Status .READY ]
447- if len (jobs_ready ) == 0 :
449+ jobs_ready = [
450+ job
451+ for job in self ._jobs_list .jobs_to_run_first
452+ if (self ._platform is None or job .platform .name .upper () == self ._platform .name .upper ())
453+ and job .status == Status .READY
454+ ]
455+ if not jobs_ready :
448456 if self .hold :
449457 jobs_ready = self ._jobs_list .get_prepared (self ._platform )
450458 else :
451459 jobs_ready = self ._jobs_list .get_ready (self ._platform )
452460
453- if self .hold and len ( jobs_ready ) > 0 :
461+ if self .hold and jobs_ready :
454462 self .compute_weight (jobs_ready )
455- sorted_jobs = sorted (
456- jobs_ready , key = operator . attrgetter ( 'distance_weight' ))
457- jobs_in_held_status = self . _jobs_list . get_held_jobs () + self . _jobs_list . get_submitted ( self ._platform , hold = self .hold )
463+ sorted_jobs = sorted (jobs_ready , key = operator . attrgetter ( 'distance_weight' ))
464+ jobs_in_held_status = self . _jobs_list . get_held_jobs () + self . _jobs_list . get_submitted (
465+ self ._platform , hold = self .hold )
458466 held_by_id = dict ()
459467 for held_job in jobs_in_held_status :
460468 if held_job .id not in held_by_id :
461469 held_by_id [held_job .id ] = []
462470 held_by_id [held_job .id ].append (held_job )
463471 current_held_jobs = len (list (held_by_id .keys ()))
464472 remaining_held_slots = 5 - current_held_jobs
465- Log .debug ("there are currently {0 } held jobs". format ( remaining_held_slots ) )
466- try :
473+ Log .debug (f"There are currently { remaining_held_slots } held jobs" )
474+ with suppress ( IndexError ) :
467475 while len (sorted_jobs ) > remaining_held_slots :
468476 del sorted_jobs [- 1 ]
469477 for job in sorted_jobs :
470478 if job .distance_weight > 3 :
471479 sorted_jobs .remove (job )
472480 jobs_ready = sorted_jobs
473- pass
474- except IndexError :
475- pass
476- if len (jobs_ready ) == 0 :
477- # If there are no jobs ready, result is tuple of empty
478- return jobs_ready ,False
479- #check if there are jobs listed on calculate_job_limits
481+
482+ # If there are no jobs ready, result is tuple of empty
483+ if not jobs_ready :
484+ return jobs_ready , False
485+
486+ # Check if there are jobs listed on calculate_job_limits
480487 self .calculate_job_limits (self ._platform )
488+ # If there is no more space in platform, result is tuple of empty
481489 if not (self ._max_wait_jobs_to_submit > 0 and self ._max_jobs_to_submit > 0 ):
482- # If there is no more space in platform, result is tuple of empty
483- Log . debug ( 'Max jobs to submit reached, waiting for more space in platform {0}' . format ( self . _platform . name ))
484- return jobs_ready , False
485- return jobs_ready ,True
490+ Log . debug ( f'Max jobs to submit reached, waiting for more space in platform { self . _platform . name } ' )
491+ return jobs_ready , False
492+
493+ return jobs_ready , True
486494
487495 def calculate_job_limits (self ,platform ,job = None ):
488496 jobs_list = self ._jobs_list
@@ -683,7 +691,6 @@ def _divide_list_by_section(self, jobs_list):
683691 del jobs_by_section [wrappers ]
684692 return jobs_by_section
685693
686-
687694 def _build_horizontal_packages (self , section_list , wrapper_limits , section , wrapper_info = {}):
688695 packages = []
689696 horizontal_packager = JobPackagerHorizontal (section_list , self ._platform .max_processors , wrapper_limits ,
@@ -731,7 +738,7 @@ def _build_vertical_packages(self, section_list, wrapper_limits,wrapper_info={})
731738 return packages
732739
733740 def _build_hybrid_package (self , jobs_list , wrapper_limits , section ,wrapper_info = {}):
734- #self.wrapper_info = wrapper_info
741+ # self.wrapper_info = wrapper_info
735742 jobs_resources = dict ()
736743 jobs_resources ['MACHINEFILES' ] = self ._as_config .get_wrapper_machinefiles ()
737744
@@ -802,7 +809,9 @@ def _build_vertical_horizontal_package(self, horizontal_packager, jobs_resources
802809 return JobPackageVerticalHorizontal (current_package , total_processors , total_wallclock ,
803810 jobs_resources = jobs_resources , method = self .wrapper_method [self .current_wrapper_section ], configuration = self ._as_config , wrapper_section = self .current_wrapper_section )
804811
805- #TODO rename and unite JobPackerVerticalMixed to JobPackerVertical since the difference between the two is not needed anymore
812+
813+ # TODO: Rename and unite JobPackerVerticalMixed to JobPackerVertical since
814+ # the difference between the two is not needed anymore
806815class JobPackagerVertical (object ):
807816 """
808817 Vertical Packager Parent Class
@@ -867,7 +876,7 @@ def build_vertical_package(self, job, wrapper_info):
867876 return self .jobs_list
868877
869878 def get_wrappable_child (self , job ):
870- pass
879+ pass # pragma: no cover
871880
872881 def _is_wrappable (self , job ):
873882 """
@@ -888,6 +897,7 @@ def _is_wrappable(self, job):
888897 return True
889898 return False
890899
900+
891901class JobPackagerVerticalMixed (JobPackagerVertical ):
892902 """
893903 Vertical Mixed Class. First statement of the constructor builds JobPackagerVertical.
@@ -926,7 +936,6 @@ def __init__(self, dict_jobs, ready_job, jobs_list, total_wallclock, max_jobs, w
926936 self .sorted_jobs = dict_jobs [date ][member ]
927937 self .index = 0
928938
929-
930939 def get_wrappable_child (self , job : Job ) -> Job :
931940 """
932941 Goes through the jobs with the same date and member as the input job, and returns the first that satisfies self._is_wrappable().
@@ -1113,7 +1122,8 @@ def components_dict(self):
11131122 def create_components_dict (self ):
11141123 self ._sectionList = []
11151124 # it was job.parameters
1116- parameters = {} # TODO machinefiles, can wait nobody is using it and I really think this was not working before anyway
1125+ # TODO machinefiles, can wait nobody is using it and I really think this was not working before anyway
1126+ parameters = {}
11171127 for job in self .job_list :
11181128 if job .section not in self ._sectionList :
11191129 self ._sectionList .append (job .section )
0 commit comments