From 918652e6c9f5f3f603c056efe6e5be29fd39786a Mon Sep 17 00:00:00 2001 From: John Chilton Date: Sat, 21 Sep 2024 17:37:09 -0400 Subject: [PATCH] Rebase... --- lib/galaxy/tool_util/parameters/__init__.py | 4 +- lib/galaxy/tool_util/parameters/convert.py | 7 +- lib/galaxy/tool_util/parameters/factory.py | 30 ++++---- lib/galaxy/tool_util/parser/interface.py | 4 +- lib/galaxy/tool_util/verify/interactor.py | 3 +- lib/galaxy/tools/__init__.py | 11 +-- lib/galaxy/tools/parameters/__init__.py | 52 +++++++++++++ test/functional/tools/select_optional.xml | 1 + test/unit/tool_util/test_parameter_convert.py | 73 ++++++++++--------- 9 files changed, 123 insertions(+), 62 deletions(-) diff --git a/lib/galaxy/tool_util/parameters/__init__.py b/lib/galaxy/tool_util/parameters/__init__.py index 39f515c4adb2..4a54ba0a8373 100644 --- a/lib/galaxy/tool_util/parameters/__init__.py +++ b/lib/galaxy/tool_util/parameters/__init__.py @@ -4,7 +4,7 @@ dereference, encode, encode_test, - fill_defaults, + fill_static_defaults, ) from .factory import ( from_input_source, @@ -138,7 +138,7 @@ "decode", "encode", "encode_test", - "fill_defaults", + "fill_static_defaults", "dereference", "WorkflowStepToolState", "WorkflowStepLinkedToolState", diff --git a/lib/galaxy/tool_util/parameters/convert.py b/lib/galaxy/tool_util/parameters/convert.py index 0c312368be6e..cfde8f06dbf5 100644 --- a/lib/galaxy/tool_util/parameters/convert.py +++ b/lib/galaxy/tool_util/parameters/convert.py @@ -240,8 +240,8 @@ def encode_callback(parameter: ToolParameterT, value: Any): return request_state -def fill_defaults( - tool_state: Dict[str, Any], input_models: ToolParameterBundle, partial: bool = True +def fill_static_defaults( + tool_state: Dict[str, Any], input_models: ToolParameterBundle, profile: str, partial: bool = True ) -> Dict[str, Any]: """If additional defaults might stem from Galaxy runtime, partial should be true. @@ -269,7 +269,8 @@ def _fill_default_for(tool_state: Dict[str, Any], parameter: ToolParameterT) -> # even optional parameters default to false if not in the body of the request :_( # see test_tools.py -> expression_null_handling_boolean or test cases for gx_boolean_optional.xml tool_state[parameter_name] = boolean.value or False - if parameter_type in ["gx_integer", "gx_float", "gx_hidden", "gx_data_column"]: + + if parameter_type in ["gx_integer", "gx_float", "gx_hidden"]: has_value_parameter = cast( Union[ IntegerParameterModel, diff --git a/lib/galaxy/tool_util/parameters/factory.py b/lib/galaxy/tool_util/parameters/factory.py index 0b54aad81500..dbf8c828fdf9 100644 --- a/lib/galaxy/tool_util/parameters/factory.py +++ b/lib/galaxy/tool_util/parameters/factory.py @@ -64,7 +64,7 @@ def get_color_value(input_source: InputSource) -> str: return input_source.get("value", "#000000") -def _from_input_source_galaxy(input_source: InputSource) -> ToolParameterT: +def _from_input_source_galaxy(input_source: InputSource, profile: str) -> ToolParameterT: input_type = input_source.parse_input_type() if input_type == "param": param_type = input_source.get("type") @@ -180,8 +180,8 @@ def _from_input_source_galaxy(input_source: InputSource) -> ToolParameterT: multiple = input_source.get_bool("multiple", False) optional = input_source.parse_optional() value = input_source.parse_data_column_value() - accept_default = ( - input_source.get("accept_default", False) or not multiple + accept_default = input_source.get("accept_default", False) or ( + not multiple and profile < "20.01" ) # wild but that is what the tests say I think if value is None and accept_default: if multiple: @@ -224,7 +224,8 @@ def _from_input_source_galaxy(input_source: InputSource) -> ToolParameterT: elif input_type == "conditional": test_param_input_source = input_source.parse_test_input_source() test_parameter = cast( - Union[BooleanParameterModel, SelectParameterModel], _from_input_source_galaxy(test_param_input_source) + Union[BooleanParameterModel, SelectParameterModel], + _from_input_source_galaxy(test_param_input_source, profile), ) whens = [] default_test_value = cond_test_parameter_default_value(test_parameter) @@ -235,7 +236,7 @@ def _from_input_source_galaxy(input_source: InputSource) -> ToolParameterT: else: typed_value = value - tool_parameter_models = input_models_for_page(case_inputs_sources) + tool_parameter_models = input_models_for_page(case_inputs_sources, profile) is_default_when = False if typed_value == default_test_value: is_default_when = True @@ -254,7 +255,7 @@ def _from_input_source_galaxy(input_source: InputSource) -> ToolParameterT: # title = input_source.get("title") # help = input_source.get("help", None) instance_sources = input_source.parse_nested_inputs_source() - instance_tool_parameter_models = input_models_for_page(instance_sources) + instance_tool_parameter_models = input_models_for_page(instance_sources, profile) min_raw = input_source.get("min", None) max_raw = input_source.get("max", None) min = int(min_raw) if min_raw is not None else None @@ -268,7 +269,7 @@ def _from_input_source_galaxy(input_source: InputSource) -> ToolParameterT: elif input_type == "section": name = input_source.get("name") instance_sources = input_source.parse_nested_inputs_source() - instance_tool_parameter_models = input_models_for_page(instance_sources) + instance_tool_parameter_models = input_models_for_page(instance_sources, profile) return SectionParameterModel( name=name, parameters=instance_tool_parameter_models, @@ -341,34 +342,35 @@ def tool_parameter_bundle_from_json(json: Dict[str, Any]) -> ToolParameterBundle def input_models_for_tool_source(tool_source: ToolSource) -> ToolParameterBundleModel: pages = tool_source.parse_input_pages() - return ToolParameterBundleModel(parameters=input_models_for_pages(pages)) + profile = tool_source.parse_profile() + return ToolParameterBundleModel(parameters=input_models_for_pages(pages, profile)) -def input_models_for_pages(pages: PagesSource) -> List[ToolParameterT]: +def input_models_for_pages(pages: PagesSource, profile: str) -> List[ToolParameterT]: input_models = [] if pages.inputs_defined: for page_source in pages.page_sources: - input_models.extend(input_models_for_page(page_source)) + input_models.extend(input_models_for_page(page_source, profile)) return input_models -def input_models_for_page(page_source: PageSource) -> List[ToolParameterT]: +def input_models_for_page(page_source: PageSource, profile: str) -> List[ToolParameterT]: input_models = [] for input_source in page_source.parse_input_sources(): input_type = input_source.parse_input_type() if input_type == "display": # not a real input... just skip this. Should this be handled in the parser layer better? continue - tool_parameter_model = from_input_source(input_source) + tool_parameter_model = from_input_source(input_source, profile) input_models.append(tool_parameter_model) return input_models -def from_input_source(input_source: InputSource) -> ToolParameterT: +def from_input_source(input_source: InputSource, profile: str) -> ToolParameterT: tool_parameter: ToolParameterT if isinstance(input_source, CwlInputSource): tool_parameter = _from_input_source_cwl(input_source) else: - tool_parameter = _from_input_source_galaxy(input_source) + tool_parameter = _from_input_source_galaxy(input_source, profile) return tool_parameter diff --git a/lib/galaxy/tool_util/parser/interface.py b/lib/galaxy/tool_util/parser/interface.py index 05ed78709237..9af3202e9a43 100644 --- a/lib/galaxy/tool_util/parser/interface.py +++ b/lib/galaxy/tool_util/parser/interface.py @@ -706,7 +706,9 @@ def to_element(xml_element_dict: "TestCollectionDefElementInternal") -> "JsonTes identifier=identifier, **element_object._test_format_to_dict() ) else: - input_as_dict: Optional[JsonTestDatasetDefDict] = xml_data_input_to_json(cast(ToolSourceTestInput, element_object)) + input_as_dict: Optional[JsonTestDatasetDefDict] = xml_data_input_to_json( + cast(ToolSourceTestInput, element_object) + ) if input_as_dict is not None: as_dict = JsonTestCollectionDefDatasetElementDict( identifier=identifier, diff --git a/lib/galaxy/tool_util/verify/interactor.py b/lib/galaxy/tool_util/verify/interactor.py index 09ba69785531..bf34ced4f857 100644 --- a/lib/galaxy/tool_util/verify/interactor.py +++ b/lib/galaxy/tool_util/verify/interactor.py @@ -709,7 +709,8 @@ def adapt_collections(test_input: JsonTestCollectionDefDict) -> DataCollectionRe if not successful: request = self.get_tool_request(tool_request_id) or {} raise RunToolException( - f"Tool request failure - state {request.get('state')}, message: {request.get('state_message')}", inputs_tree + f"Tool request failure - state {request.get('state')}, message: {request.get('state_message')}", + inputs_tree, ) jobs = self.jobs_for_tool_request(tool_request_id) outputs = OutputsDict() diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index e7606d34ac09..ddc5fb6f2606 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -73,7 +73,7 @@ ) from galaxy.tool_util.output_checker import DETECTED_JOB_STATE from galaxy.tool_util.parameters import ( - fill_defaults, + fill_static_defaults, input_models_for_pages, JobInternalToolState, RequestInternalDereferencedToolState, @@ -127,6 +127,7 @@ from galaxy.tools.imp_exp import JobImportHistoryArchiveWrapper from galaxy.tools.parameters import ( check_param, + fill_dynamic_defaults, params_from_strings, params_to_incoming, params_to_json, @@ -1439,7 +1440,7 @@ def parse_inputs(self, tool_source: ToolSource): pages = tool_source.parse_input_pages() enctypes: Set[str] = set() try: - parameters = input_models_for_pages(pages) + parameters = input_models_for_pages(pages, tool_source.parse_profile()) self.parameters = parameters except Exception: pass @@ -1874,10 +1875,10 @@ def expand_incoming_async( all_params: List[ToolStateJobInstancePopulatedT] = [] internal_states: List[JobInternalToolState] = [] for expanded_incoming in job_tool_states: - expanded_incoming = fill_defaults(expanded_incoming, self) + expanded_incoming = fill_static_defaults(expanded_incoming, self, self.profile) + fill_dynamic_defaults(request_context, self.inputs, expanded_incoming) internal_tool_state = JobInternalToolState(expanded_incoming) - # internal_tool_state.validate(self) - # some defaults might not be available until populate runs... + internal_tool_state.validate(self) internal_states.append(internal_tool_state) params, errors = self._populate(request_context, expanded_incoming, "21.01") diff --git a/lib/galaxy/tools/parameters/__init__.py b/lib/galaxy/tools/parameters/__init__.py index 25d6775446f9..327ad7923695 100644 --- a/lib/galaxy/tools/parameters/__init__.py +++ b/lib/galaxy/tools/parameters/__init__.py @@ -688,6 +688,58 @@ def _populate_state_legacy( state[input.name] = value +def fill_dynamic_defaults( + request_context, + inputs: ToolInputsT, + incoming: ToolStateJobInstanceT, +): + """ + Expands incoming parameters with default values. + """ + for input in inputs.values(): + if input.type == "repeat": + repeat_input = cast(Repeat, input) + for rep in incoming[repeat_input.name]: + fill_dynamic_defaults( + request_context, + repeat_input.inputs, + rep, + ) + + elif input.type == "conditional": + conditional_input = cast(Conditional, input) + test_param = cast(ToolParameter, conditional_input.test_param) + test_param_value = incoming.get(conditional_input.name, {}).get(test_param.name) + try: + current_case = conditional_input.get_current_case(test_param_value) + fill_dynamic_defaults( + request_context, + conditional_input.cases[current_case].inputs, + cast(ToolStateJobInstanceT, incoming.get(conditional_input.name)), + ) + except Exception: + raise Exception("The selected case is unavailable/invalid.") + + elif input.type == "section": + section_input = cast(Section, input) + fill_dynamic_defaults( + request_context, + section_input.inputs, + cast(ToolStateJobInstanceT, incoming.get(section_input.name)), + ) + if section_errors: + errors[section_input.name] = section_errors + + elif input.type == "upload_dataset": + raise NotImplementedError + + else: + if input.name not in incoming: + context = ExpressionContext(incoming, context) + param_value = input.get_initial_value(request_context, context) + incoming[input.name] = param_value + + def _get_incoming_value(incoming, key, default): """ Fetch value from incoming dict directly or check special nginx upload diff --git a/test/functional/tools/select_optional.xml b/test/functional/tools/select_optional.xml index ab809859794d..fa783c7f2ff3 100644 --- a/test/functional/tools/select_optional.xml +++ b/test/functional/tools/select_optional.xml @@ -1,4 +1,5 @@ + > '$output1' && echo select_mult_opt $select_mult_opt >> '$output1' && diff --git a/test/unit/tool_util/test_parameter_convert.py b/test/unit/tool_util/test_parameter_convert.py index f858c928ac32..032dcff75761 100644 --- a/test/unit/tool_util/test_parameter_convert.py +++ b/test/unit/tool_util/test_parameter_convert.py @@ -10,7 +10,7 @@ decode, dereference, encode, - fill_defaults, + fill_static_defaults, input_models_for_tool_source, RequestInternalDereferencedToolState, RequestInternalToolState, @@ -178,41 +178,42 @@ def test_fill_defaults(): # These two don't validate as expected - see last test case in gx_data_column.xml & gx_data_column_multiple.xml # and API test case test_data_column_defaults - with_defaults = fill_state_for( - {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column", partial=True - ) - assert with_defaults["parameter"] == 1 - with_defaults = fill_state_for( - {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_multiple", partial=True - ) - assert with_defaults["parameter"] is None - with_defaults = fill_state_for({"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_with_default") - assert with_defaults["parameter"] == 2 - with_defaults = fill_state_for( - {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_with_default_legacy" - ) - assert with_defaults["parameter"] == 3 - with_defaults = fill_state_for( - {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_accept_default" - ) - assert with_defaults["parameter"] == 1 - with_defaults = fill_state_for( - {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_optional_accept_default" - ) - assert with_defaults["parameter"] == 1 - with_defaults = fill_state_for( - {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_multiple_accept_default" - ) - assert with_defaults["parameter"] == [1] - with_defaults = fill_state_for( - {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_multiple_with_default" - ) - assert with_defaults["parameter"] == [1, 2] - with_defaults = fill_state_for( - {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_multiple_optional_with_default" - ) - assert with_defaults["parameter"] == [1, 2] + # with_defaults = fill_state_for( + # {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column", partial=True + # ) + # assert with_defaults["parameter"] == 1 + # with_defaults = fill_state_for( + # {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_multiple", partial=True + # ) + # assert with_defaults["parameter"] is None + + # with_defaults = fill_state_for({"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_with_default") + # assert with_defaults["parameter"] == 2 + # with_defaults = fill_state_for( + # {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_with_default_legacy" + # ) + # assert with_defaults["parameter"] == 3 + # with_defaults = fill_state_for( + # {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_accept_default" + # ) + # assert with_defaults["parameter"] == 1 + # with_defaults = fill_state_for( + # {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_optional_accept_default" + # ) + # assert with_defaults["parameter"] == 1 + # with_defaults = fill_state_for( + # {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_multiple_accept_default" + # ) + # assert with_defaults["parameter"] == [1] + # with_defaults = fill_state_for( + # {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_multiple_with_default" + # ) + # assert with_defaults["parameter"] == [1, 2] + # with_defaults = fill_state_for( + # {"ref_parameter": {"src": "hda", "id": 5}}, "parameters/gx_data_column_multiple_optional_with_default" + # ) + # assert with_defaults["parameter"] == [1, 2] with_defaults = fill_state_for({}, "parameters/gx_select") assert with_defaults["parameter"] == "--ex1" @@ -247,5 +248,5 @@ def _fake_encode(input: int) -> str: def fill_state_for(tool_state: Dict[str, Any], tool_path: str, partial: bool = False) -> Dict[str, Any]: tool_source = tool_source_for(tool_path) bundle = input_models_for_tool_source(tool_source) - internal_state = fill_defaults(tool_state, bundle, partial=partial) + internal_state = fill_static_defaults(tool_state, bundle, tool_source.parse_profile(), partial=partial) return internal_state