From d37e29ca14b438bf1fb53746b01f99b18e6bd638 Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Fri, 21 Nov 2025 19:07:36 +0100 Subject: [PATCH 1/4] Allow len(x) for select and data parameters with multiple="true" --- lib/galaxy/tools/evaluation.py | 1 + lib/galaxy/tools/parameters/wrapped.py | 1 + lib/galaxy/tools/wrappers.py | 23 ++++++++ test/functional/tools/multi_len.xml | 64 ++++++++++++++++++++++ test/functional/tools/multi_len_legacy.xml | 64 ++++++++++++++++++++++ test/functional/tools/sample_tool_conf.xml | 2 + 6 files changed, 155 insertions(+) create mode 100644 test/functional/tools/multi_len.xml create mode 100644 test/functional/tools/multi_len_legacy.xml diff --git a/lib/galaxy/tools/evaluation.py b/lib/galaxy/tools/evaluation.py index 595144cce6ee..5f16145f44eb 100644 --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -468,6 +468,7 @@ def wrap_input(input_values, input): if isinstance(input, DataToolParameter) and input.multiple: dataset_instances = DatasetListWrapper.to_dataset_instances(value) input_values[input.name] = DatasetListWrapper( + input, job_working_directory, dataset_instances, compute_environment=self.compute_environment, diff --git a/lib/galaxy/tools/parameters/wrapped.py b/lib/galaxy/tools/parameters/wrapped.py index 0853c3c8f2dc..61a213456a8f 100644 --- a/lib/galaxy/tools/parameters/wrapped.py +++ b/lib/galaxy/tools/parameters/wrapped.py @@ -120,6 +120,7 @@ def wrap_values(self, inputs: "ToolInputsT", input_values: dict, skip_missing_va elif isinstance(input, DataToolParameter) and input.multiple: dataset_instances = DatasetListWrapper.to_dataset_instances(value) input_values[input.name] = DatasetListWrapper( + input, None, dataset_instances, datatypes_registry=trans.app.datatypes_registry, diff --git a/lib/galaxy/tools/wrappers.py b/lib/galaxy/tools/wrappers.py index 6fd46ccc5b30..bec1065ed142 100644 --- a/lib/galaxy/tools/wrappers.py +++ b/lib/galaxy/tools/wrappers.py @@ -18,6 +18,7 @@ Union, ) +from packaging.version import Version from typing_extensions import ( Self, TypeAlias, @@ -55,6 +56,7 @@ from galaxy.tools import Tool from galaxy.tools.evaluation import ToolEvaluator from galaxy.tools.parameters.basic import ( + DataToolParameter, SelectToolParameter, ToolParameter, ) @@ -284,6 +286,11 @@ def __iter__(self) -> Iterable[str]: raise Exception("Tried to iterate over a non-multiple parameter.") return self.value.__iter__() + def __len__(self) -> int: + if not self.input.multiple: + raise Exception("Non-multiple parameter has no len().") + return self.value.__len__() + class DatasetFilenameWrapper(ToolParameterValueWrapper): """ @@ -554,6 +561,7 @@ class DatasetListWrapper(list[DatasetFilenameWrapper], ToolParameterValueWrapper def __init__( self, + input: "DataToolParameter", job_working_directory: Optional[str], datasets: Union[ Sequence[ @@ -568,6 +576,7 @@ def __init__( ], **kwargs: Any, ) -> None: + self.input = input self._dataset_elements_cache: dict[str, list[DatasetFilenameWrapper]] = {} if not isinstance(datasets, Sequence): datasets = [datasets] @@ -630,6 +639,15 @@ def __bool__(self) -> bool: # Fail `#if $param` checks in cheetah if optional input is not provided return any(self) + def __len__(self) -> int: + # optional data parameters are [None] if no input is given + # note: not self relies on the __bool__ method + profile = self.input.tool.profile if self.input.tool else None + if not self and profile is not None and Version(str(profile)) >= Version("26.0"): + return 0 + else: + return super().__len__() + __nonzero__ = __bool__ @@ -832,6 +850,11 @@ def __iter__( return [].__iter__() return self.__element_instance_list.__iter__() + # def __len__(self) -> int: + # if not self.__input_supplied: + # return 0 + # return self.__element_instance_list.__len__() + def __bool__(self) -> bool: # Fail `#if $param` checks in cheetah is optional input # not specified or if resulting collection is empty. diff --git a/test/functional/tools/multi_len.xml b/test/functional/tools/multi_len.xml new file mode 100644 index 000000000000..aa89ab29d0d1 --- /dev/null +++ b/test/functional/tools/multi_len.xml @@ -0,0 +1,64 @@ + + test len() parameters allowing multiple + '$output' && +echo 'select_ex_optional #echo len($select_ex_optional) #' >> '$output' && + +echo 'data_ex #echo len($data_ex) #' >> '$output' && +echo 'data_ex_optional #echo len($data_ex_optional) #' >> '$output' + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/multi_len_legacy.xml b/test/functional/tools/multi_len_legacy.xml new file mode 100644 index 000000000000..6f3a0c50ce81 --- /dev/null +++ b/test/functional/tools/multi_len_legacy.xml @@ -0,0 +1,64 @@ + + test len() parameters allowing multiple for 25.1 + '$output' && +echo 'select_ex_optional #echo len($select_ex_optional) #' >> '$output' && + +echo 'data_ex #echo len($data_ex) #' >> '$output' && +echo 'data_ex_optional #echo len($data_ex_optional) #' >> '$output' + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/sample_tool_conf.xml b/test/functional/tools/sample_tool_conf.xml index e1d5e36e5bcb..390a84f456a4 100644 --- a/test/functional/tools/sample_tool_conf.xml +++ b/test/functional/tools/sample_tool_conf.xml @@ -27,6 +27,8 @@ + + From eb0929bbf3bb029b15429ebdbe5d143d826a1148 Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Fri, 21 Nov 2025 19:13:56 +0100 Subject: [PATCH 2/4] remove unused function parameter unused since https://github.com/galaxyproject/galaxy/commit/c198c7cb9750e1eb1729e68ff50b44d458beb081 --- lib/galaxy/tools/evaluation.py | 4 +--- lib/galaxy/tools/parameters/wrapped.py | 2 +- lib/galaxy/tools/wrappers.py | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/tools/evaluation.py b/lib/galaxy/tools/evaluation.py index 5f16145f44eb..2259b96db4a4 100644 --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -511,9 +511,7 @@ def wrap_input(input_values, input): input, value, other_values=param_dict, compute_environment=self.compute_environment ) else: - input_values[input.name] = InputValueWrapper( - input, value, param_dict, profile=self.tool and self.tool.profile or None - ) + input_values[input.name] = InputValueWrapper(input, value, param_dict) # HACK: only wrap if check_values is not false, this deals with external # tools where the inputs don't even get passed through. These diff --git a/lib/galaxy/tools/parameters/wrapped.py b/lib/galaxy/tools/parameters/wrapped.py index 61a213456a8f..c09f184ec6d7 100644 --- a/lib/galaxy/tools/parameters/wrapped.py +++ b/lib/galaxy/tools/parameters/wrapped.py @@ -149,7 +149,7 @@ def wrap_values(self, inputs: "ToolInputsT", input_values: dict, skip_missing_va ) else: assert isinstance(input, ToolParameter) - input_values[input.name] = InputValueWrapper(input, value, incoming, tool.profile) + input_values[input.name] = InputValueWrapper(input, value, incoming) def make_dict_copy(from_dict: dict): diff --git a/lib/galaxy/tools/wrappers.py b/lib/galaxy/tools/wrappers.py index bec1065ed142..d8ff8f99a0d2 100644 --- a/lib/galaxy/tools/wrappers.py +++ b/lib/galaxy/tools/wrappers.py @@ -131,7 +131,6 @@ def __init__( input: "ToolParameter", value: Optional[str], other_values: Optional[dict[str, str]] = None, - profile: Optional[float] = None, ) -> None: self.input = input if value is None and input.type == "text": From 2c0ef346dc2384b2b6de67f685675f74cf0a0633 Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Fri, 21 Nov 2025 19:25:07 +0100 Subject: [PATCH 3/4] document profile version --- lib/galaxy/tool_util/xsd/galaxy.xsd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index cdae18ce6541..5db779e7d2a3 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -76,6 +76,10 @@ List of behavior changes associated with profile versions: - Do not use user preferences to store credentials for tools anymore. Use the new `` tag in the `` section of the tool XML instead. +### 26.0 + +- The ``len`` function applied on optional data parameters without a selected dataset gives ``0``. For smaller profiles ``1`` + ### Examples A normal tool: From 48cda08b698eee04cdb8fb2943498bad5110f313 Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Sat, 22 Nov 2025 12:05:47 +0100 Subject: [PATCH 4/4] make optional multiple data paramaters [] in case nothing is selected instead of [None] --- lib/galaxy/tool_util/xsd/galaxy.xsd | 2 +- lib/galaxy/tools/evaluation.py | 3 +- lib/galaxy/tools/parameters/wrapped.py | 3 +- lib/galaxy/tools/wrappers.py | 21 ++------------ test/functional/tools/multi_len.xml | 33 ++++++++++++++-------- test/functional/tools/multi_len_legacy.xml | 33 ++++++++++++++-------- 6 files changed, 48 insertions(+), 47 deletions(-) diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index 5db779e7d2a3..4c13022d51f4 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -78,7 +78,7 @@ List of behavior changes associated with profile versions: ### 26.0 -- The ``len`` function applied on optional data parameters without a selected dataset gives ``0``. For smaller profiles ``1`` +- Optional data parameters without a selected dataset are now empty lists, i.e. ``[]``. For smaller profiles it was ``[None]`` ### Examples diff --git a/lib/galaxy/tools/evaluation.py b/lib/galaxy/tools/evaluation.py index 2259b96db4a4..6596381eff50 100644 --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -466,9 +466,8 @@ def __populate_wrappers(self, param_dict, input_datasets, job_working_directory) def wrap_input(input_values, input): value = input_values[input.name] if isinstance(input, DataToolParameter) and input.multiple: - dataset_instances = DatasetListWrapper.to_dataset_instances(value) + dataset_instances = DatasetListWrapper.to_dataset_instances(value, self.tool.profile) input_values[input.name] = DatasetListWrapper( - input, job_working_directory, dataset_instances, compute_environment=self.compute_environment, diff --git a/lib/galaxy/tools/parameters/wrapped.py b/lib/galaxy/tools/parameters/wrapped.py index c09f184ec6d7..c8d071ecc7b9 100644 --- a/lib/galaxy/tools/parameters/wrapped.py +++ b/lib/galaxy/tools/parameters/wrapped.py @@ -118,9 +118,8 @@ def wrap_values(self, inputs: "ToolInputsT", input_values: dict, skip_missing_va values = value self.wrap_values(input.inputs, values, skip_missing_values=skip_missing_values) elif isinstance(input, DataToolParameter) and input.multiple: - dataset_instances = DatasetListWrapper.to_dataset_instances(value) + dataset_instances = DatasetListWrapper.to_dataset_instances(value, self.tool.profile) input_values[input.name] = DatasetListWrapper( - input, None, dataset_instances, datatypes_registry=trans.app.datatypes_registry, diff --git a/lib/galaxy/tools/wrappers.py b/lib/galaxy/tools/wrappers.py index d8ff8f99a0d2..10ed116adf5f 100644 --- a/lib/galaxy/tools/wrappers.py +++ b/lib/galaxy/tools/wrappers.py @@ -56,7 +56,6 @@ from galaxy.tools import Tool from galaxy.tools.evaluation import ToolEvaluator from galaxy.tools.parameters.basic import ( - DataToolParameter, SelectToolParameter, ToolParameter, ) @@ -560,7 +559,6 @@ class DatasetListWrapper(list[DatasetFilenameWrapper], ToolParameterValueWrapper def __init__( self, - input: "DataToolParameter", job_working_directory: Optional[str], datasets: Union[ Sequence[ @@ -575,7 +573,6 @@ def __init__( ], **kwargs: Any, ) -> None: - self.input = input self._dataset_elements_cache: dict[str, list[DatasetFilenameWrapper]] = {} if not isinstance(datasets, Sequence): datasets = [datasets] @@ -601,13 +598,15 @@ def to_wrapper( @staticmethod def to_dataset_instances( dataset_instance_sources: Any, + profile: Optional[float], ) -> list[Union[None, DatasetInstance]]: dataset_instances: list[Optional[DatasetInstance]] = [] if not isinstance(dataset_instance_sources, list): dataset_instance_sources = [dataset_instance_sources] for dataset_instance_source in dataset_instance_sources: if dataset_instance_source is None: - dataset_instances.append(dataset_instance_source) + if profile is None or Version(str(profile)) < Version("26.0"): + dataset_instances.append(dataset_instance_source) elif getattr(dataset_instance_source, "history_content_type", None) == "dataset": dataset_instances.append(dataset_instance_source) elif getattr(dataset_instance_source, "hda", None): @@ -638,15 +637,6 @@ def __bool__(self) -> bool: # Fail `#if $param` checks in cheetah if optional input is not provided return any(self) - def __len__(self) -> int: - # optional data parameters are [None] if no input is given - # note: not self relies on the __bool__ method - profile = self.input.tool.profile if self.input.tool else None - if not self and profile is not None and Version(str(profile)) >= Version("26.0"): - return 0 - else: - return super().__len__() - __nonzero__ = __bool__ @@ -849,11 +839,6 @@ def __iter__( return [].__iter__() return self.__element_instance_list.__iter__() - # def __len__(self) -> int: - # if not self.__input_supplied: - # return 0 - # return self.__element_instance_list.__len__() - def __bool__(self) -> bool: # Fail `#if $param` checks in cheetah is optional input # not specified or if resulting collection is empty. diff --git a/test/functional/tools/multi_len.xml b/test/functional/tools/multi_len.xml index aa89ab29d0d1..1b38d8768502 100644 --- a/test/functional/tools/multi_len.xml +++ b/test/functional/tools/multi_len.xml @@ -1,12 +1,18 @@ test len() parameters allowing multiple '$output' && -echo 'select_ex_optional #echo len($select_ex_optional) #' >> '$output' && +echo '|select_ex| = #echo len($select_ex) #' > '$output' && +echo '|select_ex_optional| = #echo len($select_ex_optional) #' >> '$output' && +#for i, e in enumerate($select_ex_optional) + && echo "select_ex_optional: element $i is $e" +#end for -echo 'data_ex #echo len($data_ex) #' >> '$output' && -echo 'data_ex_optional #echo len($data_ex_optional) #' >> '$output' +echo '|data_ex| = #echo len($data_ex) #' >> '$output' && +echo '|data_ex_optional| = #echo len($data_ex_optional) #' >> '$output' +#for i, e in enumerate($data_ex_optional) + && echo "data_ex_optional: element $i is $e" +#end for ]]> @@ -37,10 +43,10 @@ echo 'data_ex_optional #echo len($data_ex_optional) #' >> '$output' - - - - + + + + @@ -51,10 +57,13 @@ echo 'data_ex_optional #echo len($data_ex_optional) #' >> '$output' - - - - + + + + + + + diff --git a/test/functional/tools/multi_len_legacy.xml b/test/functional/tools/multi_len_legacy.xml index 6f3a0c50ce81..68f55d5e70d9 100644 --- a/test/functional/tools/multi_len_legacy.xml +++ b/test/functional/tools/multi_len_legacy.xml @@ -1,11 +1,17 @@ test len() parameters allowing multiple for 25.1 '$output' && -echo 'select_ex_optional #echo len($select_ex_optional) #' >> '$output' && +echo '|select_ex| = #echo len($select_ex) #' > '$output' && +echo '|select_ex_optional| = #echo len($select_ex_optional) #' >> '$output' && +#for i, e in enumerate($select_ex_optional) + && echo "select_ex_optional: element $i is $e" +#end for -echo 'data_ex #echo len($data_ex) #' >> '$output' && -echo 'data_ex_optional #echo len($data_ex_optional) #' >> '$output' +echo '|data_ex| = #echo len($data_ex) #' >> '$output' && +echo '|data_ex_optional| = #echo len($data_ex_optional) #' >> '$output' +#for i, e in enumerate($data_ex_optional) + && echo "data_ex_optional: element $i is $e" +#end for ]]> @@ -37,10 +43,10 @@ echo 'data_ex_optional #echo len($data_ex_optional) #' >> '$output' - - - - + + + + @@ -51,10 +57,13 @@ echo 'data_ex_optional #echo len($data_ex_optional) #' >> '$output' - - - - + + + + + + +