@@ -64,7 +64,9 @@ def generate_job_request(
6464 Creates a properly formatted job request dictionary by retrieving the specified
6565 application details and applying user-provided overrides and additional parameters.
6666 The function automatically maps the script filename (if provided) and input
67- directory to the appropriate app parameters.
67+ directory to the appropriate app parameters. It dynamically reads the app definition
68+ to detect parameter names, determines whether to use appArgs or envVariables, and
69+ automatically populates all required parameters with default values when available.
6870
6971 Args:
7072 tapis_client (Tapis): Authenticated Tapis client instance.
@@ -99,7 +101,7 @@ def generate_job_request(
99101 if script_filename is provided. Defaults to ["Input Script", "Main Script", "tclScript"].
100102 input_dir_param_name (str, optional): The 'name' of the fileInput in the Tapis app definition
101103 that corresponds to input_dir_uri. Defaults to "Input Directory".
102- CRITICAL: Must match the app's definition (e.g., "Case Directory" for OpenFOAM) .
104+ The function will auto-detect the correct name from the app definition .
103105 allocation_param_name (str, optional): Parameter name for TACC allocation.
104106 Defaults to "TACC Allocation".
105107
@@ -191,30 +193,58 @@ def generate_job_request(
191193 }
192194
193195 # --- Handle main input directory ---
194- # The 'name' of this fileInput in the job request MUST match a fileInput 'name' in the app definition.
195- # The caller is responsible for providing the correct 'input_dir_param_name'.
196+ # Automatically detect the correct input directory parameter name from app definition
196197 main_input_target_path = None
197198 main_input_automount = True
198199 found_input_def = False
200+ actual_input_param_name = input_dir_param_name # Default fallback
201+
199202 if hasattr (job_attrs , "fileInputs" ) and job_attrs .fileInputs :
203+ # First try to find exact match with provided name
200204 for fi_def in job_attrs .fileInputs :
201205 if getattr (fi_def , "name" , "" ).lower () == input_dir_param_name .lower ():
202206 main_input_target_path = getattr (fi_def , "targetPath" , None )
203207 main_input_automount = getattr (fi_def , "autoMountLocal" , True )
208+ actual_input_param_name = getattr (fi_def , "name" , "" )
204209 found_input_def = True
205210 print (
206- f"Configuring main input ' { input_dir_param_name } ' with targetPath : '{ main_input_target_path } ', autoMount: { main_input_automount } "
211+ f"Found exact match for input parameter : '{ actual_input_param_name } ' "
207212 )
208213 break
214+
215+ # If no exact match found, try to auto-detect common input directory names
216+ if not found_input_def :
217+ common_input_names = ["Input Directory" , "Case Directory" , "inputDirectory" , "inputDir" ]
218+ for fi_def in job_attrs .fileInputs :
219+ fi_name = getattr (fi_def , "name" , "" )
220+ if fi_name in common_input_names :
221+ main_input_target_path = getattr (fi_def , "targetPath" , None )
222+ main_input_automount = getattr (fi_def , "autoMountLocal" , True )
223+ actual_input_param_name = fi_name
224+ found_input_def = True
225+ print (
226+ f"Auto-detected input parameter: '{ actual_input_param_name } ' (provided: '{ input_dir_param_name } ')"
227+ )
228+ break
229+
230+ # If still not found, use the first fileInput as fallback
231+ if not found_input_def and job_attrs .fileInputs :
232+ fi_def = job_attrs .fileInputs [0 ]
233+ main_input_target_path = getattr (fi_def , "targetPath" , None )
234+ main_input_automount = getattr (fi_def , "autoMountLocal" , True )
235+ actual_input_param_name = getattr (fi_def , "name" , "" )
236+ found_input_def = True
237+ print (
238+ f"Using first available fileInput: '{ actual_input_param_name } ' (no match found for '{ input_dir_param_name } ')"
239+ )
240+
209241 if not found_input_def :
210242 print (
211- f"Warning: The provided input_dir_param_name '{ input_dir_param_name } ' was not found in the app's fileInput definitions. "
212- f"The job request will use '{ input_dir_param_name } ' as the fileInput name. "
213- f"Ensure this name is valid for the app '{ app_id } '. App fileInputs: { getattr (job_attrs , 'fileInputs' , 'Not defined' )} "
243+ f"Warning: No fileInputs found in app definition. Using provided name '{ input_dir_param_name } '"
214244 )
215245
216246 main_input_dict = {
217- "name" : input_dir_param_name , # Critical: This name must be defined in the app.
247+ "name" : actual_input_param_name , # Use the detected/matched parameter name
218248 "sourceUrl" : input_dir_uri ,
219249 "autoMountLocal" : main_input_automount ,
220250 }
@@ -282,6 +312,94 @@ def generate_job_request(
282312 else :
283313 print ("script_filename is None, skipping script parameter placement." )
284314
315+ # --- Auto-detect and add required parameters from app definition ---
316+ # Process appArgs first - add all required appArgs that aren't provided by user
317+ if hasattr (param_set_def , "appArgs" ) and param_set_def .appArgs :
318+ for app_arg_def in param_set_def .appArgs :
319+ arg_name = getattr (app_arg_def , "name" , "" )
320+ input_mode = getattr (app_arg_def , "inputMode" , "" )
321+ default_value = getattr (app_arg_def , "arg" , "" )
322+
323+ # Skip if this is the script parameter (already handled above)
324+ if script_filename and arg_name in script_param_names :
325+ continue
326+
327+ # Check if this arg is required and not already provided
328+ if input_mode == "REQUIRED" and arg_name :
329+ # Check if user already provided this arg
330+ user_provided = False
331+ if extra_app_args :
332+ for user_arg in extra_app_args :
333+ if user_arg .get ("name" ) == arg_name :
334+ user_provided = True
335+ break
336+
337+ # Also check if already added to job_req
338+ already_added = False
339+ for existing_arg in job_req ["parameterSet" ]["appArgs" ]:
340+ if existing_arg .get ("name" ) == arg_name :
341+ already_added = True
342+ break
343+
344+ if not user_provided and not already_added :
345+ if default_value :
346+ print (f"Auto-adding required appArg '{ arg_name } ' with default: '{ default_value } '" )
347+ job_req ["parameterSet" ]["appArgs" ].append ({
348+ "name" : arg_name ,
349+ "arg" : default_value
350+ })
351+ else :
352+ print (f"Warning: Required appArg '{ arg_name } ' has no default value." )
353+
354+ # Process envVariables - add all required envVariables that aren't provided by user
355+ if hasattr (param_set_def , "envVariables" ) and param_set_def .envVariables :
356+ for env_var_def in param_set_def .envVariables :
357+ var_key = getattr (env_var_def , "key" , "" )
358+ input_mode = getattr (env_var_def , "inputMode" , "" )
359+ default_value = getattr (env_var_def , "value" , "" )
360+ enum_values = getattr (env_var_def , "enum_values" , None )
361+
362+ # Skip if this is the script parameter (already handled above)
363+ if script_filename and var_key in script_param_names :
364+ continue
365+
366+ # Check if this variable is required and not already provided by user
367+ if input_mode == "REQUIRED" and var_key :
368+ # Check if user already provided this variable
369+ user_provided = False
370+ if extra_env_vars :
371+ for user_var in extra_env_vars :
372+ if user_var .get ("key" ) == var_key :
373+ user_provided = True
374+ break
375+
376+ # Also check if already added to job_req
377+ already_added = False
378+ for existing_var in job_req ["parameterSet" ]["envVariables" ]:
379+ if existing_var .get ("key" ) == var_key :
380+ already_added = True
381+ break
382+
383+ if not user_provided and not already_added :
384+ # Use default value if available
385+ value_to_use = default_value
386+
387+ # If no default but has enum values, use the first one
388+ if not value_to_use and enum_values and isinstance (enum_values , dict ):
389+ value_to_use = list (enum_values .keys ())[0 ]
390+ print (f"Auto-setting required env var '{ var_key } ' to first available option: '{ value_to_use } '" )
391+ elif value_to_use :
392+ print (f"Auto-setting required env var '{ var_key } ' to default: '{ value_to_use } '" )
393+ else :
394+ print (f"Warning: Required env var '{ var_key } ' has no default value." )
395+ continue
396+
397+ # Add to job request
398+ job_req ["parameterSet" ]["envVariables" ].append ({
399+ "key" : var_key ,
400+ "value" : value_to_use
401+ })
402+
285403 # --- Handle extra parameters ---
286404 if extra_app_args :
287405 job_req ["parameterSet" ]["appArgs" ].extend (extra_app_args )
0 commit comments