Skip to content

Commit 167fc1f

Browse files
committed
WIP: Evaluate OpenFOAM job based on param args
1 parent 54a2d0f commit 167fc1f

2 files changed

Lines changed: 129 additions & 10 deletions

File tree

dapi/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,8 @@ def generate_request(
344344
Args:
345345
app_id (str): The ID of the Tapis application to use for the job.
346346
input_dir_uri (str): Tapis URI to the input directory containing job files.
347-
script_filename (str): Name of the main script file to execute.
347+
script_filename (str, optional): Name of the main script file to execute.
348+
If None, no script parameter is added (suitable for apps like OpenFOAM).
348349
app_version (str, optional): Specific app version. If None, uses latest.
349350
job_name (str, optional): Custom job name. If None, auto-generates one.
350351
description (str, optional): Job description. If None, uses app description.

dapi/jobs.py

Lines changed: 127 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)