From f8decd4785916871874481ff8d74115934ee439d Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 30 Jan 2026 14:28:17 -0500 Subject: [PATCH] WIP: allow collection inputs to YAML tools. --- lib/galaxy/tool_util/parser/yaml.py | 20 +++++++-- lib/galaxy/tool_util/verify/interactor.py | 3 ++ .../tools/collection_paired_test_y.yml | 41 +++++++++++++++++++ test/functional/tools/sample_tool_conf.xml | 1 + .../tool_util/test_test_definition_parsing.py | 24 +++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 test/functional/tools/collection_paired_test_y.yml diff --git a/lib/galaxy/tool_util/parser/yaml.py b/lib/galaxy/tool_util/parser/yaml.py index 5702896a459..c56119f14b5 100644 --- a/lib/galaxy/tool_util/parser/yaml.py +++ b/lib/galaxy/tool_util/parser/yaml.py @@ -8,6 +8,7 @@ from typing import ( List, Optional, Tuple, + Union, ) import packaging.version @@ -34,6 +35,7 @@ from galaxy.tool_util_models.tool_source import ( HelpContent, XrefDict, YamlTemplateConfigFile, + JsonTestCollectionDefDict, ) from galaxy.util import listify from .interface import ( @@ -44,6 +46,8 @@ from .interface import ( PagesSource, ToolSource, ToolSourceTest, + ToolSourceTestInput, + ToolSourceTestInputs, ToolSourceTests, ) from .output_actions import ToolOutputActionApp @@ -358,14 +362,24 @@ class YamlToolSource(ToolSource): return json.dumps(self.root_dict, ensure_ascii=False, sort_keys=False) -def _parse_test(i, test_dict) -> ToolSourceTest: - inputs = test_dict["inputs"] +def __parse_test_inputs(i: int, test_inputs: Union[list, dict]) -> ToolSourceTestInputs: + inputs = test_inputs if is_dict(inputs): new_inputs = [] for key, value in inputs.items(): new_inputs.append({"name": key, "value": value, "attributes": {}}) - test_dict["inputs"] = new_inputs + inputs = new_inputs + for input in inputs: + if is_dict(input["value"]) and "class" in input["value"] and input["value"]["class"] == "Collection": + collection_def = JsonTestCollectionDefDict(input["value"]) + attrib = input.setdefault("attributes", {}) + attrib["collection"] = collection_def + return inputs + + +def _parse_test(i: int, test_dict: dict) -> ToolSourceTest: + test_dict["inputs"] = __parse_test_inputs(i, deepcopy(test_dict["inputs"])) outputs = test_dict["outputs"] new_outputs = [] diff --git a/lib/galaxy/tool_util/verify/interactor.py b/lib/galaxy/tool_util/verify/interactor.py index c1085e0f308..74223e0a2b5 100644 --- a/lib/galaxy/tool_util/verify/interactor.py +++ b/lib/galaxy/tool_util/verify/interactor.py @@ -2001,6 +2001,9 @@ def expanded_inputs_from_json(expanded_inputs_json: ExpandedToolInputsJsonified) if isinstance(value, dict) and value.get("model_class"): collection_def_dict = cast(XmlTestCollectionDefDict, value) loaded_inputs[key] = TestCollectionDef.from_dict(collection_def_dict) + elif isinstance(value, dict) and value.get("class") == "Collection": + collection_def_dict = cast(JsonTestCollectionDefDict, value) + loaded_inputs[key] = TestCollectionDef.from_dict(collection_def_dict) else: loaded_inputs[key] = value return loaded_inputs diff --git a/test/functional/tools/collection_paired_test_y.yml b/test/functional/tools/collection_paired_test_y.yml new file mode 100644 index 00000000000..9cfdd751528 --- /dev/null +++ b/test/functional/tools/collection_paired_test_y.yml @@ -0,0 +1,41 @@ +class: GalaxyTool +id: collection_paired_test_y +name: collection_paired_test_y +version: "0.1.0" +container: busybox +shell_command: | + cat '$(inputs.f1.forward.path)' '$(inputs.f1.reverse.path)' >> out1.txt + echo 'Collection name: $(inputs.f1.name)' +inputs: + - name: f1 + type: data_collection + collection_type: paired + label: Input pair +outputs: + - name: out1 + type: data + format: txt + from_work_dir: out1.txt +tests: + - inputs: + f1: + class: Collection + collection_type: paired + name: collection name + elements: + - identifier: forward + class: File + path: simple_line.txt + - identifier: reverse + class: File + path: simple_line_alternative.txt + outputs: + out1: + asserts: + - has_line: + line: This is a line of text. + - has_line: + line: This is a different line of text. + assert_stdout: + - has_line: + line: "Collection name: collection name" diff --git a/test/functional/tools/sample_tool_conf.xml b/test/functional/tools/sample_tool_conf.xml index ce4830ea106..dd385053adc 100644 --- a/test/functional/tools/sample_tool_conf.xml +++ b/test/functional/tools/sample_tool_conf.xml @@ -186,6 +186,7 @@ + diff --git a/test/unit/tool_util/test_test_definition_parsing.py b/test/unit/tool_util/test_test_definition_parsing.py index 77cda95c737..c49b1a44347 100644 --- a/test/unit/tool_util/test_test_definition_parsing.py +++ b/test/unit/tool_util/test_test_definition_parsing.py @@ -65,6 +65,18 @@ RECORD_TWO_FILES_EXPECTATIONS = [ (["inputs", "f1", "fields", 0, "name"], "parent"), (["inputs", "f1", "fields", 1, "type"], "File"), ] +COLLECTION_TEST_PAIRED_EXPECTATIONS = [ + (["inputs", "f1", "model_class"], "TestCollectionDef"), + (["inputs", "f1", "collection_type"], "paired"), + (["required_files", 0, 0], "simple_line.txt"), + (["required_files", 1, 0], "simple_line_alternative.txt"), +] +COLLECTION_TEST_PAIRED_Y_EXPECTATIONS = [ + (["inputs", "f1", "model_class"], "TestCollectionDef"), + (["inputs", "f1", "collection_type"], "paired"), + (["required_files", 0, 0], "simple_line.txt"), + (["required_files", 1, 0], "simple_line_alternative.txt"), +] class TestTestParsing(TestCase): @@ -131,6 +143,18 @@ class TestTestParsing(TestCase): test_0 = test_dicts[0].to_dict() self._verify_each(test_0, RECORD_TWO_FILES_EXPECTATIONS) + def test_collection_inputs(self): + self._init_tool_for_path(functional_test_tool_path("collection_paired_test.xml")) + test_dicts = self._parse_tests() + test_0 = test_dicts[0].to_dict() + self._verify_each(test_0, COLLECTION_TEST_PAIRED_EXPECTATIONS) + + def test_collection_inputs_from_yaml(self): + self._init_tool_for_path(functional_test_tool_path("collection_paired_test_y.yml")) + test_dicts = self._parse_tests() + test_0 = test_dicts[0].to_dict() + self._verify_each(test_0, COLLECTION_TEST_PAIRED_Y_EXPECTATIONS) + def test_bigwigtowig_converter(self): # defines if in_packages():