From bcf9fab1c75fb0a720de3ecbb35a490a3af7f7bf Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 27 Sep 2024 10:22:34 -0400 Subject: [PATCH] models for tool landing request state... --- lib/galaxy/tool_util/parameters/__init__.py | 12 + lib/galaxy/tool_util/parameters/convert.py | 44 ++- lib/galaxy/tool_util/parameters/models.py | 46 ++- lib/galaxy/tool_util/parameters/state.py | 18 + lib/galaxy/tools/parameters/basic.py | 13 +- lib/galaxy/tools/wrappers.py | 16 +- .../tool_util/parameter_specification.yml | 330 ++++++++++++++++++ test/unit/tool_util/test_parameter_convert.py | 17 + .../tool_util/test_parameter_specification.py | 16 + 9 files changed, 494 insertions(+), 18 deletions(-) diff --git a/lib/galaxy/tool_util/parameters/__init__.py b/lib/galaxy/tool_util/parameters/__init__.py index 8df9db0c1f16..45cd770a5e3f 100644 --- a/lib/galaxy/tool_util/parameters/__init__.py +++ b/lib/galaxy/tool_util/parameters/__init__.py @@ -5,6 +5,8 @@ encode, encode_test, fill_static_defaults, + landing_decode, + landing_encode, ) from .factory import ( from_input_source, @@ -50,8 +52,10 @@ ToolParameterT, validate_against_model, validate_internal_job, + validate_internal_landing_request, validate_internal_request, validate_internal_request_dereferenced, + validate_landing_request, validate_request, validate_test_case, validate_workflow_step, @@ -60,6 +64,8 @@ ) from .state import ( JobInternalToolState, + LandingRequestInternalToolState, + LandingRequestToolState, RequestInternalDereferencedToolState, RequestInternalToolState, RequestToolState, @@ -119,8 +125,10 @@ "ValidationFunctionT", "validate_against_model", "validate_internal_job", + "validate_internal_landing_request", "validate_internal_request", "validate_internal_request_dereferenced", + "validate_landing_request", "validate_request", "validate_test_case", "validate_workflow_step", @@ -134,6 +142,8 @@ "RequestToolState", "RequestInternalToolState", "RequestInternalDereferencedToolState", + "LandingRequestToolState", + "LandingRequestInternalToolState", "flat_state_path", "keys_starting_with", "visit_input_values", @@ -143,6 +153,8 @@ "encode", "encode_test", "fill_static_defaults", + "landing_decode", + "landing_encode", "dereference", "WorkflowStepToolState", "WorkflowStepLinkedToolState", diff --git a/lib/galaxy/tool_util/parameters/convert.py b/lib/galaxy/tool_util/parameters/convert.py index bb1ebc911089..94e4d0c5e569 100644 --- a/lib/galaxy/tool_util/parameters/convert.py +++ b/lib/galaxy/tool_util/parameters/convert.py @@ -40,6 +40,8 @@ ) from .state import ( JobInternalToolState, + LandingRequestInternalToolState, + LandingRequestToolState, RequestInternalDereferencedToolState, RequestInternalToolState, RequestToolState, @@ -67,7 +69,7 @@ def decode( external_state: RequestToolState, input_models: ToolParameterBundle, decode_id: Callable[[str], int] ) -> RequestInternalToolState: - """Prepare an external representation of tool state (request) for storing in the database (request_internal).""" + """Prepare an internal representation of tool state (request_internal) for storing in the database.""" external_state.validate(input_models) decode_callback = _decode_callback_for(decode_id) @@ -83,14 +85,14 @@ def decode( def encode( - external_state: RequestInternalToolState, input_models: ToolParameterBundle, encode_id: EncodeFunctionT + internal_state: RequestInternalToolState, input_models: ToolParameterBundle, encode_id: EncodeFunctionT ) -> RequestToolState: - """Prepare an external representation of tool state (request) for storing in the database (request_internal).""" + """Prepare an external representation of tool state (request) from persisted state in the database (request_internal).""" encode_callback = _encode_callback_for(encode_id) request_state_dict = visit_input_values( input_models, - external_state, + internal_state, encode_callback, ) request_state = RequestToolState(request_state_dict) @@ -98,6 +100,40 @@ def encode( return request_state +def landing_decode( + external_state: LandingRequestToolState, input_models: ToolParameterBundle, decode_id: Callable[[str], int] +) -> LandingRequestInternalToolState: + """Prepare an external representation of tool state (request) for storing in the database (request_internal).""" + + external_state.validate(input_models) + decode_callback = _decode_callback_for(decode_id) + internal_state_dict = visit_input_values( + input_models, + external_state, + decode_callback, + ) + + internal_request_state = LandingRequestInternalToolState(internal_state_dict) + internal_request_state.validate(input_models) + return internal_request_state + + +def landing_encode( + internal_state: LandingRequestInternalToolState, input_models: ToolParameterBundle, encode_id: EncodeFunctionT +) -> LandingRequestToolState: + """Prepare an external representation of tool state (request) for storing in the database (request_internal).""" + + encode_callback = _encode_callback_for(encode_id) + request_state_dict = visit_input_values( + input_models, + internal_state, + encode_callback, + ) + request_state = LandingRequestToolState(request_state_dict) + request_state.validate(input_models) + return request_state + + def dereference( internal_state: RequestInternalToolState, input_models: ToolParameterBundle, dereference: DereferenceCallable ) -> RequestInternalDereferencedToolState: diff --git a/lib/galaxy/tool_util/parameters/models.py b/lib/galaxy/tool_util/parameters/models.py index 329423793d57..d731560cc895 100644 --- a/lib/galaxy/tool_util/parameters/models.py +++ b/lib/galaxy/tool_util/parameters/models.py @@ -64,6 +64,8 @@ "request", "request_internal", "request_internal_dereferenced", + "landing_request", + "landing_request_internal", "job_internal", "test_case_xml", "workflow_step", @@ -219,6 +221,8 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam requires_value = self.request_requires_value if state_representation == "job_internal": requires_value = True + elif _is_landing_request(state_representation): + requires_value = False return dynamic_model_information_from_py_type(self, py_type, requires_value=requires_value) @property @@ -240,7 +244,12 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam py_type = self.py_type if state_representation == "workflow_step_linked": py_type = allow_connected_value(py_type) - return dynamic_model_information_from_py_type(self, py_type) + requires_value = self.request_requires_value + if state_representation == "job_internal": + requires_value = True + elif _is_landing_request(state_representation): + requires_value = False + return dynamic_model_information_from_py_type(self, py_type, requires_value=requires_value) @property def request_requires_value(self) -> bool: @@ -405,10 +414,19 @@ def py_type_test_case(self) -> Type: def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: if state_representation == "request": return allow_batching(dynamic_model_information_from_py_type(self, self.py_type), BatchDataInstance) + if state_representation == "landing_request": + return allow_batching( + dynamic_model_information_from_py_type(self, self.py_type, requires_value=False), BatchDataInstance + ) elif state_representation == "request_internal": return allow_batching( dynamic_model_information_from_py_type(self, self.py_type_internal), BatchDataInstanceInternal ) + elif state_representation == "landing_request_internal": + return allow_batching( + dynamic_model_information_from_py_type(self, self.py_type_internal, requires_value=False), + BatchDataInstanceInternal, + ) elif state_representation == "request_internal_dereferenced": return allow_batching( dynamic_model_information_from_py_type(self, self.py_type_internal_dereferenced), @@ -455,6 +473,12 @@ def py_type_internal(self) -> Type: def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: if state_representation == "request": return allow_batching(dynamic_model_information_from_py_type(self, self.py_type)) + elif state_representation == "landing_request": + return allow_batching(dynamic_model_information_from_py_type(self, self.py_type, requires_value=False)) + elif state_representation == "landing_request_internal": + return allow_batching( + dynamic_model_information_from_py_type(self, self.py_type_internal, requires_value=False) + ) elif state_representation in ["request_internal", "request_internal_dereferenced"]: return allow_batching(dynamic_model_information_from_py_type(self, self.py_type_internal)) elif state_representation == "job_internal": @@ -600,7 +624,10 @@ def py_type(self) -> Type: return AnyUrl def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: - return dynamic_model_information_from_py_type(self, self.py_type) + requires_value = self.request_requires_value + if _is_landing_request(state_representation): + requires_value = False + return dynamic_model_information_from_py_type(self, self.py_type, requires_value=requires_value) @property def request_requires_value(self) -> bool: @@ -1080,9 +1107,14 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam instance_class: Type[BaseModel] = create_field_model( self.parameters, f"Repeat_{self.name}", state_representation ) + min_length = self.min + max_length = self.max requires_value = self.request_requires_value if state_representation == "job_internal": requires_value = True + elif _is_landing_request(state_representation): + requires_value = False + min_length = 0 # in a landing request - parameters can be partially filled initialize_repeat: Any if requires_value: @@ -1091,7 +1123,7 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam initialize_repeat = None class RepeatType(RootModel): - root: List[instance_class] = Field(initialize_repeat, min_length=self.min, max_length=self.max) # type: ignore[valid-type] + root: List[instance_class] = Field(initialize_repeat, min_length=min_length, max_length=max_length) # type: ignore[valid-type] return DynamicModelInformation( self.name, @@ -1382,6 +1414,8 @@ def create_method(tool: ToolParameterBundle, name: str = DEFAULT_MODEL_NAME) -> create_request_model = create_model_factory("request") create_request_internal_model = create_model_factory("request_internal") create_request_internal_dereferenced_model = create_model_factory("request_internal_dereferenced") +create_landing_request_model = create_model_factory("landing_request") +create_landing_request_internal_model = create_model_factory("landing_request_internal") create_job_internal_model = create_model_factory("job_internal") create_test_case_model = create_model_factory("test_case_xml") create_workflow_step_model = create_model_factory("workflow_step") @@ -1413,6 +1447,10 @@ def create_field_model( return pydantic_model +def _is_landing_request(state_representation: StateRepresentationT): + return state_representation in ["landing_request", "landing_request_internal"] + + def validate_against_model(pydantic_model: Type[BaseModel], parameter_state: Dict[str, Any]) -> None: try: pydantic_model(**parameter_state) @@ -1441,6 +1479,8 @@ def validate_request(tool: ToolParameterBundle, request: Dict[str, Any], name: s validate_request = validate_model_type_factory("request") validate_internal_request = validate_model_type_factory("request_internal") validate_internal_request_dereferenced = validate_model_type_factory("request_internal_dereferenced") +validate_landing_request = validate_model_type_factory("landing_request") +validate_internal_landing_request = validate_model_type_factory("landing_request_internal") validate_internal_job = validate_model_type_factory("job_internal") validate_test_case = validate_model_type_factory("test_case_xml") validate_workflow_step = validate_model_type_factory("workflow_step") diff --git a/lib/galaxy/tool_util/parameters/state.py b/lib/galaxy/tool_util/parameters/state.py index af15cf23ac7b..3edc96f69c6e 100644 --- a/lib/galaxy/tool_util/parameters/state.py +++ b/lib/galaxy/tool_util/parameters/state.py @@ -15,6 +15,8 @@ from .models import ( create_job_internal_model, + create_landing_request_internal_model, + create_landing_request_model, create_request_internal_dereferenced_model, create_request_internal_model, create_request_model, @@ -84,6 +86,22 @@ def _parameter_model_for(cls, parameters: ToolParameterBundle) -> Type[BaseModel return create_request_internal_model(parameters) +class LandingRequestToolState(ToolState): + state_representation: Literal["landing_request"] = "landing_request" + + @classmethod + def _parameter_model_for(cls, parameters: ToolParameterBundle) -> Type[BaseModel]: + return create_landing_request_model(parameters) + + +class LandingRequestInternalToolState(ToolState): + state_representation: Literal["landing_request_internal"] = "landing_request_internal" + + @classmethod + def _parameter_model_for(cls, parameters: ToolParameterBundle) -> Type[BaseModel]: + return create_landing_request_internal_model(parameters) + + class RequestInternalDereferencedToolState(ToolState): state_representation: Literal["request_internal_dereferenced"] = "request_internal_dereferenced" diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index d656665be21b..5aa07efdd204 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -187,7 +187,6 @@ def __init__(self, tool, input_source, context=None): self.hidden = input_source.get_bool("hidden", False) self.refresh_on_change = input_source.get_bool("refresh_on_change", False) self.optional = input_source.parse_optional() - self.optionality_inferred = False self.is_dynamic = False self.label = input_source.parse_label() self.help = input_source.parse_help() @@ -352,6 +351,7 @@ def parse_name(input_source): class SimpleTextToolParameter(ToolParameter): def __init__(self, tool, input_source): input_source = ensure_input_source(input_source) + self.optionality_inferred = False super().__init__(tool, input_source) optional = input_source.get("optional", None) if optional is not None: @@ -405,6 +405,7 @@ class TextToolParameter(SimpleTextToolParameter): def __init__(self, tool, input_source): input_source = ensure_input_source(input_source) super().__init__(tool, input_source) + self.profile = tool.profile self.datalist = [] for title, value, _ in input_source.parse_static_options(): self.datalist.append({"label": title, "value": value}) @@ -420,6 +421,16 @@ def validate(self, value, trans=None): ): return super().validate(value, trans) + @property + def wrapper_default() -> Optional[str]: + """Handle change in default handling pre and post 23.0 profiles.""" + profile = self.profile + legacy_behavior = (profile is None or Version(str(profile)) < Version("23.0")) + default_value = None + if self.optional and self.optionality_inferred and legacy_behavior: + default_value = "" + return default_value + def to_dict(self, trans, other_values=None): d = super().to_dict(trans) other_values = other_values or {} diff --git a/lib/galaxy/tools/wrappers.py b/lib/galaxy/tools/wrappers.py index 01e8e8491bbc..34e5b84737e2 100644 --- a/lib/galaxy/tools/wrappers.py +++ b/lib/galaxy/tools/wrappers.py @@ -19,7 +19,6 @@ Union, ) -from packaging.version import Version from typing_extensions import TypeAlias from galaxy.model import ( @@ -33,7 +32,10 @@ from galaxy.model.metadata import FileParameter from galaxy.model.none_like import NoneDataset from galaxy.security.object_wrapper import wrap_with_safe_string -from galaxy.tools.parameters.basic import BooleanToolParameter +from galaxy.tools.parameters.basic import ( + BooleanToolParameter, + TextToolParameter, +) from galaxy.tools.parameters.wrapped_json import ( data_collection_input_to_staging_path_and_source_path, data_input_to_staging_path_and_source_path, @@ -126,15 +128,9 @@ def __init__( profile: Optional[float] = None, ) -> None: self.input = input - if ( - value is None - and input.type == "text" - and input.optional - and input.optionality_inferred - and (profile is None or Version(str(profile)) < Version("23.0")) - ): + if value is None and input.type == "text": # Tools with old profile versions may treat an optional text parameter as `""` - value = "" + value = cast(TextToolParameter, input).wrapper_default() self.value = value self._other_values: Dict[str, str] = other_values or {} diff --git a/test/unit/tool_util/parameter_specification.yml b/test/unit/tool_util/parameter_specification.yml index 2eb07ada9f38..29911941e714 100644 --- a/test/unit/tool_util/parameter_specification.yml +++ b/test/unit/tool_util/parameter_specification.yml @@ -32,6 +32,18 @@ gx_int: job_internal_invalid: - parameter: null - {} + landing_request_valid: + - {} + - parameter: 5 + landing_request_invalid: + - parameter: "moo" + - parameter: "5" + landing_request_internal_valid: + - {} + - parameter: 5 + landing_request_internal_invalid: + - parameter: "moo" + - parameter: "5" test_case_xml_valid: - parameter: 5 - {} @@ -71,6 +83,21 @@ gx_boolean: - parameter: false job_internal_invalid: - {} + landing_request_valid: + - {} + - parameter: true + - parameter: false + landing_request_invalid: + - parameter: "true" + - parameter: "5" + landing_request_internal_valid: + - {} + - parameter: true + - parameter: false + landing_request_internal_invalid: + - parameter: "moo" + - parameter: "true" + - parameter: "5" workflow_step_valid: - parameter: True - {} @@ -102,6 +129,20 @@ gx_int_optional: - parameter: null job_internal_invalid: - {} + landing_request_valid: + - {} + - parameter: 5 + - parameter: null + landing_request_invalid: + - parameter: "moo" + - parameter: "5" + landing_request_internal_valid: + - {} + - parameter: 5 + - parameter: null + landing_request_internal_invalid: + - parameter: "moo" + - parameter: "5" workflow_step_valid: - parameter: 5 - parameter: null @@ -132,6 +173,20 @@ gx_int_required: &gx_int_required - parameter: 5 job_internal_invalid: - {} + # and actual request will require a value but the landing only needs to specify parameters + # of interest - forcing users to make the rest of the selections. + landing_request_valid: + - {} + - parameter: 5 + landing_request_invalid: + - parameter: "moo" + - parameter: "5" + landing_request_internal_valid: + - {} + - parameter: 5 + landing_request_internal_invalid: + - parameter: "moo" + - parameter: "5" gx_int_required_via_empty_string: <<: *gx_int_required @@ -156,6 +211,14 @@ gx_text: *gx_text_request_valid request_internal_dereferenced_invalid: *gx_text_request_invalid + landing_request_valid: + *gx_text_request_valid + landing_request_invalid: + *gx_text_request_invalid + landing_request_internal_valid: + *gx_text_request_valid + landing_request_internal_invalid: + *gx_text_request_invalid job_internal_valid: - parameter: moocow - parameter: 'some spaces' @@ -198,6 +261,18 @@ gx_text_optional: - parameter: 5 - parameter: {} - parameter: { "moo": "cow" } + landing_request_valid: &gx_text_optional_landing_request_valid + - parameter: "moo cow" + - {} + - parameter: null + landing_request_invalid: &gx_text_optional_landing_request_invalid + - parameter: 5 + - parameter: 6 + landing_request_internal_valid: + *gx_text_optional_landing_request_valid + landing_request_internal_invalid: + *gx_text_optional_landing_request_invalid + workflow_step_valid: - parameter: moocow - parameter: 'some spaces' @@ -243,6 +318,18 @@ gx_select: *gx_select_request_valid request_internal_dereferenced_invalid: *gx_select_request_invalid + landing_request_valid: &gx_select_landing_request_valid + - parameter: "--ex1" + - parameter: "ex2" + - {} + landing_request_invalid: &gx_select_landing_request_invalid + - parameter: 5 + - parameter: null + - parameter: "Ex1" + landing_request_internal_valid: + *gx_select_landing_request_valid + landing_request_internal_invalid: + *gx_select_landing_request_invalid job_internal_valid: - parameter: '--ex1' - parameter: 'ex2' @@ -283,6 +370,19 @@ gx_select_optional: - parameter: ["ex2"] - parameter: {} - parameter: 5 + landing_request_valid: &gx_select_optional_landing_request_valid + - parameter: "--ex1" + - parameter: "ex2" + - parameter: null + - {} + landing_request_invalid: &gx_select_optional_landing_request_invalid + - parameter: 5 + - parameter: "Ex1" + - parameter: {} + landing_request_internal_valid: + *gx_select_optional_landing_request_valid + landing_request_internal_invalid: + *gx_select_optional_landing_request_invalid job_internal_valid: - parameter: '--ex1' - parameter: 'ex2' @@ -326,6 +426,20 @@ gx_select_multiple: - parameter: ["Ex1"] - parameter: {} - parameter: 5 + landing_request_valid: &gx_select_multiple_landing_request_valid + - parameter: ["--ex1"] + - parameter: ["ex2"] + - parameter: null + - {} + landing_request_invalid: &gx_select_multiple_landing_request_invalid + - parameter: 5 + - parameter: ["Ex1"] + - parameter: {} + - parameter: false + landing_request_internal_valid: + *gx_select_multiple_landing_request_valid + landing_request_internal_invalid: + *gx_select_multiple_landing_request_invalid workflow_step_valid: - parameter: ["--ex1"] - parameter: ["ex2"] @@ -379,6 +493,17 @@ gx_genomebuild: request_invalid: - parameter: null - parameter: 9 + landing_request_valid: &gx_genomebuild_landing_request_valid + - parameter: hg19 + - parameter: hg18 + - {} + landing_request_invalid: &gx_genomebuild_landing_request_invalid + - parameter: null + - parameter: 9 + landing_request_internal_valid: + *gx_genomebuild_landing_request_valid + landing_request_internal_invalid: + *gx_genomebuild_landing_request_invalid job_internal_valid: - parameter: hg19 job_internal_invalid: @@ -394,6 +519,17 @@ gx_genomebuild_optional: request_invalid: - parameter: 8 - parameter: true + landing_request_valid: &gx_genomebuild_optional_landing_request_valid + - parameter: hg19 + - parameter: hg18 + - {} + - parameter: null + landing_request_invalid: &gx_genomebuild_optional_landing_request_invalid + - parameter: 9 + landing_request_internal_valid: + *gx_genomebuild_optional_landing_request_valid + landing_request_internal_invalid: + *gx_genomebuild_optional_landing_request_invalid job_internal_valid: - parameter: null job_internal_invalid: @@ -415,6 +551,18 @@ gx_directory_uri: - parameter: "justsomestring" - parameter: true - parameter: null + landing_request_valid: &gx_directory_uri_landing_request_valid + - parameter: "gxfiles://foobar/" + - parameter: "gxfiles://foobar" + - {} + landing_request_invalid: &gx_directory_uri_landing_request_invalid + - parameter: true + - parameter: null + - parameter: 8 + landing_request_internal_valid: + *gx_directory_uri_landing_request_valid + landing_request_internal_invalid: + *gx_directory_uri_landing_request_invalid job_internal_valid: - parameter: "gxfiles://foobar/" - parameter: "gxfiles://foobar" @@ -435,6 +583,20 @@ gx_hidden: - parameter: 5 - parameter: {} - parameter: { "moo": "cow" } + landing_request_valid: &gx_hidden_landing_request_valid + - parameter: moocow + - parameter: 'some spaces' + - parameter: '' + - {} + landing_request_invalid: &gx_hidden_landing_request_invalid + - parameter: null + - parameter: 5 + - parameter: {} + - parameter: { "moo": "cow" } + landing_request_internal_valid: + *gx_hidden_landing_request_valid + landing_request_internal_invalid: + *gx_hidden_landing_request_invalid job_internal_valid: - parameter: moocow - parameter: 'some spaces' @@ -465,6 +627,20 @@ gx_hidden_optional: - parameter: 5 - parameter: {} - parameter: { "moo": "cow" } + landing_request_valid: &gx_hidden_optional_landing_request_valid + - parameter: moocow + - parameter: 'some spaces' + - parameter: '' + - {} + - parameter: null + landing_request_invalid: &gx_hidden_optional_landing_request_invalid + - parameter: 5 + - parameter: {} + - parameter: { "moo": "cow" } + landing_request_internal_valid: + *gx_hidden_optional_landing_request_valid + landing_request_internal_invalid: + *gx_hidden_optional_landing_request_invalid workflow_step_valid: - parameter: moocow - {} @@ -492,6 +668,30 @@ gx_float: - parameter: "5" - parameter: "5.0" - parameter: { "moo": "cow" } + job_internal_valid: + - parameter: 5 + - parameter: 5.0 + job_internal_invalid: + - {} + - parameter: "5" + - parameter: "5.0" + - parameter: null + landing_request_valid: + - {} + - parameter: 5 + - parameter: 5.0 + landing_request_invalid: + - parameter: "moo" + - parameter: "5" + - parameter: "5.0" + landing_request_internal_valid: + - {} + - parameter: 5 + - parameter: 5.0 + landing_request_internal_invalid: + - parameter: "moo" + - parameter: "5" + - parameter: "5.0" test_case_xml_valid: - parameter: 5 - parameter: 5.0 @@ -529,6 +729,32 @@ gx_float_optional: - parameter: "5.0" - parameter: {} - parameter: { "moo": "cow" } + job_internal_valid: + - parameter: 5 + - parameter: 5.0 + - parameter: null + job_internal_invalid: + - {} + - parameter: "5" + - parameter: "5.0" + landing_request_valid: + - {} + - parameter: 5 + - parameter: 5.0 + - parameter: null + landing_request_invalid: + - parameter: "moo" + - parameter: "5" + - parameter: "5.0" + landing_request_internal_valid: + - {} + - parameter: 5 + - parameter: 5.0 + - parameter: null + landing_request_internal_invalid: + - parameter: "moo" + - parameter: "5" + - parameter: "5.0" test_case_xml_valid: - parameter: 5 - parameter: 5.0 @@ -616,6 +842,24 @@ gx_data: # the difference between request internal and request internal dereferenced is that these have been converted # to datasets. - parameter: {src: url, url: "https://raw.githubusercontent.com/galaxyproject/planemo/7be1bf5b3971a43eaa73f483125bfb8cabf1c440/tests/data/hello.txt", ext: "txt"} + landing_request_valid: + - parameter: {src: hda, id: abcdabcd} + - parameter: {src: url, url: "https://raw.githubusercontent.com/galaxyproject/planemo/7be1bf5b3971a43eaa73f483125bfb8cabf1c440/tests/data/hello.txt", "ext": "txt"} + - parameter: {__class__: "Batch", values: [{src: hdca, id: abcdabcd}]} + - {} + - parameter: {src: url, url: "https://raw.githubusercontent.com/galaxyproject/planemo/7be1bf5b3971a43eaa73f483125bfb8cabf1c440/tests/data/hello.txt", "ext": "txt"} + landing_request_invalid: + - parameter: {__class__: "Batch", values: [{src: hdca, id: 5}]} + - parameter: {src: hda, id: 5} + - parameter: {src: hda, id: 0} + landing_request_internal_valid: + - parameter: {src: hda, id: 5} + - parameter: {__class__: "Batch", values: [{src: hdca, id: 5}]} + - {} + - parameter: {src: url, url: "https://raw.githubusercontent.com/galaxyproject/planemo/7be1bf5b3971a43eaa73f483125bfb8cabf1c440/tests/data/hello.txt", "ext": "txt"} + landing_request_internal_invalid: + - parameter: {src: hda, id: abcdabcd} + - parameter: {__class__: "Batch", values: [{src: hdca, id: abcdabcd}]} job_internal_valid: - parameter: {src: hda, id: 7} job_internal_invalid: @@ -744,6 +988,16 @@ gx_data_multiple: # the difference between request internal and request internal dereferenced is that these have been converted # to datasets. - parameter: [{src: url, url: "https://raw.githubusercontent.com/galaxyproject/planemo/7be1bf5b3971a43eaa73f483125bfb8cabf1c440/tests/data/hello.txt", ext: "txt"}] + landing_request_valid: + - parameter: [{src: hda, id: abcdabcd}] + - {} + landing_request_invalid: + - parameter: [{src: hda, id: 6}] + landing_request_internal_valid: + - parameter: [{src: hda, id: 6}] + - {} + landing_request_internal_invalid: + - parameter: [{src: hda, id: abcdabcd}] job_internal_valid: - parameter: {src: hda, id: 5} - parameter: [{src: hda, id: 5}, {src: hda, id: 6}] @@ -824,6 +1078,18 @@ gx_data_collection: - parameter: {src: hdca, id: 5} request_internal_dereferenced_invalid: - parameter: {src: hdca, id: abcdabcd} + landing_request_valid: + - parameter: {src: hdca, id: abcdabcd} + - {} + landing_request_invalid: + - parameter: {src: hdca, id: 5} + - parameter: [{src: hdca, id: abcdabcd}] + landing_request_internal_valid: + - parameter: {src: hdca, id: 5} + - {} + landing_request_internal_invalid: + - parameter: {src: hdca, id: abcdabcd} + - parameter: [{src: hdca, id: abcdabcd}] workflow_step_valid: - {} workflow_step_invalid: @@ -909,6 +1175,20 @@ gx_data_collection_optional: - parameter: 5 - parameter: "5" - parameter: {} + landing_request_valid: + - parameter: {src: hdca, id: abcdabcd} + - parameter: null + - {} + landing_request_invalid: + - parameter: {src: hdca, id: 5} + - parameter: [{src: hdca, id: abcdabcd}] + landing_request_internal_valid: + - parameter: {src: hdca, id: 5} + - parameter: null + - {} + landing_request_internal_invalid: + - parameter: {src: hdca, id: abcdabcd} + - parameter: [{src: hdca, id: abcdabcd}] job_internal_valid: - parameter: {src: hdca, id: 5} - parameter: null @@ -957,6 +1237,16 @@ gx_conditional_boolean: # in that case having an integer_parameter is not acceptable. - conditional_parameter: integer_parameter: 5 + landing_request_valid: &gx_conditional_boolean_landing_request_valid + - {} + landing_request_invalid: &gx_conditional_boolean_landing_request_invalid + - conditional_parameter: + test_parameter: false + integer_parameter: 1 + landing_request_internal_valid: + *gx_conditional_boolean_landing_request_valid + landing_request_internal_invalid: + *gx_conditional_boolean_landing_request_invalid job_internal_valid: - conditional_parameter: test_parameter: true @@ -1106,6 +1396,20 @@ gx_repeat_boolean: - { boolean_parameter: false } - { boolean_parameter: 4 } - parameter: 5 + landing_request_valid: &gx_repeat_boolean_landing_request_valid + - {} + - parameter: + - { boolean_parameter: true } + - { boolean_parameter: false } + - parameter: [{}] + - parameter: [{}, {}] + landing_request_invalid: &gx_repeat_boolean_landing_request_invalid + - parameter: + - { boolean_parameter: 4 } + landing_request_internal_valid: + *gx_repeat_boolean_landing_request_valid + landing_request_internal_invalid: + *gx_repeat_boolean_landing_request_invalid job_internal_valid: - parameter: - { boolean_parameter: true } @@ -1174,6 +1478,16 @@ gx_repeat_data: request_internal_invalid: - parameter: - { data_parameter: { src: hda, id: abcdabcd } } + landing_request_valid: &gx_repeat_data_landing_request_valid + - {} + # unlike above - in landing mode all parameters are optional so it is find to have an unset data parameters + - parameter: [{}] + landing_request_invalid: &gx_repeat_data_landing_request_invalid + - parameter: 5 + landing_request_internal_valid: + *gx_repeat_data_landing_request_valid + landing_request_internal_invalid: + *gx_repeat_data_landing_request_invalid job_internal_valid: - parameter: - { data_parameter: { src: hda, id: 5 } } @@ -1197,6 +1511,22 @@ gx_repeat_data_min: - parameter: [{}, {}] - parameter: [{}] - parameter: 5 + landing_request_valid: + - {} + # ignore the min here - can just specify the first set of parameters and I think that should work + - parameter: + - { data_parameter: {src: hda, id: abcdabcd} } + landing_request_invalid: + - parameter: + - { data_parameter: {src: hda, id: 5} } + landing_request_internal_valid: + - {} + # ignore the min here - can just specify the first set of parameters and I think that should work + - parameter: + - { data_parameter: {src: hda, id: 5} } + landing_request_internal_invalid: + - parameter: + - { data_parameter: {src: hda, id: abcdabcd} } request_internal_valid: - parameter: - { data_parameter: { src: hda, id: 5 } } diff --git a/test/unit/tool_util/test_parameter_convert.py b/test/unit/tool_util/test_parameter_convert.py index f86750026766..a14eab6d5657 100644 --- a/test/unit/tool_util/test_parameter_convert.py +++ b/test/unit/tool_util/test_parameter_convert.py @@ -12,6 +12,9 @@ encode, fill_static_defaults, input_models_for_tool_source, + landing_decode, + landing_encode, + LandingRequestToolState, RequestInternalDereferencedToolState, RequestInternalToolState, RequestToolState, @@ -102,6 +105,20 @@ def test_multi_data(): assert encoded_state.input_state["parameter"][1]["id"] == EXAMPLE_ID_2_ENCODED +def test_landing_encode_data(): + tool_source = tool_source_for("parameters/gx_data") + bundle = input_models_for_tool_source(tool_source) + request_state = LandingRequestToolState({"parameter": {"src": "hda", "id": EXAMPLE_ID_1_ENCODED}}) + request_state.validate(bundle) + decoded_state = landing_decode(request_state, bundle, _fake_decode) + assert decoded_state.input_state["parameter"]["src"] == "hda" + assert decoded_state.input_state["parameter"]["id"] == EXAMPLE_ID_1 + + encoded_state = landing_encode(decoded_state, bundle, _fake_encode) + assert encoded_state.input_state["parameter"]["src"] == "hda" + assert encoded_state.input_state["parameter"]["id"] == EXAMPLE_ID_1_ENCODED + + def test_dereference(): tool_source = tool_source_for("parameters/gx_data") bundle = input_models_for_tool_source(tool_source) diff --git a/test/unit/tool_util/test_parameter_specification.py b/test/unit/tool_util/test_parameter_specification.py index 012213a02499..ed4b437449b5 100644 --- a/test/unit/tool_util/test_parameter_specification.py +++ b/test/unit/tool_util/test_parameter_specification.py @@ -18,8 +18,10 @@ RequestToolState, ToolParameterBundleModel, validate_internal_job, + validate_internal_landing_request, validate_internal_request, validate_internal_request_dereferenced, + validate_landing_request, validate_request, validate_test_case, validate_workflow_step, @@ -97,6 +99,10 @@ def _test_file(file: str, specification=None, parameter_bundle: Optional[ToolPar "request_internal_invalid": _assert_internal_requests_invalid, "request_internal_dereferenced_valid": _assert_internal_requests_dereferenced_validate, "request_internal_dereferenced_invalid": _assert_internal_requests_dereferenced_invalid, + "landing_request_valid": _assert_landing_requests_validate, + "landing_request_invalid": _assert_landing_requests_invalid, + "landing_request_internal_valid": _assert_internal_landing_requests_validate, + "landing_request_internal_invalid": _assert_internal_landing_requests_invalid, "job_internal_valid": _assert_internal_jobs_validate, "job_internal_invalid": _assert_internal_jobs_invalid, "test_case_xml_valid": _assert_test_cases_validate, @@ -165,6 +171,12 @@ def _assert_invalid(parameters: ToolParameterBundleModel, request: RawStateDict) _assert_workflow_step_linked_validates, _assert_workflow_step_linked_invalid = model_assertion_function_factory( validate_workflow_step_linked, "linked workflow step tool state" ) +_assert_landing_request_validates, _assert_landing_request_invalid = model_assertion_function_factory( + validate_landing_request, "landing request" +) +_assert_internal_landing_request_validates, _assert_internal_landing_request_invalid = model_assertion_function_factory( + validate_internal_landing_request, " internallanding request" +) _assert_requests_validate = partial(_for_each, _assert_request_validates) _assert_requests_invalid = partial(_for_each, _assert_request_invalid) @@ -180,6 +192,10 @@ def _assert_invalid(parameters: ToolParameterBundleModel, request: RawStateDict) _assert_workflow_steps_invalid = partial(_for_each, _assert_workflow_step_invalid) _assert_workflow_steps_linked_validate = partial(_for_each, _assert_workflow_step_linked_validates) _assert_workflow_steps_linked_invalid = partial(_for_each, _assert_workflow_step_linked_invalid) +_assert_landing_requests_validate = partial(_for_each, _assert_landing_request_validates) +_assert_landing_requests_invalid = partial(_for_each, _assert_landing_request_invalid) +_assert_internal_landing_requests_validate = partial(_for_each, _assert_internal_landing_request_validates) +_assert_internal_landing_requests_invalid = partial(_for_each, _assert_internal_landing_request_invalid) def decode_val(val: str) -> int: