44import  os 
55from  abc  import  ABC , abstractmethod 
66from  datetime  import  datetime 
7+ from  pathlib  import  Path 
78from  typing  import  List , Callable , Union , Sequence 
89
910import  yaml 
@@ -284,9 +285,11 @@ def __init__(
284285                self .build , self .name , self .registry .path .as_posix ()
285286            )
286287
287-     def  stage (self , time_windows = None , run_mode = "sequential" , run_dir = "" ) ->  None :
288+     def  stage (
289+         self , time_windows = None , run_mode = "sequential" , stage_dir = "results" , run_id = "run" 
290+     ) ->  None :
288291        """ 
289-         Core method to interface a model  with the experiment. 
292+         Retrieve model artifacts and Set up its interface  with the experiment. 
290293
291294        1) Get the model from filesystem, Zenodo or Git. Prepares the directory 
292295        2) If source code, creates the computational environment (conda, venv or Docker) 
@@ -306,7 +309,8 @@ def stage(self, time_windows=None, run_mode="sequential", run_dir="") -> None:
306309            model_class = self .__class__ .__name__ ,
307310            prefix = self .__dict__ .get ("prefix" , self .name ),
308311            run_mode = run_mode ,
309-             run_dir = run_dir ,
312+             stage_dir = stage_dir ,
313+             run_id = run_id ,
310314        )
311315
312316    def  get_source (self , zenodo_id : int  =  None , giturl : str  =  None , ** kwargs ) ->  None :
@@ -406,73 +410,83 @@ def prepare_args(self, start: datetime, end: datetime, **kwargs) -> None:
406410        """ 
407411        window_str  =  timewindow2str ([start , end ])
408412
409-         filepath  =  self .registry .get_args_key (window_str )
410-         fmt  =  os .path .splitext (filepath )[1 ]
411- 
412-         if  fmt  ==  ".txt" :
413- 
414-             def  replace_arg (arg , val , fp ):
415-                 with  open (fp , "r" ) as  filearg_ :
416-                     lines  =  filearg_ .readlines ()
417- 
418-                 pattern_exists  =  False 
419-                 for  k , line  in  enumerate (lines ):
420-                     if  line .startswith (arg ):
421-                         lines [k ] =  f"{ arg } { val } \n " 
422-                         pattern_exists  =  True 
423-                         break   # assume there's only one occurrence of the key 
424-                 if  not  pattern_exists :
425-                     lines .append (f"{ arg } { val } \n " )
426-                 with  open (fp , "w" ) as  file :
427-                     file .writelines (lines )
428- 
429-             replace_arg ("start_date" , start .isoformat (), filepath )
430-             replace_arg ("end_date" , end .isoformat (), filepath )
431-             for  i , j  in  kwargs .items ():
432-                 replace_arg (i , j , filepath )
433- 
434-         elif  fmt  ==  ".json" :
435-             with  open (filepath , "r" ) as  file_ :
436-                 args  =  json .load (file_ )
437-             args ["start_date" ] =  start .isoformat ()
438-             args ["end_date" ] =  end .isoformat ()
439- 
440-             args .update (kwargs )
441- 
442-             with  open (filepath , "w" ) as  file_ :
443-                 json .dump (args , file_ , indent = 2 )
444- 
445-         elif  fmt  ==  ".yml"  or  fmt  ==  ".yaml" :
446- 
447-             def  nested_update (dest : dict , src : dict , max_depth : int  =  3 , _level : int  =  1 ):
448-                 """ 
449-                 Recursively update dest with values from src down to max_depth levels. 
450-                 - If dest[k] and src[k] are both dicts, recurse (until max_depth). 
451-                 - Otherwise overwrite dest[k] with src[k]. 
452-                 """ 
453-                 for  key , val  in  src .items ():
413+         dest_path  =  Path (self .registry .get_args_key (window_str ))
414+         tpl_path  =  self .registry .get_args_template_path ()
415+         suffix  =  tpl_path .suffix .lower ()
416+ 
417+         if  suffix  ==  ".txt" :
418+ 
419+             def  load_kv (fp : Path ) ->  dict :
420+                 data  =  {}
421+                 if  fp .exists ():
422+                     with  open (fp , "r" ) as  f :
423+                         for  line  in  f :
424+                             line  =  line .strip ()
425+                             if  not  line  or  line .startswith ("#" ):
426+                                 continue 
427+                             if  "="  in  line :
428+                                 k , v  =  line .split ("=" , 1 )
429+                                 data [k .strip ()] =  v .strip ()
430+                 return  data 
431+ 
432+             def  dump_kv (fp : Path , data : dict ) ->  None :
433+                 ordered_keys  =  []
434+                 for  k  in  ("start_date" , "end_date" ):
435+                     if  k  in  data :
436+                         ordered_keys .append (k )
437+                 ordered_keys  +=  sorted (
438+                     k  for  k  in  data .keys () if  k  not  in "start_date" , "end_date" )
439+                 )
440+ 
441+                 with  open (fp , "w" ) as  f :
442+                     for  k  in  ordered_keys :
443+                         f .write (f"{ k } { data [k ]} \n " )
444+ 
445+             data  =  load_kv (tpl_path )
446+             data ["start_date" ] =  start .isoformat ()
447+             data ["end_date" ] =  end .isoformat ()
448+             for  k , v  in  (kwargs  or  {}).items ():
449+                 data [k ] =  v 
450+             dump_kv (dest_path , data )
451+ 
452+         elif  suffix  ==  ".json" :
453+             base  =  {}
454+             if  tpl_path .exists ():
455+                 with  open (tpl_path , "r" ) as  f :
456+                     base  =  json .load (f ) or  {}
457+             base ["start_date" ] =  start .isoformat ()
458+             base ["end_date" ] =  end .isoformat ()
459+             base .update (kwargs  or  {})
460+ 
461+             with  open (dest_path , "w" ) as  f :
462+                 json .dump (base , f , indent = 2 )
463+ 
464+         elif  suffix  in  (".yml" , ".yaml" ):
465+             if  tpl_path .exists ():
466+                 with  open (tpl_path , "r" ) as  f :
467+                     data  =  yaml .safe_load (f ) or  {}
468+             else :
469+                 data  =  {}
470+ 
471+             data ["start_date" ] =  start .isoformat ()
472+             data ["end_date" ] =  end .isoformat ()
473+ 
474+             def  nested_update (dest : dict , src : dict , max_depth : int  =  3 , _lvl : int  =  1 ):
475+                 for  key , val  in  (src  or  {}).items ():
454476                    if  (
455-                         _level  <  max_depth 
477+                         _lvl  <  max_depth 
456478                        and  key  in  dest 
457479                        and  isinstance (dest [key ], dict )
458480                        and  isinstance (val , dict )
459481                    ):
460-                         nested_update (dest [key ], val , max_depth , _level  +  1 )
482+                         nested_update (dest [key ], val , max_depth , _lvl  +  1 )
461483                    else :
462484                        dest [key ] =  val 
463485
464-             if  not  os .path .exists (filepath ):
465-                 template_file  =  os .path .join (
466-                     self .registry .path , "input" , self .registry .args_file 
467-                 )
468-             else :
469-                 template_file  =  filepath 
486+             nested_update (data , self .func_kwargs  or  {})
487+             nested_update (data , kwargs  or  {})
488+             with  open (dest_path , "w" ) as  f :
489+                 yaml .safe_dump (data , f , indent = 2 )
470490
471-             with  open (template_file , "r" ) as  file_ :
472-                 args  =  yaml .safe_load (file_ )
473-             args ["start_date" ] =  start .isoformat ()
474-             args ["end_date" ] =  end .isoformat ()
475- 
476-             nested_update (args , self .func_kwargs )
477-             with  open (filepath , "w" ) as  file_ :
478-                 yaml .safe_dump (args , file_ , indent = 2 )
491+         else :
492+             raise  ValueError (f"Unsupported args file format: { suffix }  )
0 commit comments