@@ -841,9 +841,14 @@ def compile_workflow_once(yaml_tree_ast: YamlTree,
841841 steps_list .append (step_i_copy )
842842 yaml_tree .update ({'steps' : steps_list }) # steps_list ?
843843
844+ # --------------------------------------------------------
845+ # Helpers for generating yaml_inputs for workflow execution from inputs_workflow
846+ # --------------------------------------------------------
844847 def type_includes_null (cwl_type : Any ) -> bool :
845848 if isinstance (cwl_type , list ):
846849 return "null" in cwl_type
850+ if isinstance (cwl_type , str ) and cwl_type .endswith ("?" ):
851+ return True
847852 return False
848853
849854 def strip_null_from_union (cwl_type : Any ) -> Any :
@@ -852,99 +857,111 @@ def strip_null_from_union(cwl_type: Any) -> Any:
852857 if len (non_null ) == 1 :
853858 return non_null [0 ]
854859 return non_null
860+ if isinstance (cwl_type , str ) and cwl_type .endswith ("?" ):
861+ return cwl_type [:- 1 ]
855862 return cwl_type
856863
857864 def emit_file_or_dir (type_name : str , value : Any , fmt : Any = None ) -> Dict [str , Any ]:
858- obj = {
865+ obj : Dict [ str , Any ] = {
859866 "class" : type_name ,
860867 "location" : value
861868 }
862869 if type_name == "File" and fmt :
863870 obj ["format" ] = fmt
864871 return obj
865872
866- def populate_scalar_val (in_dict : Dict [str , Any ]) -> Any :
873+ # --------------------------------------------------------
874+ # Scalar emission for each primitive type, as well as File and Directory
875+ # --------------------------------------------------------
876+ def populate_scalar_val (cwl_type : Any , value : Any , fmt : Any = None ) -> Any :
877+ match cwl_type :
878+ case "File" :
879+ obj : Dict [str , Any ] = {
880+ "class" : "File" ,
881+ "location" : value
882+ }
883+ if fmt :
884+ obj ["format" ] = fmt
885+ return obj
886+
887+ case "Directory" :
888+ return {
889+ "class" : "Directory" ,
890+ "location" : value
891+ }
892+
893+ case "string" :
894+ return str (value )
895+
896+ case "int" :
897+ return int (value )
898+
899+ case "float" :
900+ return float (value )
901+
902+ case "boolean" :
903+ return bool (value )
904+
905+ case _:
906+ # Unknown or already structured type
907+ return value
908+
909+ # --------------------------------------------------------
910+ # Main input handling for yaml_inputs
911+ # --------------------------------------------------------
912+ def populate_input_value (in_dict : Dict [str , Any ]) -> Any :
867913 raw_type = in_dict ["type" ]
868914 value = in_dict .get ("value" )
869915 fmt = in_dict .get ("format" )
870916
871- # Optional null handling is done outside this function
872- cwl_type = strip_null_from_union (raw_type )
873-
874- # File
875- if cwl_type == "File" :
876- return emit_file_or_dir ("File" , value , fmt )
877-
878- # Directory
879- if cwl_type == "Directory" :
880- return emit_file_or_dir ("Directory" , value )
881-
882- # Primitive types
883- if cwl_type in ("string" , "string?" ):
884- return str (value )
885-
886- if cwl_type in ("int" , "int?" ):
887- return int (value )
888-
889- if cwl_type in ("float" , "float?" ):
890- return float (value )
891-
892- if cwl_type in ("boolean" , "boolean?" ):
893- return bool (value )
917+ # ---------- Null handling ----------
918+ if value is None :
919+ if type_includes_null (raw_type ):
920+ return None
921+ raise ValueError (
922+ f"Required input of type { raw_type } was not provided."
923+ )
894924
895- # Fallback for unknown types
896- return value
925+ # Normalize type (strip null / ?)
926+ cwl_type = strip_null_from_union ( raw_type )
897927
898- def populate_input_value (in_dict : Dict [str , Any ]) -> Any :
899- raw_type = in_dict ["type" ]
900- value = in_dict .get ("value" )
928+ # Normalize type
929+ cwl_type = strip_null_from_union (raw_type )
901930
902- # --- Handle optional null case ---
903- if value is None :
904- if type_includes_null (raw_type ):
905- return None # caller will omit key
906- else :
907- raise ValueError (
908- f"Required input of type { raw_type } was not provided."
909- )
910-
911- # --- Handle array types ---
912- # Case 1: {"type": "array", "items": ...}
913- if isinstance (raw_type , dict ) and raw_type .get ("type" ) == "array" :
914- item_type = raw_type ["items" ]
931+ # ---------- Array handling ----------
932+ # Case: {"type": "array", "items": ...}
933+ if isinstance (cwl_type , dict ) and cwl_type .get ("type" ) == "array" :
934+ item_type = strip_null_from_union (cwl_type ["items" ])
935+ # wrap scalar into list if necessary for lenient shape handling
936+ values = value if isinstance (value , list ) else [value ]
915937 return [
916- populate_scalar_val ({
917- "type" : item_type ,
918- "value" : val ,
919- "format" : in_dict .get ("format" )
920- })
921- for val in value
938+ populate_scalar_val (item_type , v , fmt )
939+ for v in values
922940 ]
923941
924- # Case 2 : union including array
925- if isinstance (raw_type , list ):
926- for t in raw_type :
942+ # Case: union including array
943+ if isinstance (cwl_type , list ):
944+ for t in cwl_type :
927945 if isinstance (t , dict ) and t .get ("type" ) == "array" :
928- item_type = t ["items" ]
946+ item_type = strip_null_from_union (t ["items" ])
947+ # wrap scalar into list if necessary for lenient shape handling
948+ values = value if isinstance (value , list ) else [value ]
929949 return [
930- populate_scalar_val ({
931- "type" : item_type ,
932- "value" : val ,
933- "format" : in_dict .get ("format" )
934- })
935- for val in value
950+ populate_scalar_val (item_type , v , fmt )
951+ for v in values
936952 ]
937953
938- # --- Scalar case ---
939- return populate_scalar_val (in_dict )
954+ # ---------- Scalar case ------- ---
955+ return populate_scalar_val (cwl_type , value , fmt )
940956
941- # ------------------------------
942- # Generate the final workflow inputs by populating the values from the inputs file.
943- # ------------------------------
957+ # --------------------------------------------------------
958+ # Generate yaml_inputs by populating values from inputs_file_workflow
959+ # To be dumped in *_yaml_inputs.yml and passed to CWL workflow execution as --inputs-file
960+ # --------------------------------------------------------
944961 yaml_inputs : WorkflowInputsFile = {}
945962 for key , in_dict in inputs_file_workflow .items ():
946963 val = populate_input_value (in_dict )
947- # Omit optional- null values only
964+ # Omit optional null fields only
948965 if val is None :
949966 continue
950967 yaml_inputs [key ] = val
0 commit comments