mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-19 10:51:34 +08:00
Merge pull request #14955 from jmchilton/default_uris_2
Implement default locations for data and collection parameters.
This commit is contained in:
@@ -1005,7 +1005,7 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
for pja in step.post_job_actions
|
||||
]
|
||||
else:
|
||||
inputs = step.module.get_runtime_inputs(connections=step.output_connections)
|
||||
inputs = step.module.get_runtime_inputs(step, connections=step.output_connections)
|
||||
step_model = {"inputs": [input.to_dict(trans) for input in inputs.values()]}
|
||||
step_model["when"] = step.when_expression
|
||||
step_model["replacement_parameters"] = step.module.get_informal_replacement_parameters(step)
|
||||
@@ -1157,7 +1157,7 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
else:
|
||||
module = step.module
|
||||
step_dict["label"] = module.name
|
||||
step_dict["inputs"] = do_inputs(module.get_runtime_inputs(), step.state.inputs, "", step)
|
||||
step_dict["inputs"] = do_inputs(module.get_runtime_inputs(step), step.state.inputs, "", step)
|
||||
step_dicts.append(step_dict)
|
||||
return {
|
||||
"name": workflow.name,
|
||||
@@ -1806,6 +1806,11 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
|
||||
if "in" in step_dict:
|
||||
for input_name, input_dict in step_dict["in"].items():
|
||||
# This is just a bug in gxformat? I think the input
|
||||
# defaults should be called input to match the input modules's
|
||||
# input parameter name.
|
||||
if input_name == "default":
|
||||
input_name = "input"
|
||||
step_input = step.get_or_add_input(input_name)
|
||||
NO_DEFAULT_DEFINED = object()
|
||||
default = input_dict.get("default", NO_DEFAULT_DEFINED)
|
||||
|
||||
@@ -7625,10 +7625,20 @@ class WorkflowStep(Base, RepresentById):
|
||||
|
||||
@property
|
||||
def input_default_value(self):
|
||||
tool_state = self.tool_inputs
|
||||
default_value = tool_state.get("default")
|
||||
if default_value:
|
||||
default_value = json.loads(default_value)["value"]
|
||||
self.get_input_default_value(None)
|
||||
|
||||
def get_input_default_value(self, default_default):
|
||||
# parameter_input and the data parameters handle this slightly differently
|
||||
# unfortunately.
|
||||
if self.type == "parameter_input":
|
||||
tool_state = self.tool_inputs
|
||||
default_value = tool_state.get("default", default_default)
|
||||
else:
|
||||
default_value = default_default
|
||||
for step_input in self.inputs:
|
||||
if step_input.name == "input" and step_input.default_value_set:
|
||||
default_value = step_input.default_value
|
||||
break
|
||||
return default_value
|
||||
|
||||
@property
|
||||
|
||||
@@ -425,6 +425,9 @@ class InputSource(metaclass=ABCMeta):
|
||||
def parse_when_input_sources(self):
|
||||
raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
|
||||
|
||||
def parse_default(self) -> Optional[Dict[str, Any]]:
|
||||
return None
|
||||
|
||||
|
||||
class PageSource(metaclass=ABCMeta):
|
||||
def parse_display(self):
|
||||
|
||||
@@ -5,7 +5,9 @@ import os
|
||||
import re
|
||||
import uuid
|
||||
from typing import (
|
||||
Any,
|
||||
cast,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
@@ -1274,6 +1276,56 @@ class XmlInputSource(InputSource):
|
||||
sources.append((value, case_page_source))
|
||||
return sources
|
||||
|
||||
def parse_default(self) -> Optional[Dict[str, Any]]:
|
||||
def file_default_from_elem(elem):
|
||||
# TODO: hashes, created_from_basename, etc...
|
||||
return {"class": "File", "location": elem.get("location")}
|
||||
|
||||
def read_elements(collection_elem):
|
||||
element_dicts = []
|
||||
elements = collection_elem.findall("element")
|
||||
for element in elements:
|
||||
identifier = element.get("name")
|
||||
subcollection_elem = element.find("collection")
|
||||
if subcollection_elem:
|
||||
collection_type = subcollection_elem.get("collection_type")
|
||||
element_dicts.append(
|
||||
{
|
||||
"class": "Collection",
|
||||
"identifier": identifier,
|
||||
"collection_type": collection_type,
|
||||
"elements": read_elements(subcollection_elem),
|
||||
}
|
||||
)
|
||||
else:
|
||||
element_dict = file_default_from_elem(element)
|
||||
element_dict["identifier"] = identifier
|
||||
element_dicts.append(element_dict)
|
||||
return element_dicts
|
||||
|
||||
elem = self.input_elem
|
||||
element_type = self.input_elem.get("type")
|
||||
if element_type == "data":
|
||||
default_elem = elem.find("default")
|
||||
if default_elem is not None:
|
||||
return file_default_from_elem(default_elem)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
default_elem = elem.find("default")
|
||||
if default_elem is not None:
|
||||
default_elem = elem.find("default")
|
||||
collection_type = default_elem.get("collection_type")
|
||||
name = default_elem.get("name", elem.get("name"))
|
||||
return {
|
||||
"class": "Collection",
|
||||
"name": name,
|
||||
"collection_type": collection_type,
|
||||
"elements": read_elements(default_elem),
|
||||
}
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class ParallelismInfo:
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import json
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
)
|
||||
|
||||
import packaging.version
|
||||
@@ -358,6 +360,11 @@ class YamlInputSource(InputSource):
|
||||
static_options.append((label, value, selected))
|
||||
return static_options
|
||||
|
||||
def parse_default(self) -> Optional[Dict[str, Any]]:
|
||||
input_dict = self.input_dict
|
||||
default_def = input_dict.get("default", None)
|
||||
return default_def
|
||||
|
||||
|
||||
def _ensure_has(dict, defaults):
|
||||
for key, value in defaults.items():
|
||||
|
||||
@@ -3731,6 +3731,7 @@ allow access to Python code to generate options for a select list. See
|
||||
<xs:element name="options" type="ParamOptions"/>
|
||||
<xs:element name="validator" type="Validator" />
|
||||
<xs:element name="sanitizer" type="Sanitizer"/>
|
||||
<xs:element name="default" type="ParamDefault" />
|
||||
<xs:element name="help" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation xml:lang="en">Documentation for help</xs:documentation>
|
||||
@@ -4115,6 +4116,73 @@ dataset for the contained input of the type specified using the ``type`` tag.
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="ParamDefault">
|
||||
<xs:annotation>
|
||||
<xs:documentation xml:lang="en"><![CDATA[
|
||||
]]>
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:sequence>
|
||||
<!-- can have zero or one collection elements -->
|
||||
<xs:element name="element" type="ParamDefaultElement" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="collection_type" type="CollectionType" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation xml:lang="en"><![CDATA[
|
||||
Collection type for default collection (if param type is data_collection). Simple collection types are
|
||||
either ``list`` or ``paired``, nested collections are specified as colon separated list of simple
|
||||
collection types (the most common types are ``list``, ``paired``,
|
||||
``list:paired``, or ``list:list``).
|
||||
]]></xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
|
||||
<xs:attribute name="location" type="xs:anyURI" gxdocs:added="23.2" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation xml:lang="en"><![CDATA[
|
||||
Galaxy-aware URI for the default file. This should only be used with parameters of type "data".
|
||||
]]></xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="ParamDefaultCollection">
|
||||
<xs:sequence>
|
||||
<xs:element name="element" type="ParamDefaultElement" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
|
||||
<xs:attribute name="collection_type" type="CollectionType" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation xml:lang="en"><![CDATA[
|
||||
Collection type for default collection (if param type is data_collection). Simple collection types are
|
||||
either ``list`` or ``paired``, nested collections are specified as colon separated list of simple
|
||||
collection types (the most common types are ``list``, ``paired``,
|
||||
``list:paired``, or ``list:list``).
|
||||
]]></xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="ParamDefaultElement">
|
||||
<xs:sequence>
|
||||
<xs:element name="collection" type="ParamDefaultCollection" minOccurs="0" maxOccurs="1" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation xml:lang="en">Name (and element identifier) for this element</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="location" type="xs:anyURI" gxdocs:added="23.2" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation xml:lang="en"><![CDATA[
|
||||
Galaxy-aware URI for the default file for collection element.
|
||||
]]></xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="ParamOptions">
|
||||
<xs:annotation>
|
||||
<xs:documentation xml:lang="en"><![CDATA[
|
||||
|
||||
@@ -8,6 +8,7 @@ import logging
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import urllib.parse
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
@@ -26,12 +27,16 @@ from galaxy.managers.dbkeys import read_dbnames
|
||||
from galaxy.model import (
|
||||
cached_id,
|
||||
Dataset,
|
||||
DatasetCollection,
|
||||
DatasetCollectionElement,
|
||||
DatasetHash,
|
||||
DatasetInstance,
|
||||
DatasetSource,
|
||||
HistoryDatasetAssociation,
|
||||
HistoryDatasetCollectionAssociation,
|
||||
LibraryDatasetDatasetAssociation,
|
||||
)
|
||||
from galaxy.model.dataset_collections import builder
|
||||
from galaxy.schema.fetch_data import FilesPayload
|
||||
from galaxy.tool_util.parser import get_input_source as ensure_input_source
|
||||
from galaxy.util import (
|
||||
@@ -43,6 +48,7 @@ from galaxy.util import (
|
||||
)
|
||||
from galaxy.util.dictifiable import Dictifiable
|
||||
from galaxy.util.expressions import ExpressionContext
|
||||
from galaxy.util.hash_util import HASH_NAMES
|
||||
from galaxy.util.rules_dsl import RuleSet
|
||||
from . import (
|
||||
dynamic_options,
|
||||
@@ -2090,6 +2096,11 @@ class DataToolParameter(BaseDataToolParameter):
|
||||
self._parse_options(input_source)
|
||||
# Load conversions required for the dataset input
|
||||
self.conversions = []
|
||||
self.default_object = input_source.parse_default()
|
||||
if self.optional and self.default_object is not None:
|
||||
raise ParameterValueError(
|
||||
"Cannot specify a Galaxy tool data parameter to be both optional and have a default value.", self.name
|
||||
)
|
||||
for name, conv_extension in input_source.parse_conversion_tuples():
|
||||
assert None not in [
|
||||
name,
|
||||
@@ -2110,9 +2121,11 @@ class DataToolParameter(BaseDataToolParameter):
|
||||
other_values = other_values or {}
|
||||
if trans.workflow_building_mode is workflow_building_modes.ENABLED or is_runtime_value(value):
|
||||
return None
|
||||
if not value and not self.optional:
|
||||
if not value and not self.optional and not self.default_object:
|
||||
raise ParameterValueError("specify a dataset of the required format / build for parameter", self.name)
|
||||
if value in [None, "None", ""]:
|
||||
if self.default_object:
|
||||
return raw_to_galaxy(trans, self.default_object)
|
||||
return None
|
||||
if isinstance(value, dict) and "values" in value:
|
||||
value = self.to_python(value, trans.app)
|
||||
@@ -2407,6 +2420,11 @@ class DataCollectionToolParameter(BaseDataToolParameter):
|
||||
self.multiple = False # Accessed on DataToolParameter a lot, may want in future
|
||||
self.is_dynamic = True
|
||||
self._parse_options(input_source) # TODO: Review and test.
|
||||
self.default_object = input_source.parse_default()
|
||||
if self.optional and self.default_object is not None:
|
||||
raise ParameterValueError(
|
||||
"Cannot specify a Galaxy tool data parameter to be both optional and have a default value.", self.name
|
||||
)
|
||||
|
||||
@property
|
||||
def collection_types(self):
|
||||
@@ -2443,9 +2461,11 @@ class DataCollectionToolParameter(BaseDataToolParameter):
|
||||
rval: Optional[Union[DatasetCollectionElement, HistoryDatasetCollectionAssociation]] = None
|
||||
if trans.workflow_building_mode is workflow_building_modes.ENABLED:
|
||||
return None
|
||||
if not value and not self.optional:
|
||||
if not value and not self.optional and not self.default_object:
|
||||
raise ParameterValueError("specify a dataset collection of the correct type", self.name)
|
||||
if value in [None, "None"]:
|
||||
if self.default_object:
|
||||
return raw_to_galaxy(trans, self.default_object)
|
||||
return None
|
||||
if isinstance(value, dict) and "values" in value:
|
||||
value = self.to_python(value, trans.app)
|
||||
@@ -2660,6 +2680,91 @@ class RulesListToolParameter(BaseJsonToolParameter):
|
||||
return ""
|
||||
|
||||
|
||||
# Code from CWL branch to massage in order to be shared across tools and workflows,
|
||||
# and for CWL artifacts as well as Galaxy ones.
|
||||
def raw_to_galaxy(trans, as_dict_value):
|
||||
app = trans.app
|
||||
history = trans.history
|
||||
|
||||
object_class = as_dict_value["class"]
|
||||
if object_class == "File":
|
||||
# TODO: relative_to = "/"
|
||||
location = as_dict_value.get("location")
|
||||
name = (
|
||||
as_dict_value.get("identifier")
|
||||
or as_dict_value.get("basename")
|
||||
or os.path.basename(urllib.parse.urlparse(location).path)
|
||||
)
|
||||
extension = as_dict_value.get("format") or "data"
|
||||
dataset = Dataset()
|
||||
source = DatasetSource()
|
||||
source.source_uri = location
|
||||
# TODO: validate this...
|
||||
source.transform = as_dict_value.get("transform")
|
||||
dataset.sources.append(source)
|
||||
|
||||
for hash_name in HASH_NAMES:
|
||||
# TODO: Convert md5 -> MD5 during tool parsing.
|
||||
if hash_name in as_dict_value:
|
||||
hash_object = DatasetHash()
|
||||
hash_object.hash_function = hash_name
|
||||
hash_object.hash_value = as_dict_value[hash_name]
|
||||
dataset.hashes.append(hash_object)
|
||||
|
||||
if "created_from_basename" in as_dict_value:
|
||||
dataset.created_from_basename = as_dict_value["created_from_basename"]
|
||||
|
||||
dataset.state = Dataset.states.DEFERRED
|
||||
primary_data = HistoryDatasetAssociation(
|
||||
name=name,
|
||||
extension=extension,
|
||||
metadata_deferred=True,
|
||||
designation=None,
|
||||
visible=True,
|
||||
dbkey="?",
|
||||
dataset=dataset,
|
||||
flush=False,
|
||||
sa_session=trans.sa_session,
|
||||
)
|
||||
primary_data.state = Dataset.states.DEFERRED
|
||||
permissions = app.security_agent.history_get_default_permissions(history)
|
||||
app.security_agent.set_all_dataset_permissions(primary_data.dataset, permissions, new=True, flush=False)
|
||||
trans.sa_session.add(primary_data)
|
||||
history.stage_addition(primary_data)
|
||||
history.add_pending_items()
|
||||
trans.sa_session.flush()
|
||||
return primary_data
|
||||
else:
|
||||
name = as_dict_value.get("name")
|
||||
collection_type = as_dict_value.get("collection_type")
|
||||
collection = DatasetCollection(
|
||||
collection_type=collection_type,
|
||||
)
|
||||
hdca = HistoryDatasetCollectionAssociation(
|
||||
name=name,
|
||||
collection=collection,
|
||||
)
|
||||
|
||||
def write_elements_to_collection(has_elements, collection_builder):
|
||||
element_dicts = has_elements.get("elements")
|
||||
for element_dict in element_dicts:
|
||||
element_class = element_dict["class"]
|
||||
identifier = element_dict["identifier"]
|
||||
if element_class == "File":
|
||||
hda = raw_to_galaxy(trans, element_dict)
|
||||
collection_builder.add_dataset(identifier, hda)
|
||||
else:
|
||||
subcollection_builder = collection_builder.get_level(identifier)
|
||||
write_elements_to_collection(element_dict, subcollection_builder)
|
||||
|
||||
collection_builder = builder.BoundCollectionBuilder(collection)
|
||||
write_elements_to_collection(as_dict_value, collection_builder)
|
||||
collection_builder.populate()
|
||||
trans.sa_session.add(hdca)
|
||||
trans.sa_session.flush()
|
||||
return hdca
|
||||
|
||||
|
||||
parameter_types = dict(
|
||||
text=TextToolParameter,
|
||||
integer=IntegerToolParameter,
|
||||
|
||||
@@ -75,6 +75,7 @@ from galaxy.tools.parameters.basic import (
|
||||
IntegerToolParameter,
|
||||
is_runtime_value,
|
||||
parameter_types,
|
||||
raw_to_galaxy,
|
||||
runtime_to_json,
|
||||
SelectToolParameter,
|
||||
TextToolParameter,
|
||||
@@ -98,6 +99,8 @@ from galaxy.util.tool_shed.common_util import get_tool_shed_url_from_tool_shed_r
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from galaxy.schema.invocation import InvocationMessageUnion
|
||||
from galaxy.workflow.run import WorkflowProgress
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -175,7 +178,7 @@ def to_cwl(value, hda_references, step):
|
||||
return value
|
||||
|
||||
|
||||
def from_cwl(value, hda_references, progress):
|
||||
def from_cwl(value, hda_references, progress: "WorkflowProgress"):
|
||||
# TODO: turn actual files into HDAs here ... somehow I suppose. Things with
|
||||
# file:// locations for instance.
|
||||
if isinstance(value, dict) and "class" in value and "location" in value:
|
||||
@@ -395,7 +398,7 @@ class WorkflowModule:
|
||||
def get_runtime_state(self) -> DefaultToolState:
|
||||
raise TypeError("Abstract method")
|
||||
|
||||
def get_runtime_inputs(self, **kwds):
|
||||
def get_runtime_inputs(self, step, connections: Optional[Iterable[WorkflowStepConnection]] = None):
|
||||
"""Used internally by modules and when displaying inputs in workflow
|
||||
editor and run workflow templates.
|
||||
"""
|
||||
@@ -432,7 +435,7 @@ class WorkflowModule:
|
||||
return NO_REPLACEMENT
|
||||
|
||||
visit_input_values(
|
||||
self.get_runtime_inputs(connections=step.output_connections),
|
||||
self.get_runtime_inputs(step, connections=step.output_connections),
|
||||
state.inputs,
|
||||
update_value,
|
||||
no_replacement_value=NO_REPLACEMENT,
|
||||
@@ -449,19 +452,19 @@ class WorkflowModule:
|
||||
return NO_REPLACEMENT
|
||||
|
||||
visit_input_values(
|
||||
self.get_runtime_inputs(), state.inputs, update_value, no_replacement_value=NO_REPLACEMENT
|
||||
self.get_runtime_inputs(step), state.inputs, update_value, no_replacement_value=NO_REPLACEMENT
|
||||
)
|
||||
|
||||
return state, step_errors
|
||||
|
||||
def encode_runtime_state(self, runtime_state):
|
||||
def encode_runtime_state(self, step, runtime_state):
|
||||
"""Takes the computed runtime state and serializes it during run request creation."""
|
||||
return runtime_state.encode(Bunch(inputs=self.get_runtime_inputs()), self.trans.app)
|
||||
return runtime_state.encode(Bunch(inputs=self.get_runtime_inputs(step)), self.trans.app)
|
||||
|
||||
def decode_runtime_state(self, runtime_state):
|
||||
def decode_runtime_state(self, step, runtime_state):
|
||||
"""Takes the serialized runtime state and decodes it when running the workflow."""
|
||||
state = DefaultToolState()
|
||||
state.decode(runtime_state, Bunch(inputs=self.get_runtime_inputs()), self.trans.app)
|
||||
state.decode(runtime_state, Bunch(inputs=self.get_runtime_inputs(step)), self.trans.app)
|
||||
return state
|
||||
|
||||
def execute(self, trans, progress, invocation_step, use_cached_job=False):
|
||||
@@ -533,7 +536,7 @@ class WorkflowModule:
|
||||
|
||||
for input_dict in all_inputs:
|
||||
name = input_dict["name"]
|
||||
data = progress.replacement_for_input(step, input_dict)
|
||||
data = progress.replacement_for_input(self.trans, step, input_dict)
|
||||
can_map_over = hasattr(data, "collection") # and data.collection.allow_implicit_mapping
|
||||
|
||||
if not can_map_over:
|
||||
@@ -825,7 +828,7 @@ class SubWorkflowModule(WorkflowModule):
|
||||
state.inputs = dict()
|
||||
return state
|
||||
|
||||
def get_runtime_inputs(self, connections=None):
|
||||
def get_runtime_inputs(self, step, connections: Optional[Iterable[WorkflowStepConnection]] = None):
|
||||
inputs = {}
|
||||
for step in self.subworkflow.steps:
|
||||
if step.type == "tool":
|
||||
@@ -932,7 +935,13 @@ class InputModule(WorkflowModule):
|
||||
def execute(self, trans, progress, invocation_step, use_cached_job=False):
|
||||
invocation = invocation_step.workflow_invocation
|
||||
step = invocation_step.workflow_step
|
||||
step_outputs = dict(output=step.state.inputs["input"])
|
||||
input_value = step.state.inputs["input"]
|
||||
if input_value is None:
|
||||
default_value = step.get_input_default_value(NO_REPLACEMENT)
|
||||
if default_value is not NO_REPLACEMENT:
|
||||
input_value = raw_to_galaxy(trans, default_value)
|
||||
|
||||
step_outputs = dict(output=input_value)
|
||||
|
||||
# Web controller may set copy_inputs_to_history, API controller always sets
|
||||
# inputs.
|
||||
@@ -1025,7 +1034,7 @@ class InputDataModule(InputModule):
|
||||
filter_set = ["data"]
|
||||
return ", ".join(filter_set)
|
||||
|
||||
def get_runtime_inputs(self, connections=None):
|
||||
def get_runtime_inputs(self, step, connections: Optional[Iterable[WorkflowStepConnection]] = None):
|
||||
parameter_def = self._parse_state_into_dict()
|
||||
optional = parameter_def["optional"]
|
||||
tag = parameter_def["tag"]
|
||||
@@ -1037,6 +1046,10 @@ class InputDataModule(InputModule):
|
||||
data_src = dict(
|
||||
name="input", label=self.label, multiple=False, type="data", format=formats, tag=tag, optional=optional
|
||||
)
|
||||
default_unset = object()
|
||||
default = step.get_input_default_value(default_unset)
|
||||
if default is not default_unset:
|
||||
data_src["default"] = default
|
||||
input_param = DataToolParameter(None, data_src, self.trans)
|
||||
return dict(input=input_param)
|
||||
|
||||
@@ -1096,7 +1109,7 @@ class InputDataCollectionModule(InputModule):
|
||||
inputs["tag"] = input_tag
|
||||
return inputs
|
||||
|
||||
def get_runtime_inputs(self, **kwds):
|
||||
def get_runtime_inputs(self, step, connections: Optional[Iterable[WorkflowStepConnection]] = None):
|
||||
parameter_def = self._parse_state_into_dict()
|
||||
collection_type = parameter_def["collection_type"]
|
||||
optional = parameter_def["optional"]
|
||||
@@ -1366,7 +1379,7 @@ class InputParameterModule(WorkflowModule):
|
||||
parameter_type_cond.cases = cases
|
||||
return {"parameter_definition": parameter_type_cond}
|
||||
|
||||
def restrict_options(self, connections: Iterable[WorkflowStepConnection], default_value):
|
||||
def restrict_options(self, step, connections: Iterable[WorkflowStepConnection], default_value):
|
||||
try:
|
||||
static_options = []
|
||||
# Retrieve possible runtime options for 'select' type inputs
|
||||
@@ -1389,7 +1402,7 @@ class InputParameterModule(WorkflowModule):
|
||||
for step in module.subworkflow.input_steps:
|
||||
if step.input_type == "parameter" and step.label == subworkflow_input_name:
|
||||
static_options.append(
|
||||
step.module.get_runtime_inputs(connections=step.output_connections)[
|
||||
step.module.get_runtime_inputs(step, connections=step.output_connections)[
|
||||
"input"
|
||||
].static_options
|
||||
)
|
||||
@@ -1421,7 +1434,7 @@ class InputParameterModule(WorkflowModule):
|
||||
except Exception:
|
||||
log.debug("Failed to generate options for text parameter, falling back to free text.", exc_info=True)
|
||||
|
||||
def get_runtime_inputs(self, connections: Optional[Iterable[WorkflowStepConnection]] = None, **kwds):
|
||||
def get_runtime_inputs(self, step, connections: Optional[Iterable[WorkflowStepConnection]] = None):
|
||||
parameter_def = self._parse_state_into_dict()
|
||||
parameter_type = parameter_def["parameter_type"]
|
||||
optional = parameter_def["optional"]
|
||||
@@ -1440,7 +1453,7 @@ class InputParameterModule(WorkflowModule):
|
||||
attemptRestrictOnConnections = is_text and parameter_def.get("restrictOnConnections") and connections
|
||||
if attemptRestrictOnConnections:
|
||||
connections = cast(Iterable[WorkflowStepConnection], connections)
|
||||
restricted_options = self.restrict_options(connections=connections, default_value=default_value)
|
||||
restricted_options = self.restrict_options(step, connections=connections, default_value=default_value)
|
||||
if restricted_options is not None:
|
||||
restricted_inputs = True
|
||||
parameter_kwds["options"] = restricted_options
|
||||
@@ -1518,7 +1531,7 @@ class InputParameterModule(WorkflowModule):
|
||||
step = invocation_step.workflow_step
|
||||
input_value = step.state.inputs["input"]
|
||||
if input_value is None:
|
||||
default_value = safe_loads(step.tool_inputs.get("default", "{}"))
|
||||
default_value = step.get_input_default_value(NO_REPLACEMENT)
|
||||
# TODO: look at parameter type and infer if value should be a dictionary
|
||||
# instead. Guessing only field parameter types in CWL branch would have
|
||||
# default as dictionary like this.
|
||||
@@ -1985,7 +1998,7 @@ class ToolModule(WorkflowModule):
|
||||
output_step for output_step in steps if connection.output_step_id == output_step.id
|
||||
)
|
||||
if output_step.type.startswith("data"):
|
||||
output_inputs = output_step.module.get_runtime_inputs(connections=connections)
|
||||
output_inputs = output_step.module.get_runtime_inputs(output_step, connections=connections)
|
||||
output_value = output_inputs["input"].get_initial_value(self.trans, context)
|
||||
if input_type == "data" and isinstance(
|
||||
output_value, self.trans.app.model.HistoryDatasetCollectionAssociation
|
||||
@@ -2087,7 +2100,7 @@ class ToolModule(WorkflowModule):
|
||||
state.inputs = self.state.inputs
|
||||
return state
|
||||
|
||||
def get_runtime_inputs(self, **kwds):
|
||||
def get_runtime_inputs(self, step, connections: Optional[Iterable[WorkflowStepConnection]] = None):
|
||||
return self.get_inputs()
|
||||
|
||||
def compute_runtime_state(self, trans, step=None, step_updates=None):
|
||||
@@ -2108,12 +2121,12 @@ class ToolModule(WorkflowModule):
|
||||
f"Tool {self.tool_id} missing. Cannot compute runtime state.", tool_id=self.tool_id
|
||||
)
|
||||
|
||||
def decode_runtime_state(self, runtime_state):
|
||||
def decode_runtime_state(self, step, runtime_state):
|
||||
"""Take runtime state from persisted invocation and convert it
|
||||
into a DefaultToolState object for use during workflow invocation.
|
||||
"""
|
||||
if self.tool:
|
||||
state = super().decode_runtime_state(runtime_state)
|
||||
state = super().decode_runtime_state(step, runtime_state)
|
||||
if RUNTIME_STEP_META_STATE_KEY in runtime_state:
|
||||
self.__restore_step_meta_runtime_state(json.loads(runtime_state[RUNTIME_STEP_META_STATE_KEY]))
|
||||
return state
|
||||
@@ -2169,7 +2182,7 @@ class ToolModule(WorkflowModule):
|
||||
if iteration_elements and prefixed_name in iteration_elements: # noqa: B023
|
||||
replacement = iteration_elements[prefixed_name] # noqa: B023
|
||||
else:
|
||||
replacement = progress.replacement_for_input(step, input_dict)
|
||||
replacement = progress.replacement_for_input(trans, step, input_dict)
|
||||
|
||||
if replacement is not NO_REPLACEMENT:
|
||||
if not isinstance(input, BaseDataToolParameter):
|
||||
@@ -2214,7 +2227,7 @@ class ToolModule(WorkflowModule):
|
||||
if iteration_elements and step_input_name in iteration_elements: # noqa: B023
|
||||
value = iteration_elements[step_input_name] # noqa: B023
|
||||
else:
|
||||
value = progress.replacement_for_input(step, all_inputs_by_name[step_input_name])
|
||||
value = progress.replacement_for_input(trans, step, all_inputs_by_name[step_input_name])
|
||||
# TODO: only do this for values... is everything with a default
|
||||
# this way a field parameter? I guess not?
|
||||
extra_step_state[step_input_name] = value
|
||||
|
||||
@@ -31,6 +31,7 @@ from galaxy.schema.invocation import (
|
||||
InvocationWarningWorkflowOutputNotFound,
|
||||
WarningReason,
|
||||
)
|
||||
from galaxy.tools.parameters.basic import raw_to_galaxy
|
||||
from galaxy.util import ExecutionTimer
|
||||
from galaxy.workflow import modules
|
||||
from galaxy.workflow.run_request import (
|
||||
@@ -327,6 +328,8 @@ STEP_OUTPUT_DELAYED = object()
|
||||
|
||||
|
||||
class ModuleInjector(Protocol):
|
||||
trans: "WorkRequestContext"
|
||||
|
||||
def inject(self, step, step_args=None, steps=None, **kwargs):
|
||||
pass
|
||||
|
||||
@@ -399,7 +402,7 @@ class WorkflowProgress:
|
||||
raise MessageException(public_message)
|
||||
runtime_state = step_states[step_id].value
|
||||
assert step.module
|
||||
step.state = step.module.decode_runtime_state(runtime_state)
|
||||
step.state = step.module.decode_runtime_state(step, runtime_state)
|
||||
|
||||
invocation_step = step_invocations_by_id.get(step_id, None)
|
||||
if invocation_step and invocation_step.state == "scheduled":
|
||||
@@ -408,7 +411,7 @@ class WorkflowProgress:
|
||||
remaining_steps.append((step, invocation_step))
|
||||
return remaining_steps
|
||||
|
||||
def replacement_for_input(self, step: "WorkflowStep", input_dict: Dict[str, Any]) -> Any:
|
||||
def replacement_for_input(self, trans, step: "WorkflowStep", input_dict: Dict[str, Any]) -> Any:
|
||||
replacement: Union[
|
||||
modules.NoReplacement,
|
||||
model.DatasetCollectionInstance,
|
||||
@@ -416,6 +419,7 @@ class WorkflowProgress:
|
||||
] = modules.NO_REPLACEMENT
|
||||
prefixed_name = input_dict["name"]
|
||||
multiple = input_dict["multiple"]
|
||||
is_data = input_dict["input_type"] in ["dataset", "dataset_collection"]
|
||||
if prefixed_name in step.input_connections_by_name:
|
||||
connection = step.input_connections_by_name[prefixed_name]
|
||||
if input_dict["input_type"] == "dataset" and multiple:
|
||||
@@ -431,9 +435,12 @@ class WorkflowProgress:
|
||||
else:
|
||||
replacement = temp
|
||||
else:
|
||||
is_data = input_dict["input_type"] in ["dataset", "dataset_collection"]
|
||||
replacement = self.replacement_for_connection(connection[0], is_data=is_data)
|
||||
|
||||
else:
|
||||
for step_input in step.inputs:
|
||||
if step_input.name == prefixed_name and step_input.default_value_set:
|
||||
if is_data:
|
||||
replacement = raw_to_galaxy(trans, step_input.default_value)
|
||||
return replacement
|
||||
|
||||
def replacement_for_connection(self, connection: "WorkflowStepConnection", is_data: bool = True) -> Any:
|
||||
@@ -694,6 +701,9 @@ class WorkflowProgress:
|
||||
when_values=when_values,
|
||||
)
|
||||
|
||||
def raw_to_galaxy(self, value: dict):
|
||||
return raw_to_galaxy(self.module_injector.trans, value)
|
||||
|
||||
def _recover_mapping(self, step_invocation: WorkflowInvocationStep) -> None:
|
||||
try:
|
||||
step_invocation.workflow_step.module.recover_mapping(step_invocation, self)
|
||||
|
||||
@@ -119,13 +119,16 @@ def _normalize_inputs(
|
||||
for possible_input_key in possible_input_keys:
|
||||
if possible_input_key in inputs:
|
||||
inputs_key = possible_input_key
|
||||
default_value = step.tool_inputs.get("default")
|
||||
|
||||
default_not_set = object()
|
||||
has_default = step.get_input_default_value(default_not_set) is not default_not_set
|
||||
optional = step.input_optional
|
||||
# Need to be careful here to make sure 'default' has correct type - not sure how to do that
|
||||
# but asserting 'optional' is definitely a bool and not a String->Bool or something is a good
|
||||
# start to ensure tool state is being preserved and loaded in a type safe way.
|
||||
assert isinstance(optional, bool)
|
||||
if not inputs_key and default_value is None and not optional:
|
||||
assert isinstance(has_default, bool)
|
||||
if not inputs_key and not has_default and not optional:
|
||||
message = f"Workflow cannot be run because an expected input step '{step.id}' ({step.label}) is not optional and no input."
|
||||
raise exceptions.MessageException(message)
|
||||
if inputs_key:
|
||||
@@ -495,7 +498,7 @@ def workflow_run_config_to_request(
|
||||
for step in workflow.steps:
|
||||
steps_by_id[step.id] = step
|
||||
assert step.module
|
||||
serializable_runtime_state = step.module.encode_runtime_state(step.state)
|
||||
serializable_runtime_state = step.module.encode_runtime_state(step, step.state)
|
||||
|
||||
step_state = WorkflowRequestStepState()
|
||||
step_state.workflow_step = step
|
||||
|
||||
@@ -57,11 +57,13 @@ from galaxy_test.base.workflow_fixtures import (
|
||||
WORKFLOW_WITH_BAD_COLUMN_PARAMETER_GOOD_TEST_DATA,
|
||||
WORKFLOW_WITH_CUSTOM_REPORT_1,
|
||||
WORKFLOW_WITH_CUSTOM_REPORT_1_TEST_DATA,
|
||||
WORKFLOW_WITH_DEFAULT_FILE_DATASET_INPUT,
|
||||
WORKFLOW_WITH_DYNAMIC_OUTPUT_COLLECTION,
|
||||
WORKFLOW_WITH_MAPPED_OUTPUT_COLLECTION,
|
||||
WORKFLOW_WITH_OUTPUT_COLLECTION,
|
||||
WORKFLOW_WITH_OUTPUT_COLLECTION_MAPPING,
|
||||
WORKFLOW_WITH_RULES_1,
|
||||
WORKFLOW_WITH_STEP_DEFAULT_FILE_DATASET_INPUT,
|
||||
)
|
||||
from ._framework import ApiTestCase
|
||||
from .sharable import SharingApiTests
|
||||
@@ -4694,6 +4696,57 @@ data_input:
|
||||
content = self.dataset_populator.get_history_dataset_content(history_id)
|
||||
assert len(content.splitlines()) == 3, content
|
||||
|
||||
def test_run_with_default_file_dataset_input(self):
|
||||
with self.dataset_populator.test_history() as history_id:
|
||||
run_response = self._run_workflow(
|
||||
WORKFLOW_WITH_DEFAULT_FILE_DATASET_INPUT,
|
||||
history_id=history_id,
|
||||
wait=True,
|
||||
assert_ok=True,
|
||||
)
|
||||
invocation_details = self.workflow_populator.get_invocation(run_response.invocation_id, step_details=True)
|
||||
assert invocation_details["steps"][0]["outputs"]["output"]["src"] == "hda"
|
||||
dataset_details = self.dataset_populator.get_history_dataset_details(
|
||||
history_id, dataset_id=invocation_details["steps"][1]["outputs"]["out_file1"]["id"]
|
||||
)
|
||||
assert dataset_details["file_ext"] == "txt"
|
||||
assert "chr1" in dataset_details["peek"]
|
||||
|
||||
def test_run_with_default_file_dataset_input_and_explicit_input(self):
|
||||
with self.dataset_populator.test_history() as history_id:
|
||||
run_response = self._run_workflow(
|
||||
WORKFLOW_WITH_DEFAULT_FILE_DATASET_INPUT,
|
||||
test_data="""
|
||||
default_file_input:
|
||||
value: 1.fasta
|
||||
type: File
|
||||
""",
|
||||
history_id=history_id,
|
||||
wait=True,
|
||||
assert_ok=True,
|
||||
)
|
||||
invocation_details = self.workflow_populator.get_invocation(run_response.invocation_id, step_details=True)
|
||||
assert invocation_details["steps"][0]["outputs"]["output"]["src"] == "hda"
|
||||
dataset_details = self.dataset_populator.get_history_dataset_details(
|
||||
history_id, dataset_id=invocation_details["steps"][1]["outputs"]["out_file1"]["id"]
|
||||
)
|
||||
assert dataset_details["file_ext"] == "txt"
|
||||
assert (
|
||||
"gtttgccatcttttgctgctctagggaatccagcagctgtcaccatgtaaacaagcccaggctagaccaGTTACCCTCATCATCTTAGCTGATAGCCAGCCAGCCACCACAGGCA"
|
||||
in dataset_details["peek"]
|
||||
)
|
||||
|
||||
def test_run_with_default_file_in_step_inline(self):
|
||||
with self.dataset_populator.test_history() as history_id:
|
||||
self._run_workflow(
|
||||
WORKFLOW_WITH_STEP_DEFAULT_FILE_DATASET_INPUT,
|
||||
history_id=history_id,
|
||||
wait=True,
|
||||
assert_ok=True,
|
||||
)
|
||||
content = self.dataset_populator.get_history_dataset_content(history_id)
|
||||
assert "chr1" in content
|
||||
|
||||
def test_run_with_validated_parameter_connection_invalid(self):
|
||||
with self.dataset_populator.test_history() as history_id:
|
||||
self._run_jobs(
|
||||
|
||||
@@ -1147,3 +1147,33 @@ outputs:
|
||||
outer_output_2:
|
||||
outputSource: subworkflow/inner_output_2
|
||||
"""
|
||||
|
||||
WORKFLOW_WITH_DEFAULT_FILE_DATASET_INPUT = """
|
||||
class: GalaxyWorkflow
|
||||
inputs:
|
||||
default_file_input:
|
||||
default:
|
||||
class: File
|
||||
basename: a file
|
||||
format: txt
|
||||
location: https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed
|
||||
steps:
|
||||
cat1:
|
||||
tool_id: cat1
|
||||
in:
|
||||
input1: default_file_input
|
||||
"""
|
||||
|
||||
WORKFLOW_WITH_STEP_DEFAULT_FILE_DATASET_INPUT = """
|
||||
class: GalaxyWorkflow
|
||||
steps:
|
||||
cat1:
|
||||
tool_id: cat1
|
||||
in:
|
||||
input1:
|
||||
default:
|
||||
class: File
|
||||
basename: a file
|
||||
format: txt
|
||||
location: https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<tool id="collection_nested_default" name="collection_nested_default" version="0.1.0">
|
||||
<command>
|
||||
echo #for $f in $f1# ${f.is_collection} #end for# >> $out1;
|
||||
cat #for $f in $f1# #if $f.is_collection# #for $inner in $f# ${inner} #end for# #else# $f # #end if# #end for# >> $out2
|
||||
</command>
|
||||
<inputs>
|
||||
<param name="f1" type="data_collection">
|
||||
<default collection_type="list:paired">
|
||||
<element name="i1">
|
||||
<collection collection_type="paired">
|
||||
<element name="forward" location="https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed" />
|
||||
<element name="reverse" location="https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.fasta" />
|
||||
</collection>
|
||||
</element>
|
||||
</default>
|
||||
</param>
|
||||
</inputs>
|
||||
<outputs>
|
||||
<data format="txt" name="out1" />
|
||||
<data format="txt" name="out2" />
|
||||
</outputs>
|
||||
<tests>
|
||||
<test>
|
||||
<output name="out1">
|
||||
<assert_contents>
|
||||
<has_line line="True" />
|
||||
</assert_contents>
|
||||
</output>
|
||||
<output name="out2">
|
||||
<assert_contents>
|
||||
<has_text text="CCDS989.1_cds_0_0_chr1_147962193_r" />
|
||||
<has_text text=">hg17" />
|
||||
</assert_contents>
|
||||
</output>
|
||||
</test>
|
||||
<test>
|
||||
<param name="f1">
|
||||
<collection type="paired">
|
||||
<element name="forward" value="simple_line.txt" />
|
||||
<element name="reverse" value="simple_line_alternative.txt" />
|
||||
</collection>
|
||||
</param>
|
||||
<output name="out1">
|
||||
<assert_contents>
|
||||
<has_line line="False False" />
|
||||
</assert_contents>
|
||||
</output>
|
||||
</test>
|
||||
</tests>
|
||||
</tool>
|
||||
@@ -0,0 +1,40 @@
|
||||
<tool id="collection_paired_default" name="collection_paired_default" version="0.1.0">
|
||||
<command>
|
||||
cat $f1.forward $f1['reverse'] >> $out1;
|
||||
</command>
|
||||
<inputs>
|
||||
<param name="f1" type="data_collection" collection_type="paired" label="Input pair">
|
||||
<default collection_type="paired">
|
||||
<element name="forward" location="https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed" />
|
||||
<element name="reverse" location="https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.fasta" />
|
||||
</default>
|
||||
</param>
|
||||
</inputs>
|
||||
<outputs>
|
||||
<data format="txt" name="out1" />
|
||||
</outputs>
|
||||
<tests>
|
||||
<test>
|
||||
<param name="f1">
|
||||
<collection type="paired">
|
||||
<element name="forward" value="simple_line.txt" />
|
||||
<element name="reverse" value="simple_line_alternative.txt" />
|
||||
</collection>
|
||||
</param>
|
||||
<output name="out1">
|
||||
<assert_contents>
|
||||
<has_line line="This is a line of text." />
|
||||
<has_line line="This is a different line of text." />
|
||||
</assert_contents>
|
||||
</output>
|
||||
</test>
|
||||
<test>
|
||||
<output name="out1">
|
||||
<assert_contents>
|
||||
<has_text text="CCDS989.1_cds_0_0_chr1_147962193_r" />
|
||||
<has_text text=">hg17" />
|
||||
</assert_contents>
|
||||
</output>
|
||||
</test>
|
||||
</tests>
|
||||
</tool>
|
||||
@@ -0,0 +1,21 @@
|
||||
<tool id="cat_default" name="cat_default" version="1.0.0">
|
||||
<description></description>
|
||||
<command><![CDATA[
|
||||
cat '$input1' > '$out_file1'
|
||||
]]></command>
|
||||
<inputs>
|
||||
<param name="input1" type="data" label="Concatenate Dataset">
|
||||
<default location="https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed" />
|
||||
</param>
|
||||
</inputs>
|
||||
<outputs>
|
||||
<data name="out_file1" format="input" metadata_source="input1"/>
|
||||
</outputs>
|
||||
<tests>
|
||||
<test>
|
||||
<output name="out_file1" file="1.bed" />
|
||||
</test>
|
||||
</tests>
|
||||
<help>
|
||||
</help>
|
||||
</tool>
|
||||
@@ -176,9 +176,11 @@
|
||||
<tool file="output_action_change_format.xml" />
|
||||
<tool file="output_action_change_format_paired.xml" />
|
||||
<tool file="collection_paired_test.xml" />
|
||||
<tool file="collection_paired_default.xml" />
|
||||
<tool file="collection_paired_structured_like.xml" />
|
||||
<tool file="collection_paired_conditional_structured_like.xml" />
|
||||
<tool file="collection_nested_test.xml" />
|
||||
<tool file="collection_nested_default.xml" />
|
||||
<tool file="collection_mixed_param.xml" />
|
||||
<tool file="collection_two_paired.xml" />
|
||||
<tool file="collection_creates_pair.xml" />
|
||||
@@ -237,6 +239,7 @@
|
||||
parameter, and multiple datasets from a collection. -->
|
||||
<tool file="for_workflows/cat.xml" />
|
||||
<tool file="for_workflows/cat_list.xml" />
|
||||
<tool file="for_workflows/cat_default.xml" />
|
||||
<tool file="for_workflows/cat_collection.xml" />
|
||||
<tool file="for_workflows/head.xml" />
|
||||
<tool file="for_workflows/cat_interleave.xml" />
|
||||
|
||||
@@ -693,6 +693,79 @@ class TestExpressionTestToolLoader(BaseLoaderTestCase):
|
||||
assert output0["attributes"]["object"] is None
|
||||
|
||||
|
||||
class TestDefaultDataTestToolLoader(BaseLoaderTestCase):
|
||||
source_file_name = os.path.join(galaxy_directory(), "test/functional/tools/for_workflows/cat_default.xml")
|
||||
source_contents = None
|
||||
|
||||
def test_input_parsing(self):
|
||||
input_pages = self._tool_source.parse_input_pages()
|
||||
assert input_pages.inputs_defined
|
||||
page_sources = input_pages.page_sources
|
||||
assert len(page_sources) == 1
|
||||
page_source = page_sources[0]
|
||||
input_sources = page_source.parse_input_sources()
|
||||
assert len(input_sources) == 1
|
||||
data_input = input_sources[0]
|
||||
default_dict = data_input.parse_default()
|
||||
assert default_dict
|
||||
assert default_dict["location"] == "https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed"
|
||||
|
||||
|
||||
class TestDefaultCollectionDataTestToolLoader(BaseLoaderTestCase):
|
||||
source_file_name = os.path.join(galaxy_directory(), "test/functional/tools/collection_paired_default.xml")
|
||||
source_contents = None
|
||||
|
||||
def test_input_parsing(self):
|
||||
input_pages = self._tool_source.parse_input_pages()
|
||||
assert input_pages.inputs_defined
|
||||
page_sources = input_pages.page_sources
|
||||
assert len(page_sources) == 1
|
||||
page_source = page_sources[0]
|
||||
input_sources = page_source.parse_input_sources()
|
||||
assert len(input_sources) == 1
|
||||
data_input = input_sources[0]
|
||||
default_dict = data_input.parse_default()
|
||||
assert default_dict
|
||||
assert default_dict["collection_type"] == "paired"
|
||||
elements = default_dict["elements"]
|
||||
assert len(elements) == 2
|
||||
element0 = elements[0]
|
||||
assert element0["identifier"] == "forward"
|
||||
assert element0["location"] == "https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed"
|
||||
element1 = elements[1]
|
||||
assert element1["identifier"] == "reverse"
|
||||
assert element1["location"] == "https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.fasta"
|
||||
|
||||
|
||||
class TestDefaultNestedCollectionDataTestToolLoader(BaseLoaderTestCase):
|
||||
source_file_name = os.path.join(galaxy_directory(), "test/functional/tools/collection_nested_default.xml")
|
||||
source_contents = None
|
||||
|
||||
def test_input_parsing(self):
|
||||
input_pages = self._tool_source.parse_input_pages()
|
||||
assert input_pages.inputs_defined
|
||||
page_sources = input_pages.page_sources
|
||||
assert len(page_sources) == 1
|
||||
page_source = page_sources[0]
|
||||
input_sources = page_source.parse_input_sources()
|
||||
assert len(input_sources) == 1
|
||||
data_input = input_sources[0]
|
||||
default_dict = data_input.parse_default()
|
||||
assert default_dict
|
||||
assert default_dict["collection_type"] == "list:paired"
|
||||
elements = default_dict["elements"]
|
||||
assert len(elements) == 1
|
||||
element0 = elements[0]
|
||||
assert element0["identifier"] == "i1"
|
||||
|
||||
elements0 = element0["elements"]
|
||||
assert len(elements0) == 2
|
||||
elements00 = elements0[0]
|
||||
assert elements00["identifier"] == "forward"
|
||||
elements01 = elements0[1]
|
||||
assert elements01["identifier"] == "reverse"
|
||||
|
||||
|
||||
class TestExpressionOutputDataToolLoader(BaseLoaderTestCase):
|
||||
source_file_name = os.path.join(galaxy_directory(), "test/functional/tools/expression_pick_larger_file.xml")
|
||||
source_contents = None
|
||||
|
||||
@@ -427,7 +427,10 @@ def __new_subworkflow_module(workflow=TEST_WORKFLOW_YAML):
|
||||
|
||||
|
||||
def __assert_has_runtime_input(module, label=None, collection_type=None):
|
||||
inputs = module.get_runtime_inputs()
|
||||
test_step = getattr(module, "test_step", None)
|
||||
if test_step is None:
|
||||
test_step = mock.MagicMock()
|
||||
inputs = module.get_runtime_inputs(test_step)
|
||||
assert len(inputs) == 1
|
||||
assert "input" in inputs
|
||||
input_param = inputs["input"]
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
from typing import cast
|
||||
|
||||
from galaxy import model
|
||||
from galaxy.model.base import transaction
|
||||
from galaxy.util.unittest import TestCase
|
||||
from galaxy.workflow.run import WorkflowProgress
|
||||
from galaxy.workflow.run import (
|
||||
ModuleInjector,
|
||||
WorkflowProgress,
|
||||
)
|
||||
from .workflow_support import (
|
||||
MockApp,
|
||||
MockTrans,
|
||||
yaml_to_model,
|
||||
)
|
||||
|
||||
@@ -76,7 +82,8 @@ class TestWorkflowProgress(TestCase):
|
||||
self.invocation.workflow = workflow
|
||||
|
||||
def _new_workflow_progress(self):
|
||||
return WorkflowProgress(self.invocation, self.inputs_by_step_id, MockModuleInjector(self.progress), {})
|
||||
mock_injector: ModuleInjector = cast(ModuleInjector, MockModuleInjector(self.progress))
|
||||
return WorkflowProgress(self.invocation, self.inputs_by_step_id, mock_injector, {})
|
||||
|
||||
def _set_previous_progress(self, outputs):
|
||||
for i, (step_id, step_value) in enumerate(outputs):
|
||||
@@ -132,7 +139,7 @@ class TestWorkflowProgress(TestCase):
|
||||
"input_type": "dataset",
|
||||
"multiple": False,
|
||||
}
|
||||
replacement = progress.replacement_for_input(self._step(2), step_dict)
|
||||
replacement = progress.replacement_for_input(None, self._step(2), step_dict)
|
||||
assert replacement is hda
|
||||
|
||||
def test_connect_tool_output(self):
|
||||
@@ -169,7 +176,7 @@ class TestWorkflowProgress(TestCase):
|
||||
"input_type": "dataset",
|
||||
"multiple": False,
|
||||
}
|
||||
replacement = progress.replacement_for_input(self._step(4), step_dict)
|
||||
replacement = progress.replacement_for_input(None, self._step(4), step_dict)
|
||||
assert replacement is hda3
|
||||
|
||||
# TODO: Replace multiple true HDA with HDCA
|
||||
@@ -216,6 +223,7 @@ class TestWorkflowProgress(TestCase):
|
||||
"multiple": False,
|
||||
}
|
||||
assert hda is subworkflow_progress.replacement_for_input(
|
||||
None,
|
||||
subworkflow_cat_step,
|
||||
step_dict,
|
||||
)
|
||||
@@ -241,8 +249,9 @@ class MockModuleInjector:
|
||||
class MockModule:
|
||||
def __init__(self, progress):
|
||||
self.progress = progress
|
||||
self.trans = MockTrans()
|
||||
|
||||
def decode_runtime_state(self, runtime_state):
|
||||
def decode_runtime_state(self, step, runtime_state):
|
||||
return True
|
||||
|
||||
def recover_mapping(self, invocation_step, progress):
|
||||
|
||||
Reference in New Issue
Block a user