Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions src/workflow/CommandExecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ def run_topp(self, tool: str, input_output: dict, custom_params: dict = {}) -> b

# Load parameters for non-defaults
params = self.parameter_manager.get_parameters_from_json()

# NEW LOGIC: Ask the ParameterManager which keys are strictly booleans
bool_params = self.parameter_manager.get_boolean_parameters(tool)

# Construct commands for each process
for i in range(n_processes):
command = [tool]
Expand All @@ -281,27 +285,38 @@ def run_topp(self, tool: str, input_output: dict, custom_params: dict = {}) -> b
# standard case, files was a list of strings, take the file name at index
else:
command += [value[i]]

# Add non-default TOPP tool parameters
if tool in params.keys():
for k, v in params[tool].items():
# NEW LOGIC: Intercept boolean flags
if k in bool_params:
# Only add the implicit flag if True. If False, omit entirely.
if str(v).lower() == "true":
command += [f"-{k}"]
else:
# Existing logic for strings, ints, floats
command += [f"-{k}"]
if v != "" and v is not None:
if isinstance(v, str) and "\n" in v:
command += v.split("\n")
else:
command += [str(v)]

# Add custom parameters
for k, v in custom_params.items():
# NEW LOGIC: Intercept custom boolean flags
if k in bool_params:
if str(v).lower() == "true":
command += [f"-{k}"]
else:
command += [f"-{k}"]
# Skip only empty strings (pass flag with no value)
# Note: 0 and 0.0 are valid values, so use explicit check
if v != "" and v is not None:
if isinstance(v, str) and "\n" in v:
command += v.split("\n")
if isinstance(v, list):
command += [str(x) for x in v]
else:
command += [str(v)]
# Add custom parameters
for k, v in custom_params.items():
command += [f"-{k}"]
# Skip only empty strings (pass flag with no value)
# Note: 0 and 0.0 are valid values, so use explicit check
if v != "" and v is not None:
if isinstance(v, list):
command += [str(x) for x in v]
else:
command += [str(v)]

# Add threads parameter for TOPP tools
command += ["-threads", str(threads_per_command)]
commands.append(command)
Expand Down
57 changes: 56 additions & 1 deletion src/workflow/ParameterManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import shutil
import subprocess
import streamlit as st
import xml.etree.ElementTree as ET
from pathlib import Path

class ParameterManager:
Expand Down Expand Up @@ -287,4 +288,58 @@ def clear_parameter_session_state(self) -> None:
if key.startswith(self.param_prefix) or key.startswith(self.topp_param_prefix)
]
for key in keys_to_delete:
del st.session_state[key]
del st.session_state[key]

def get_boolean_parameters(self, tool: str) -> list:
"""
Parses the tool's .ini XML file to find all parameters explicitly defined as type 'bool'.
This bypasses the pyOpenMS internal mapping which loses the boolean type definition.

Args:
tool: Name of the TOPP tool (e.g., "FeatureFinderMetabo")

Returns:
list: A list of parameter names (strings) that are strictly booleans,
including their full hierarchical paths (e.g., 'algorithm:epd:masstrace_snr_filtering').
"""
# SELF-HEALING FIX: Force generate the .ini file if it doesn't exist yet
if not self.create_ini(tool):
return []

ini_path = Path(self.ini_dir, f"{tool}.ini")

bool_params = []
try:
tree = ET.parse(ini_path)
root = tree.getroot()

# Recursive function to build the hierarchical path from XML nodes
def traverse(node, current_path):
for child in node:
if child.tag == "ITEM" and child.get("type") == "bool":
name = child.get("name")
if name:
bool_params.append(current_path + name)
elif child.tag == "NODE":
name = child.get("name")
if name:
# Append the node name and a colon to the path
traverse(child, current_path + name + ":")

# Start traversal from the XML root
traverse(root, "")

# OpenMS INI files usually encapsulate everything in a top-level <NODE name="ToolName">.
# We must strip this prefix so the keys perfectly match the JSON session state keys.
tool_prefix = f"{tool}:1:"
cleaned_params = [
p[len(tool_prefix):] if p.startswith(tool_prefix) else p
for p in bool_params
]

return cleaned_params
Comment thread
coderabbitai[bot] marked this conversation as resolved.

except Exception as e:
print(f"Error parsing boolean parameters for {tool}: {e}")
pass # Safely return empty list if XML parsing fails
return []
Loading