From 8d10c12310fb62005b7750f17c6eb298a8c0fb97 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 12 Feb 2024 15:22:15 +0100 Subject: [PATCH] Implement request header and body templating --- lib/galaxy/tool_util/xsd/galaxy.xsd | 39 +++++++- .../tools/parameters/dynamic_options.py | 90 +++++++++++++++---- lib/galaxy/work/context.py | 11 +-- test/functional/tools/select_from_url.xml | 25 +++++- test/unit/app/tools/test_dynamic_options.py | 57 ++++++++++++ 5 files changed, 197 insertions(+), 25 deletions(-) create mode 100644 test/unit/app/tools/test_dynamic_options.py diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index 461600fa68b5..6ad79a7549a2 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -4039,6 +4039,16 @@ of "type" specified for this expression block. + + + Select a request method, defaults to GET if unspecified + + + + + + + Determine options from data hosted at specified URL. + + + Set the request method to use for options provided using from_url. + + Deprecated. @@ -4446,10 +4461,12 @@ used to generate dynamic options. - - - - + + + + + + Documentation for file @@ -4458,6 +4475,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(FromUrlOptions.request_headers) + 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 a5bb476e0d19..8e4fc74afc21 100644 --- a/lib/galaxy/work/context.py +++ b/lib/galaxy/work/context.py @@ -4,6 +4,7 @@ Dict, List, Optional, + Tuple, ) from typing_extensions import Literal @@ -44,15 +45,15 @@ def __init__( self.__user_current_roles: Optional[List[Role]] = None self.__history = history self._url_builder = url_builder - self._short_term_cache: Dict[str, Any] = {} + 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, key: str, value: Any): - self._short_term_cache[key] = value + def set_cache_value(self, args: Tuple[str, ...], value: Any): + self._short_term_cache[args] = value - def get_cache_value(self, key: str, default: Any = None) -> Any: - return self._short_term_cache.get(key, default) + def get_cache_value(self, args: Tuple[str, ...], default: Any = None) -> Any: + return self._short_term_cache.get(args, default) @property def app(self): diff --git a/test/functional/tools/select_from_url.xml b/test/functional/tools/select_from_url.xml index 260b239dc908..60a712e74631 100644 --- a/test/functional/tools/select_from_url.xml +++ b/test/functional/tools/select_from_url.xml @@ -2,7 +2,8 @@ '$param_value' && echo '$url_param_value_postprocessed' > '$param_value_postprocessed' && -echo '$invalid_url_param_value_postprocessed' > '$invalid_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' ]]> @@ -33,17 +34,34 @@ echo '$invalid_url_param_value_postprocessed' > '$invalid_param_value_postproces }]]> + + + + + {"x-api-key": "${__user__.extra_preferences.fake_api_key if $__user__ else "anon"}"} + + + {"name": "value"} + + + [header, header]) + }]]> + + + + @@ -59,6 +77,11 @@ echo '$invalid_url_param_value_postprocessed' > '$invalid_param_value_postproces + + + + + 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]]