diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index 3a60a9ce2e12..d9885d4cce71 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -3938,8 +3938,8 @@ Name | Description ``$__tool_data_path__`` | ``config/galaxy.ini``'s tool_data_path value ``$__root_dir__`` | Top-level Galaxy source directory made absolute via ``os.path.abspath()`` ``$__datatypes_config__`` | ``config/galaxy.ini``'s datatypes_config value -``$__user_id__`` | Email's numeric ID (id column of ``galaxy_user`` table in the database) -``$__user_email__`` | User's email address +``$__user_id__`` | Numeric ID of user (id column of ``galaxy_user`` table in the database) +``$__user_email__`` | Email address of user ``$__app__`` | The ``galaxy.app.UniverseApplication`` instance, gives access to all other configuration file variables (e.g. $__app__.config.output_size_limit). Should be used as a last resort, may go away in future releases. ``$__target_datatype__`` | Only available in converter tools when run internally by Galaxy. Contains the target datatype of the conversion @@ -4039,6 +4039,16 @@ of "type" specified for this expression block. + + + Select a request method, defaults to GET if unspecified + + + + + + + ``` +### ``from_url`` + +The following example demonstrates getting options from a third-party server +with server side requests. + +```xml + + + + +``` + +Here a GET request is made to [https://usegalaxy.org/api/genomes](https://usegalaxy.org/api/genomes), which returns +an array of arrays, such as + +```json +[ + ["unspecified (?)", "?"], + ["A. ceylanicum Mar. 2014 (WS243/Acey_2013.11.30.genDNA/ancCey1) (ancCey1)", "ancCey1"], + ... +] +``` +Each inner array is a user-selectable option, where the first item in the inner array +is the `name` of the option (as shown in the select field in the user interface), and +the second option is the `value` that is passed on to the tool. An optional third +element can be added to the inner array which corresponds to the `selected` state. +If the third item is `true` then this particular option is pre-selected. + +A more complicated example is shown below, where a POST request is made with a templated +request header and body. The upstream response is then also transformed using an ecma 5.1 +expression: + +```xml + + + + + {"x-api-key": "${__user__.extra_preferences.fake_api_key if $__user__ else "anon"}"} + + + {"name": "value"} + + + [header, header]) + }]]]]> + + +``` + +The header and body templating mechanism can be used to access protected resources, +and the `postprocess_expression` can be used to transform arbitrary JSON responses +to arrays of `name` and `value`, or arrays of `name`, `value` and `selected`. + +For an example tool see [select_from_url.xml](https://github.com/galaxyproject/galaxy/tree/dev/test/functional/tools/select_from_url.xml). + ### ``from_file`` The following example is for Blast databases. In this example users maybe select @@ -4390,7 +4457,7 @@ used to generate dynamic options. - + @@ -4407,6 +4474,16 @@ used to generate dynamic options. Determine options from a data table. + + + Determine options from data hosted at specified URL. + + + + + Set the request method to use for options provided using 'from_url'. + + Deprecated. @@ -4440,9 +4517,12 @@ used to generate dynamic options. - - - + + + + + + Documentation for file @@ -4451,6 +4531,20 @@ used to generate dynamic options. + + + + + + + + + + + + + + Optional[str]: + if maybe_string is not None: + if maybe_string.text: + return maybe_string.text.strip() + return None + + +def parse_from_url_options(elem: Element) -> Optional[FromUrlOptions]: + from_url = elem.get("from_url") + if from_url: + request_method = cast(Literal["GET", "POST"], elem.get("request_method", "GET")) + assert request_method in get_args(REQUEST_METHODS) + request_headers = strip_or_none(elem.find("request_headers")) + request_body = strip_or_none(elem.find("request_body")) + postprocess_expression = strip_or_none(elem.find("postprocess_expression")) + return FromUrlOptions( + from_url, + request_method=request_method, + request_headers=request_headers, + request_body=request_body, + postprocess_expression=postprocess_expression, + ) + return None + + +def template_or_none(template: Optional[str], context: Dict[str, Any]) -> Optional[str]: + if template: + return fill_template(template, context=context) + return None + + def _get_ref_data(other_values, ref_name): """ get the list of data sets from ref_name diff --git a/lib/galaxy/work/context.py b/lib/galaxy/work/context.py index 8a1206018c0a..8e4fc74afc21 100644 --- a/lib/galaxy/work/context.py +++ b/lib/galaxy/work/context.py @@ -1,7 +1,10 @@ import abc from typing import ( + Any, + Dict, List, Optional, + Tuple, ) from typing_extensions import Literal @@ -42,9 +45,16 @@ def __init__( self.__user_current_roles: Optional[List[Role]] = None self.__history = history self._url_builder = url_builder + self._short_term_cache: Dict[Tuple[str, ...], Any] = {} 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 4682a88fa148..b823ce3d5921 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -18,7 +18,6 @@ Union, ) -from cwl_utils.expression import do_eval from typing_extensions import TypedDict from galaxy import ( @@ -59,6 +58,7 @@ MappingParameters, PartialJobExecution, ) +from galaxy.tools.expressions import do_eval from galaxy.tools.parameters import ( check_param, params_to_incoming, @@ -226,10 +226,6 @@ def evaluate_value_from_expressions(progress, step, execution_state, extra_step_ as_cwl_value = do_eval( when_expression, step_state, - [{"class": "InlineJavascriptRequirement"}], - None, - None, - {}, ) except Exception: # Exception contains script and traceback, which could be helpful for debugging workflows, diff --git a/test/functional/tools/sample_tool_conf.xml b/test/functional/tools/sample_tool_conf.xml index ae0f15a5a7bc..8df30a78eff6 100644 --- a/test/functional/tools/sample_tool_conf.xml +++ b/test/functional/tools/sample_tool_conf.xml @@ -23,6 +23,7 @@ + diff --git a/test/functional/tools/select_from_url.xml b/test/functional/tools/select_from_url.xml new file mode 100644 index 000000000000..60a712e74631 --- /dev/null +++ b/test/functional/tools/select_from_url.xml @@ -0,0 +1,87 @@ + + '$param_value' && +echo '$url_param_value_postprocessed' > '$param_value_postprocessed' && +echo '$invalid_url_param_value_postprocessed' > '$invalid_param_value_postprocessed' && +echo '$url_param_value_header_and_body' > '$param_value_header_and_body' + ]]> + + + + + + + + + [v.chrom, v.len]) ) + ]]> + + + + + [v.chrom, v.len]) + } else { + return [["The fallback value", "default"]] + } + }]]> + + + + + + + {"x-api-key": "${__user__.extra_preferences.fake_api_key if $__user__ else "anon"}"} + + + {"name": "value"} + + + [header, header]) + }]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/unit/app/tools/test_dynamic_options.py b/test/unit/app/tools/test_dynamic_options.py new file mode 100644 index 000000000000..38626d65b991 --- /dev/null +++ b/test/unit/app/tools/test_dynamic_options.py @@ -0,0 +1,57 @@ +from galaxy.app_unittest_utils.galaxy_mock import MockApp +from galaxy.tools.parameters.dynamic_options import DynamicOptions +from galaxy.util import XML +from galaxy.util.bunch import Bunch +from galaxy.work.context import WorkRequestContext + + +def get_from_url_option(): + return DynamicOptions( + XML( + """ + + + {"x-api-key": "${__user__.extra_preferences.resource_api_key if $__user__ else "anon"}"} + + + {"some_key": "some_value"} + + [v.chrom, v.len]) + } else { + return [["The fallback value", "default"]] + } + }]]> + +""" + ), + Bunch(), + ) + + +def test_dynamic_option_parsing(): + from_url_option = get_from_url_option() + assert from_url_option.from_url_options + assert from_url_option.from_url_options.from_url == "https://usegalaxy.org/api/genomes/dm6" + + +def test_dynamic_option_cache(): + app = MockApp() + trans = WorkRequestContext(app=app) + from_url_option = get_from_url_option() + options = from_url_option.from_url_options + assert options + args = (options.from_url, options.request_method, options.request_body, '{"x-api-key": "anon"}') + trans.set_cache_value( + args, + { + "id": "dm6", + "reference": True, + "chrom_info": [{"chrom": "chr2L", "len": 23513712}], + "prev_chroms": False, + "next_chroms": False, + "start_index": 0, + }, + ) + assert from_url_option.get_options(trans, {}) == [["chr2L", "23513712", False]]