From 8700999153a355746ddb7737d2ce8f878a91a582 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 8 Apr 2026 20:12:06 +0200 Subject: [PATCH] Allow batch-wrapped HDCA on single data param in from_json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HDCA rejection added in b3f6dd71a7 (#22401) only considered the execution path, where ``expand_meta_parameters`` unwraps ``{"batch": true, "values": [...]}`` before ``check_param`` runs. Tool form building (``/api/tools/{id}/build``) takes a different path: ``populate_state`` → ``check_param`` → ``from_json`` receives the raw batch wrapper, which ``to_python`` then unwraps to an HDCA, tripping the new rejection and returning a 400. That breaks the form re-render every time a user picks a paired collection for a non-multiple ``data`` parameter in the UI. Remember whether the incoming value was a batch wrapper and, when it was, return the HDCA instead of raising. Execution is unaffected because the wrapper never reaches ``from_json`` there. --- lib/galaxy/tools/parameters/basic.py | 17 ++++++++++++++--- lib/galaxy_test/api/test_tools.py | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index 65d2a7c8767..7ae51e0d472 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -2163,7 +2163,9 @@ class DataToolParameter(BaseDataToolParameter): if self.default_object: return raw_to_galaxy(trans.app, trans.history, self.default_object) return None + batch_wrapper = False if isinstance(value, MutableMapping) and "values" in value: + batch_wrapper = bool(value.get("batch")) value = self.to_python(value, trans.app) if isinstance(value, str) and value.find(",") > 0: value = [int(value_part) for value_part in value.split(",")] @@ -2277,15 +2279,24 @@ class DataToolParameter(BaseDataToolParameter): if len(rval) > 0: single_value = rval[0] if isinstance(single_value, HistoryDatasetCollectionAssociation): + if batch_wrapper: + # A batch wrapper ({"batch": true, "values": [...]}) + # only reaches ``from_json`` during tool form building + # (``/api/tools/{id}/build``); at execution time + # ``expand_meta_parameters`` has already replaced the + # wrapper with per-element values. Returning the HDCA + # lets the form re-render and preserves the user's + # map-over selection. + return single_value # A non-multiple data parameter cannot reduce a dataset # collection. Without this check the HDCA is silently # accepted here, then ``collect_input_dataset_collections`` # rewrites the state to a list of the collection's HDAs and # ``wrap_values`` later crashes with a raw ``TypeError`` # (https://github.com/galaxyproject/galaxy/issues/22401). - # Map-over remains supported via ``{"batch": true, ...}``, - # which is expanded to per-element jobs before reaching - # ``from_json``. + # Map-over is still supported via ``{"batch": true, ...}``; + # at execution time that wrapper is expanded to per-element + # jobs before reaching ``from_json``. raise ParameterValueError( "dataset collection supplied to single input dataset parameter; " "to run the tool over each element of the collection, use the map-over option", diff --git a/lib/galaxy_test/api/test_tools.py b/lib/galaxy_test/api/test_tools.py index 5c69b534585..360e6a0de79 100644 --- a/lib/galaxy_test/api/test_tools.py +++ b/lib/galaxy_test/api/test_tools.py @@ -2510,6 +2510,23 @@ class TestToolsApi(ApiTestCase, TestsTools): collection.assert_has_dataset_element("forward").with_contents_stripped("forward") collection.assert_has_dataset_element("reverse").with_contents_stripped("reverse") + @skip_without_tool("identifier_in_conditional") + def test_hdca_accepted_via_batch_for_single_data_param_in_conditional(self, history_id): + # Regression test: tool form building (/api/tools/{id}/build) must + # accept a batch-wrapped HDCA for a non-multiple ``data`` parameter. + # The reject-HDCA check added for + # https://github.com/galaxyproject/galaxy/issues/22401 only applies at + # execution time; at build time the batch wrapper has not yet been + # expanded and reaches ``DataToolParameter.from_json`` intact. + hdca_id = self._build_pair(history_id, ["123", "456"]) + inputs = { + "outer_cond|multi_input": False, + "outer_cond|input1": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]}, + } + # ``build_tool_state`` calls ``raise_for_status`` internally, so any + # non-2xx response (e.g. the 400 this test guards against) fails loudly. + self.dataset_populator.build_tool_state("identifier_in_conditional", history_id, inputs) + @skip_without_tool("identifier_in_conditional") def test_hdca_rejected_for_single_data_param_in_conditional(self, history_id): # Regression test for https://github.com/galaxyproject/galaxy/issues/22401 .