diff --git a/lib/galaxy/managers/context.py b/lib/galaxy/managers/context.py index 24c29567a80b..4d55fcffbadb 100644 --- a/lib/galaxy/managers/context.py +++ b/lib/galaxy/managers/context.py @@ -39,10 +39,13 @@ import string from json import dumps from typing import ( + Any, Callable, cast, + Dict, List, Optional, + Tuple, ) from sqlalchemy import select @@ -204,6 +207,13 @@ class ProvidesUserContext(ProvidesAppContext): galaxy_session: Optional[GalaxySession] = None _tag_handler: Optional[GalaxyTagHandlerSession] = None + _short_term_cache: Dict[Tuple[str, ...], Any] + + def set_cache_value(self, args: Tuple[str, ...], value: Any): + self._short_term_cache[args] = value + + def get_cache_value(self, args: Tuple[str, ...], default: Any = None) -> Any: + return self._short_term_cache.get(args, default) @property def tag_handler(self): diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index d1f50b945c28..1fae939dd5d1 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -3670,6 +3670,17 @@ def element_is_valid(element: model.DatasetCollectionElement): return False +class FilterNullTool(FilterDatasetsTool): + tool_type = "filter_null" + require_dataset_ok = True + + @staticmethod + def element_is_valid(element: model.DatasetCollectionElement): + element_object = element.element_object + assert isinstance(element_object, model.DatasetInstance) + return element_object.extension == "expression.json" and element_object.blurb == "skipped" + + class FlattenTool(DatabaseOperationTool): tool_type = "flatten_collection" require_terminal_states = False diff --git a/lib/galaxy/tools/parameters/dynamic_options.py b/lib/galaxy/tools/parameters/dynamic_options.py index b8cd20dab37d..e354c93eabb5 100644 --- a/lib/galaxy/tools/parameters/dynamic_options.py +++ b/lib/galaxy/tools/parameters/dynamic_options.py @@ -286,14 +286,12 @@ def get_dependency_name(self): return self.ref_name def filter_options(self, options, trans, other_values): - if trans is not None and trans.workflow_building_mode: - return [] ref = other_values.get(self.ref_name, None) - if ref is None: + if ref is None or is_runtime_value(ref): ref = [] # - for HDCAs the list of contained HDAs is extracted - # - single values are transformed in a single eleent list + # - single values are transformed in a single element list # - remaining cases are already lists (select and data parameters with multiple=true) if isinstance(ref, HistoryDatasetCollectionAssociation): ref = ref.to_hda_representative(multiple=True) @@ -835,6 +833,9 @@ def get_field_by_name_for_value(self, field_name, value, trans, other_values): return rval def get_options(self, trans, other_values): + + rval = [] + def to_triple(values): if len(values) == 2: return [str(values[0]), str(values[1]), False] @@ -877,8 +878,7 @@ def to_triple(values): data = [] # We only support the very specific ["name", "value", "selected"] format for now. - return [to_triple(d) for d in data] - rval = [] + rval = [to_triple(d) for d in data] if ( self.file_fields is not None or self.tool_data_table is not None diff --git a/lib/galaxy/webapps/base/webapp.py b/lib/galaxy/webapps/base/webapp.py index 822e5b7ec236..75a14f04e301 100644 --- a/lib/galaxy/webapps/base/webapp.py +++ b/lib/galaxy/webapps/base/webapp.py @@ -14,6 +14,7 @@ Any, Dict, Optional, + Tuple, ) from urllib.parse import urlparse @@ -325,6 +326,7 @@ def __init__( self.galaxy_session = None self.error_message = None self.host = self.request.host + self._short_term_cache: Dict[Tuple[str, ...], Any] = {} # set any cross origin resource sharing headers if configured to do so self.set_cors_headers() diff --git a/lib/galaxy/work/context.py b/lib/galaxy/work/context.py index 8e4fc74afc21..ec1146adc102 100644 --- a/lib/galaxy/work/context.py +++ b/lib/galaxy/work/context.py @@ -49,12 +49,6 @@ def __init__( self.workflow_building_mode = workflow_building_mode self.galaxy_session = galaxy_session - def set_cache_value(self, args: Tuple[str, ...], value: Any): - self._short_term_cache[args] = value - - def get_cache_value(self, args: Tuple[str, ...], default: Any = None) -> Any: - return self._short_term_cache.get(args, default) - @property def app(self): return self._app diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index a5fadafeb28a..2cdef868d4ee 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -1414,7 +1414,7 @@ def restrict_options(self, step, connections: Iterable[WorkflowStepConnection], def callback(input, prefixed_name, context, **kwargs): if prefixed_name == connection.input_name and hasattr(input, "get_options"): # noqa: B023 - static_options.append(input.get_options(self.trans, {})) + static_options.append(input.get_options(self.trans, context)) visit_input_values(tool_inputs, module.state.inputs, callback) elif isinstance(module, SubWorkflowModule): diff --git a/lib/galaxy_test/api/test_workflow_build_module.py b/lib/galaxy_test/api/test_workflow_build_module.py new file mode 100644 index 000000000000..546f6192e397 --- /dev/null +++ b/lib/galaxy_test/api/test_workflow_build_module.py @@ -0,0 +1,19 @@ +from galaxy_test.base.populators import ( + skip_without_tool, + WorkflowPopulator, +) +from ._framework import ApiTestCase + + +class TestBuildWorkflowModule(ApiTestCase): + + def setUp(self): + super().setUp() + self.workflow_populator = WorkflowPopulator(self.galaxy_interactor) + + @skip_without_tool("select_from_url") + def test_build_module_filter_dynamic_select(self): + # Verify that filtering on parameters that depend on parameter and validators works + # fine in workflow building mode. + module = self.workflow_populator.build_module(step_type="tool", content_id="select_from_url") + assert not module["errors"], module["errors"] diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py index 014880ebd1e6..bd5feaa01592 100644 --- a/lib/galaxy_test/base/populators.py +++ b/lib/galaxy_test/base/populators.py @@ -2298,6 +2298,12 @@ def import_tool(self, tool) -> Dict[str, Any]: assert upload_response.status_code == 200, upload_response return upload_response.json() + def build_module(self, step_type: str, content_id: Optional[str] = None, inputs: Optional[Dict[str, Any]] = None): + payload = {"inputs": inputs or {}, "type": step_type, "content_id": content_id} + response = self._post("workflows/build_module", data=payload, json=True) + assert response.status_code == 200, response + return response.json() + def _import_tool_response(self, tool) -> Response: using_requirement("admin") tool_str = json.dumps(tool, indent=4) diff --git a/test/functional/tools/select_from_url.xml b/test/functional/tools/select_from_url.xml index 60a712e74631..488055677e85 100644 --- a/test/functional/tools/select_from_url.xml +++ b/test/functional/tools/select_from_url.xml @@ -10,6 +10,13 @@ echo '$url_param_value_header_and_body' > '$param_value_header_and_body' + + + + + + +