mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge branch 'release_24.2' into dev
This commit is contained in:
@@ -70,8 +70,8 @@ function executeActivity(activity: Activity) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="activity-settings rounded no-highlight">
|
||||
<div v-if="foundActivities" class="activity-settings-content">
|
||||
<div>
|
||||
<div v-if="foundActivities">
|
||||
<button
|
||||
v-for="activity in filteredActivities"
|
||||
:key="activity.id"
|
||||
@@ -122,7 +122,7 @@ function executeActivity(activity: Activity) {
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="activity-settings-content">
|
||||
<div v-else>
|
||||
<b-alert v-localize class="py-1 px-2" show> No matching activities found. </b-alert>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,16 +131,6 @@ function executeActivity(activity: Activity) {
|
||||
<style lang="scss">
|
||||
@import "theme/blue.scss";
|
||||
|
||||
.activity-settings {
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.activity-settings-content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.activity-settings-item {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -148,6 +138,7 @@ function executeActivity(activity: Activity) {
|
||||
transition: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.activity-settings-item:hover {
|
||||
background: $gray-200;
|
||||
}
|
||||
|
||||
@@ -104,11 +104,11 @@ onMounted(() => {
|
||||
<h3>Create Visualization</h3>
|
||||
<DelayedInput :delay="100" placeholder="Search visualizations" @change="query = $event" />
|
||||
</template>
|
||||
<div class="overflow-y mt-2">
|
||||
<div>
|
||||
<LoadingSpan v-if="isLoading" message="Loading visualizations" />
|
||||
<div v-else-if="filteredPlugins.length > 0">
|
||||
<div v-for="plugin in filteredPlugins" :key="plugin.name">
|
||||
<button :data-plugin-name="plugin.name" @click="selectVisualization(plugin)">
|
||||
<button class="plugin-item" :data-plugin-name="plugin.name" @click="selectVisualization(plugin)">
|
||||
<div class="d-flex">
|
||||
<div class="plugin-thumbnail mr-2">
|
||||
<img v-if="plugin.logo" alt="visualization" :src="absPath(plugin.logo)" />
|
||||
@@ -143,6 +143,20 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "theme/blue.scss";
|
||||
|
||||
.plugin-item {
|
||||
background: none;
|
||||
border: none;
|
||||
text-align: left;
|
||||
transition: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.plugin-item:hover {
|
||||
background: $gray-200;
|
||||
}
|
||||
|
||||
.plugin-thumbnail {
|
||||
img {
|
||||
width: 2rem;
|
||||
|
||||
@@ -8103,7 +8103,7 @@ class WorkflowStep(Base, RepresentById, UsesCreateAndUpdateTime):
|
||||
tool_errors: Mapped[Optional[bytes]] = mapped_column(JSONType)
|
||||
position: Mapped[Optional[bytes]] = mapped_column(MutableJSONType)
|
||||
config: Mapped[Optional[bytes]] = mapped_column(JSONType)
|
||||
order_index: Mapped[Optional[int]]
|
||||
order_index: Mapped[int]
|
||||
when_expression: Mapped[Optional[bytes]] = mapped_column(JSONType)
|
||||
uuid: Mapped[Optional[Union[UUID, str]]] = mapped_column(UUIDType)
|
||||
label: Mapped[Optional[str]] = mapped_column(Unicode(255))
|
||||
@@ -8201,17 +8201,18 @@ class WorkflowStep(Base, RepresentById, UsesCreateAndUpdateTime):
|
||||
# Ensure input_connections has already been set.
|
||||
|
||||
# Make connection information available on each step by input name.
|
||||
inputs_by_name = {}
|
||||
inputs_by_name: Dict[str, Any] = {}
|
||||
for step_input in self.inputs:
|
||||
input_name = step_input.name
|
||||
assert input_name not in inputs_by_name
|
||||
inputs_by_name[input_name] = step_input
|
||||
self._inputs_by_name = inputs_by_name
|
||||
return inputs_by_name
|
||||
|
||||
@property
|
||||
def inputs_by_name(self):
|
||||
if self._inputs_by_name is None:
|
||||
self.setup_inputs_by_name()
|
||||
return self.setup_inputs_by_name()
|
||||
return self._inputs_by_name
|
||||
|
||||
def get_input(self, input_name):
|
||||
@@ -8690,7 +8691,7 @@ class WorkflowInvocation(Base, UsesCreateAndUpdateTime, Dictifiable, Serializabl
|
||||
state: Mapped[Optional[str]] = mapped_column(TrimmedString(64), index=True)
|
||||
scheduler: Mapped[Optional[str]] = mapped_column(TrimmedString(255), index=True)
|
||||
handler: Mapped[Optional[str]] = mapped_column(TrimmedString(255), index=True)
|
||||
uuid: Mapped[Optional[Union[UUID, str]]] = mapped_column(UUIDType())
|
||||
uuid: Mapped[Optional[Union[UUID]]] = mapped_column(UUIDType())
|
||||
history_id: Mapped[Optional[int]] = mapped_column(ForeignKey("history.id"), index=True)
|
||||
|
||||
history = relationship("History", back_populates="workflow_invocations")
|
||||
@@ -9415,7 +9416,7 @@ class WorkflowInvocationStep(Base, Dictifiable, Serializable):
|
||||
)
|
||||
action: Mapped[Optional[bytes]] = mapped_column(MutableJSONType)
|
||||
|
||||
workflow_step = relationship("WorkflowStep")
|
||||
workflow_step: Mapped[WorkflowStep] = relationship("WorkflowStep")
|
||||
job: Mapped[Optional["Job"]] = relationship(back_populates="workflow_invocation_step", uselist=False)
|
||||
implicit_collection_jobs = relationship("ImplicitCollectionJobs", uselist=False)
|
||||
output_dataset_collections = relationship(
|
||||
|
||||
@@ -358,6 +358,7 @@ class WorkflowRunCrateProfileBuilder:
|
||||
|
||||
def _add_step_parameter_fp(self, step: WorkflowInvocationStep, crate: ROCrate):
|
||||
param_id = step.workflow_step.label
|
||||
assert step.workflow_step.tool_inputs
|
||||
param_type = step.workflow_step.tool_inputs["parameter_type"]
|
||||
return crate.add(
|
||||
ContextEntity(
|
||||
@@ -375,6 +376,7 @@ class WorkflowRunCrateProfileBuilder:
|
||||
|
||||
def _add_step_tool_pv(self, step: WorkflowInvocationStep, tool_input: str, crate: ROCrate):
|
||||
param_id = tool_input
|
||||
assert step.workflow_step.tool_inputs
|
||||
return crate.add(
|
||||
ContextEntity(
|
||||
crate,
|
||||
|
||||
@@ -48,7 +48,7 @@ IS_OS_X = sys.platform == "darwin"
|
||||
VERSIONED_ENV_DIR_NAME = re.compile(r"__(.*)@(.*)")
|
||||
UNVERSIONED_ENV_DIR_NAME = re.compile(r"__(.*)@_uv_")
|
||||
USE_PATH_EXEC_DEFAULT = False
|
||||
CONDA_PACKAGE_SPECS = ("conda>=23.7.0", "conda-libmamba-solver", "'pyopenssl>=22.1.0'")
|
||||
CONDA_PACKAGE_SPECS = ("conda>=23.7.0", "conda-libmamba-solver", "pyopenssl>=22.1.0")
|
||||
CONDA_BUILD_SPECS = ("conda-build>=3.22.0",)
|
||||
USE_LOCAL_DEFAULT = False
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from .grouping import (
|
||||
)
|
||||
from .workflow_utils import (
|
||||
is_runtime_value,
|
||||
NO_REPLACEMENT,
|
||||
runtime_to_json,
|
||||
)
|
||||
from .wrapped import flat_to_nested_state
|
||||
@@ -174,14 +175,36 @@ def visit_input_values(
|
||||
if input.name not in input_values:
|
||||
args["error"] = f"No value found for '{args.get('prefixed_label')}'."
|
||||
new_value = callback(**args)
|
||||
|
||||
# is this good enough ? feels very ugh
|
||||
if new_value == [no_replacement_value]:
|
||||
# Single unspecified value in multiple="true" input with a single null input, pretend it's a singular value
|
||||
new_value = no_replacement_value
|
||||
if isinstance(new_value, list):
|
||||
# Maybe mixed input, I guess tool defaults don't really make sense here ?
|
||||
# Would e.g. be default dataset in multiple="true" input, you wouldn't expect the default to be inserted
|
||||
# if other inputs are connected and provided.
|
||||
new_value = [item if not item == no_replacement_value else None for item in new_value]
|
||||
|
||||
if no_replacement_value is REPLACE_ON_TRUTHY:
|
||||
replace = bool(new_value)
|
||||
else:
|
||||
replace = new_value != no_replacement_value
|
||||
if replace:
|
||||
input_values[input.name] = new_value
|
||||
elif replace_optional_connections and is_runtime_value(value) and hasattr(input, "value"):
|
||||
input_values[input.name] = input.value
|
||||
elif replace_optional_connections:
|
||||
# Only used in workflow context
|
||||
has_default = hasattr(input, "value")
|
||||
if new_value is value is NO_REPLACEMENT or is_runtime_value(value):
|
||||
# NO_REPLACEMENT means value was connected but left unspecified
|
||||
if has_default:
|
||||
# Use default if we have one
|
||||
input_values[input.name] = input.value
|
||||
else:
|
||||
# Should fail if input is not optional and does not have default value
|
||||
# Effectively however depends on parameter implementation.
|
||||
# We might want to raise an exception here, instead of depending on a tool parameter value error.
|
||||
input_values[input.name] = None
|
||||
|
||||
def get_current_case(input, input_values):
|
||||
test_parameter = input.test_param
|
||||
|
||||
@@ -49,7 +49,10 @@ from galaxy.tool_util.parser.util import (
|
||||
ParameterParseException,
|
||||
text_input_is_optional,
|
||||
)
|
||||
from galaxy.tools.parameters.workflow_utils import workflow_building_modes
|
||||
from galaxy.tools.parameters.workflow_utils import (
|
||||
NO_REPLACEMENT,
|
||||
workflow_building_modes,
|
||||
)
|
||||
from galaxy.util import (
|
||||
sanitize_param,
|
||||
string_as_bool,
|
||||
@@ -247,6 +250,8 @@ class ToolParameter(UsesDictVisibleKeys):
|
||||
def value_to_basic(self, value, app, use_security=False):
|
||||
if is_runtime_value(value):
|
||||
return runtime_to_json(value)
|
||||
elif value == NO_REPLACEMENT:
|
||||
return {"__class__": "NoReplacement"}
|
||||
return self.to_json(value, app, use_security)
|
||||
|
||||
def value_from_basic(self, value, app, ignore_errors=False):
|
||||
@@ -255,8 +260,11 @@ class ToolParameter(UsesDictVisibleKeys):
|
||||
if isinstance(self, HiddenToolParameter):
|
||||
raise ParameterValueError(message_suffix="Runtime Parameter not valid", parameter_name=self.name)
|
||||
return runtime_to_object(value)
|
||||
elif isinstance(value, MutableMapping) and value.get("__class__") == "UnvalidatedValue":
|
||||
return value["value"]
|
||||
elif isinstance(value, MutableMapping):
|
||||
if value.get("__class__") == "UnvalidatedValue":
|
||||
return value["value"]
|
||||
elif value.get("__class__") == "NoReplacement":
|
||||
return NO_REPLACEMENT
|
||||
# Delegate to the 'to_python' method
|
||||
try:
|
||||
return self.to_python(value, app)
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
from collections.abc import MutableMapping
|
||||
|
||||
|
||||
class NoReplacement:
|
||||
|
||||
def __str__(self):
|
||||
return "NO_REPLACEMENT singleton"
|
||||
|
||||
|
||||
NO_REPLACEMENT = NoReplacement()
|
||||
|
||||
|
||||
class workflow_building_modes:
|
||||
DISABLED = False
|
||||
ENABLED = True
|
||||
|
||||
@@ -95,6 +95,8 @@ from galaxy.tools.parameters.populate_model import populate_model
|
||||
from galaxy.tools.parameters.workflow_utils import (
|
||||
ConnectedValue,
|
||||
is_runtime_value,
|
||||
NO_REPLACEMENT,
|
||||
NoReplacement,
|
||||
runtime_to_json,
|
||||
workflow_building_modes,
|
||||
)
|
||||
@@ -129,14 +131,6 @@ RUNTIME_POST_JOB_ACTIONS_KEY = "__POST_JOB_ACTIONS__"
|
||||
POSSIBLE_PARAMETER_TYPES: Tuple[INPUT_PARAMETER_TYPES] = get_args(INPUT_PARAMETER_TYPES)
|
||||
|
||||
|
||||
class NoReplacement:
|
||||
def __str__(self):
|
||||
return "NO_REPLACEMENT singleton"
|
||||
|
||||
|
||||
NO_REPLACEMENT = NoReplacement()
|
||||
|
||||
|
||||
class ConditionalStepWhen(BooleanToolParameter):
|
||||
pass
|
||||
|
||||
@@ -416,14 +410,11 @@ class WorkflowModule:
|
||||
"""
|
||||
return {}
|
||||
|
||||
def compute_runtime_state(self, trans, step=None, step_updates=None):
|
||||
def compute_runtime_state(self, trans, step=None, step_updates=None, replace_default_values=False):
|
||||
"""Determine the runtime state (potentially different from self.state
|
||||
which describes configuration state). This (again unlike self.state) is
|
||||
currently always a `DefaultToolState` object.
|
||||
|
||||
If `step` is not `None`, it will be used to search for default values
|
||||
defined in workflow input steps.
|
||||
|
||||
If `step_updates` is `None`, this is likely for rendering the run form
|
||||
for instance and no runtime properties are available and state must be
|
||||
solely determined by the default runtime state described by the step.
|
||||
@@ -432,6 +423,8 @@ class WorkflowModule:
|
||||
supplied by the workflow runner.
|
||||
"""
|
||||
state = self.get_runtime_state()
|
||||
if replace_default_values and step:
|
||||
state.inputs = step.state.inputs
|
||||
step_errors = {}
|
||||
|
||||
if step is not None:
|
||||
@@ -441,8 +434,11 @@ class WorkflowModule:
|
||||
if step_input is None:
|
||||
return NO_REPLACEMENT
|
||||
|
||||
if step_input.default_value_set:
|
||||
return step_input.default_value
|
||||
if replace_default_values and step_input.default_value_set:
|
||||
input_value = step_input.default_value
|
||||
if isinstance(input, BaseDataToolParameter):
|
||||
input_value = raw_to_galaxy(trans.app, trans.history, input_value)
|
||||
return input_value
|
||||
|
||||
return NO_REPLACEMENT
|
||||
|
||||
@@ -469,7 +465,7 @@ class WorkflowModule:
|
||||
|
||||
return state, step_errors
|
||||
|
||||
def encode_runtime_state(self, step, runtime_state):
|
||||
def encode_runtime_state(self, step, runtime_state: DefaultToolState):
|
||||
"""Takes the computed runtime state and serializes it during run request creation."""
|
||||
return runtime_state.encode(Bunch(inputs=self.get_runtime_inputs(step)), self.trans.app)
|
||||
|
||||
@@ -954,7 +950,7 @@ class InputModule(WorkflowModule):
|
||||
|
||||
def get_runtime_state(self):
|
||||
state = DefaultToolState()
|
||||
state.inputs = dict(input=None)
|
||||
state.inputs = dict(input=NO_REPLACEMENT)
|
||||
return state
|
||||
|
||||
def get_all_inputs(self, data_only=False, connectable_only=False):
|
||||
@@ -966,7 +962,7 @@ class InputModule(WorkflowModule):
|
||||
invocation = invocation_step.workflow_invocation
|
||||
step = invocation_step.workflow_step
|
||||
input_value = step.state.inputs["input"]
|
||||
if input_value is None:
|
||||
if input_value is NO_REPLACEMENT:
|
||||
default_value = step.get_input_default_value(NO_REPLACEMENT)
|
||||
if default_value is not NO_REPLACEMENT:
|
||||
input_value = raw_to_galaxy(trans.app, trans.history, default_value)
|
||||
@@ -993,7 +989,7 @@ class InputModule(WorkflowModule):
|
||||
# everything should come in from the API and this can be eliminated.
|
||||
if not invocation.has_input_for_step(step.id):
|
||||
content = next(iter(step_outputs.values()))
|
||||
if content:
|
||||
if content and content is not NO_REPLACEMENT:
|
||||
invocation.add_input(content, step.id)
|
||||
progress.set_outputs_for_input(invocation_step, step_outputs)
|
||||
return None
|
||||
@@ -1582,7 +1578,7 @@ class InputParameterModule(WorkflowModule):
|
||||
|
||||
def get_runtime_state(self):
|
||||
state = DefaultToolState()
|
||||
state.inputs = dict(input=None)
|
||||
state.inputs = dict(input=NO_REPLACEMENT)
|
||||
return state
|
||||
|
||||
def get_all_outputs(self, data_only=False):
|
||||
@@ -1609,7 +1605,7 @@ class InputParameterModule(WorkflowModule):
|
||||
input_value = progress.inputs_by_step_id[step.id]
|
||||
else:
|
||||
input_value = step.state.inputs["input"]
|
||||
if input_value is None:
|
||||
if input_value is NO_REPLACEMENT:
|
||||
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
|
||||
@@ -2233,13 +2229,14 @@ class ToolModule(WorkflowModule):
|
||||
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):
|
||||
def compute_runtime_state(self, trans, step=None, step_updates=None, replace_default_values=False):
|
||||
# Warning: This method destructively modifies existing step state.
|
||||
if self.tool:
|
||||
step_errors = {}
|
||||
state = self.state
|
||||
self.runtime_post_job_actions = {}
|
||||
state, step_errors = super().compute_runtime_state(trans, step, step_updates)
|
||||
state, step_errors = super().compute_runtime_state(
|
||||
trans, step, step_updates, replace_default_values=replace_default_values
|
||||
)
|
||||
if step_updates:
|
||||
self.runtime_post_job_actions = step_updates.get(RUNTIME_POST_JOB_ACTIONS_KEY, {})
|
||||
step_metadata_runtime_state = self.__step_meta_runtime_state()
|
||||
@@ -2266,7 +2263,11 @@ class ToolModule(WorkflowModule):
|
||||
)
|
||||
|
||||
def execute(
|
||||
self, trans, progress: "WorkflowProgress", invocation_step, use_cached_job: bool = False
|
||||
self,
|
||||
trans,
|
||||
progress: "WorkflowProgress",
|
||||
invocation_step: "WorkflowInvocationStep",
|
||||
use_cached_job: bool = False,
|
||||
) -> Optional[bool]:
|
||||
invocation = invocation_step.workflow_invocation
|
||||
step = invocation_step.workflow_step
|
||||
@@ -2275,7 +2276,12 @@ class ToolModule(WorkflowModule):
|
||||
# TODO: why do we even create an invocation, seems like something we could check on submit?
|
||||
message = f"Specified tool [{tool.id}] in step {step.order_index + 1} is not workflow-compatible."
|
||||
raise exceptions.MessageException(message)
|
||||
self.state, _ = self.compute_runtime_state(
|
||||
trans, step, step_updates=progress.param_map.get(step.id), replace_default_values=True
|
||||
)
|
||||
step.state = self.state
|
||||
tool_state = step.state
|
||||
assert tool_state is not None
|
||||
tool_inputs = tool.inputs.copy()
|
||||
# Not strictly needed - but keep Tool state clean by stripping runtime
|
||||
# metadata parameters from it.
|
||||
@@ -2404,7 +2410,7 @@ class ToolModule(WorkflowModule):
|
||||
mapping_params=mapping_params,
|
||||
history=invocation.history,
|
||||
collection_info=collection_info,
|
||||
workflow_invocation_uuid=invocation.uuid.hex,
|
||||
workflow_invocation_uuid=invocation.uuid.hex if invocation.uuid else None,
|
||||
invocation_step=invocation_step,
|
||||
max_num_jobs=max_num_jobs,
|
||||
validate_outputs=validate_outputs,
|
||||
|
||||
@@ -9,6 +9,7 @@ from galaxy.tools.parameters import visit_input_values
|
||||
from galaxy.tools.parameters.basic import contains_workflow_parameter
|
||||
from galaxy.tools.parameters.workflow_utils import (
|
||||
ConnectedValue,
|
||||
NO_REPLACEMENT,
|
||||
runtime_to_json,
|
||||
)
|
||||
from .schema import (
|
||||
@@ -41,10 +42,7 @@ from .schema import (
|
||||
UpgradeSubworkflowAction,
|
||||
UpgradeToolAction,
|
||||
)
|
||||
from ..modules import (
|
||||
InputParameterModule,
|
||||
NO_REPLACEMENT,
|
||||
)
|
||||
from ..modules import InputParameterModule
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
+26
-22
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import uuid
|
||||
from collections.abc import MutableMapping
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
@@ -37,6 +38,11 @@ from galaxy.schema.invocation import (
|
||||
WarningReason,
|
||||
)
|
||||
from galaxy.tools.parameters.basic import raw_to_galaxy
|
||||
from galaxy.tools.parameters.workflow_utils import (
|
||||
is_runtime_value,
|
||||
NO_REPLACEMENT,
|
||||
NoReplacement,
|
||||
)
|
||||
from galaxy.tools.parameters.wrapped import nested_key_to_path
|
||||
from galaxy.util import ExecutionTimer
|
||||
from galaxy.workflow import modules
|
||||
@@ -334,6 +340,7 @@ class WorkflowInvoker:
|
||||
)
|
||||
|
||||
def _invoke_step(self, invocation_step: WorkflowInvocationStep) -> Optional[bool]:
|
||||
assert invocation_step.workflow_step.module
|
||||
incomplete_or_none = invocation_step.workflow_step.module.execute(
|
||||
self.trans,
|
||||
self.progress,
|
||||
@@ -432,11 +439,11 @@ class WorkflowProgress:
|
||||
|
||||
def replacement_for_input(self, trans, step: "WorkflowStep", input_dict: Dict[str, Any]):
|
||||
replacement: Union[
|
||||
modules.NoReplacement,
|
||||
NoReplacement,
|
||||
model.DatasetCollectionInstance,
|
||||
List[model.DatasetCollectionInstance],
|
||||
HistoryItem,
|
||||
] = modules.NO_REPLACEMENT
|
||||
] = NO_REPLACEMENT
|
||||
prefixed_name = input_dict["name"]
|
||||
multiple = input_dict["multiple"]
|
||||
is_data = input_dict["input_type"] in ["dataset", "dataset_collection"]
|
||||
@@ -456,15 +463,18 @@ class WorkflowProgress:
|
||||
replacement = temp
|
||||
else:
|
||||
replacement = self.replacement_for_connection(connection[0], is_data=is_data)
|
||||
elif step.state and (state_input := get_path(step.state.inputs, nested_key_to_path(prefixed_name), None)):
|
||||
elif (
|
||||
step.state
|
||||
and (state_input := get_path(step.state.inputs, nested_key_to_path(prefixed_name), None))
|
||||
and not is_runtime_value(state_input)
|
||||
):
|
||||
# workflow submitted with step parameters populates state directly
|
||||
# via populate_module_and_state
|
||||
replacement = state_input
|
||||
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.app, trans.history, step_input.default_value)
|
||||
elif (step_input := step.inputs_by_name.get(prefixed_name)) and step_input.default_value_set:
|
||||
replacement = step_input.default_value
|
||||
if is_data:
|
||||
replacement = raw_to_galaxy(trans.app, trans.history, step_input.default_value)
|
||||
return replacement
|
||||
|
||||
def replacement_for_connection(self, connection: "WorkflowStepConnection", is_data: bool = True):
|
||||
@@ -494,6 +504,8 @@ class WorkflowProgress:
|
||||
dependent_workflow_step_id=output_step_id,
|
||||
)
|
||||
)
|
||||
if isinstance(replacement, MutableMapping) and replacement.get("__class__") == "NoReplacement":
|
||||
return NO_REPLACEMENT
|
||||
if isinstance(replacement, model.HistoryDatasetCollectionAssociation):
|
||||
if not replacement.collection.populated:
|
||||
if not replacement.waiting_for_elements:
|
||||
@@ -574,19 +586,8 @@ class WorkflowProgress:
|
||||
if self.inputs_by_step_id:
|
||||
step_id = step.id
|
||||
if step_id not in self.inputs_by_step_id and "output" not in outputs:
|
||||
default_value = step.get_input_default_value(modules.NO_REPLACEMENT)
|
||||
if default_value is not modules.NO_REPLACEMENT:
|
||||
outputs["output"] = default_value
|
||||
else:
|
||||
log.error(f"{step.log_str()} not found in inputs_step_id {self.inputs_by_step_id}")
|
||||
raise modules.FailWorkflowEvaluation(
|
||||
why=InvocationFailureOutputNotFound(
|
||||
reason=FailureReason.output_not_found,
|
||||
workflow_step_id=invocation_step.workflow_step_id,
|
||||
output_name="output",
|
||||
dependent_workflow_step_id=invocation_step.workflow_step_id,
|
||||
)
|
||||
)
|
||||
default_value = step.get_input_default_value(NO_REPLACEMENT)
|
||||
outputs["output"] = default_value
|
||||
elif step_id in self.inputs_by_step_id:
|
||||
if self.inputs_by_step_id[step_id] is not None or "output" not in outputs:
|
||||
outputs["output"] = self.inputs_by_step_id[step_id]
|
||||
@@ -620,7 +621,7 @@ class WorkflowProgress:
|
||||
# Add this non-data, non workflow-output output to the workflow outputs.
|
||||
# This is required for recovering the output in the next scheduling iteration,
|
||||
# and should be replaced with a WorkflowInvocationStepOutputValue ASAP.
|
||||
if not workflow_outputs_by_name.get(output_name) and not output_object == modules.NO_REPLACEMENT:
|
||||
if not workflow_outputs_by_name.get(output_name) and output_object is not NO_REPLACEMENT:
|
||||
workflow_output = model.WorkflowOutput(step, output_name=output_name)
|
||||
step.workflow_outputs.append(workflow_output)
|
||||
for workflow_output in step.workflow_outputs:
|
||||
@@ -645,6 +646,8 @@ class WorkflowProgress:
|
||||
)
|
||||
|
||||
def _record_workflow_output(self, step: "WorkflowStep", workflow_output: "WorkflowOutput", output: Any) -> None:
|
||||
if output is NO_REPLACEMENT:
|
||||
output = {"__class__": "NoReplacement"}
|
||||
self.workflow_invocation.add_output(workflow_output, step, output)
|
||||
|
||||
def mark_step_outputs_delayed(self, step: "WorkflowStep", why: Optional[str] = None) -> None:
|
||||
@@ -737,6 +740,7 @@ class WorkflowProgress:
|
||||
return raw_to_galaxy(self.module_injector.trans.app, self.module_injector.trans.history, value)
|
||||
|
||||
def _recover_mapping(self, step_invocation: WorkflowInvocationStep) -> None:
|
||||
assert step_invocation.workflow_step.module
|
||||
try:
|
||||
step_invocation.workflow_step.module.recover_mapping(step_invocation, self)
|
||||
except modules.DelayedWorkflowEvaluation as de:
|
||||
|
||||
@@ -30,6 +30,7 @@ from galaxy.model.base import (
|
||||
from galaxy.tool_util.parameters import DataRequestUri
|
||||
from galaxy.tools.parameters.basic import ParameterValueError
|
||||
from galaxy.tools.parameters.meta import expand_workflow_inputs
|
||||
from galaxy.tools.parameters.workflow_utils import NO_REPLACEMENT
|
||||
from galaxy.workflow.modules import WorkflowModuleInjector
|
||||
from galaxy.workflow.resources import get_resource_mapper_function
|
||||
|
||||
@@ -599,7 +600,8 @@ def workflow_run_config_to_request(
|
||||
type=param_types.REPLACEMENT_PARAMETERS,
|
||||
)
|
||||
for step_id, content in run_config.inputs.items():
|
||||
workflow_invocation.add_input(content, step_id)
|
||||
if content is not NO_REPLACEMENT:
|
||||
workflow_invocation.add_input(content, step_id)
|
||||
for step_id, param_dict in run_config.param_map.items():
|
||||
add_parameter(
|
||||
name=str(step_id),
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
- that: has_text
|
||||
text: "1"
|
||||
- doc: |
|
||||
Test that null is replaced with default value (follows https://www.commonwl.org/v1.2/Workflow.html#WorkflowInputParameter)
|
||||
Test that explicit null is not replaced and fails
|
||||
expect_failure: true
|
||||
job:
|
||||
required_int_with_default:
|
||||
type: raw
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
- that: has_text
|
||||
text: "1"
|
||||
- doc: |
|
||||
Test that null is replaced with default value (follows https://www.commonwl.org/v1.2/Workflow.html#WorkflowInputParameter)
|
||||
Test that explicit null is not replaced and fails
|
||||
expect_failure: true
|
||||
job:
|
||||
optional_int_with_default:
|
||||
type: raw
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
- doc: |
|
||||
Test that scheduling succeeds even if step needs rescheduling
|
||||
job:
|
||||
optional_text:
|
||||
type: raw
|
||||
value: null
|
||||
outputs:
|
||||
out:
|
||||
class: File
|
||||
asserts:
|
||||
- that: has_text
|
||||
text: "null"
|
||||
@@ -0,0 +1,30 @@
|
||||
class: GalaxyWorkflow
|
||||
inputs:
|
||||
optional_text:
|
||||
type: text
|
||||
optional: true
|
||||
when:
|
||||
type: boolean
|
||||
default: true
|
||||
outputs:
|
||||
out:
|
||||
outputSource: gx_text_optional/inputs_json
|
||||
steps:
|
||||
pick_value:
|
||||
tool_id: pick_value
|
||||
tool_state:
|
||||
style_cond:
|
||||
pick_style: first
|
||||
type_cond:
|
||||
param_type: boolean
|
||||
pick_from:
|
||||
- value: true
|
||||
gx_text_optional:
|
||||
tool_id: gx_text_optional
|
||||
# the when expression requires pick_value to execute, which recapitulates https://github.com/galaxyproject/galaxy/issues/19328
|
||||
when: $(inputs.when)
|
||||
in:
|
||||
parameter:
|
||||
source: optional_text
|
||||
when:
|
||||
source: pick_value/boolean_param
|
||||
@@ -14,6 +14,7 @@ import pytest
|
||||
|
||||
from galaxy import model
|
||||
from galaxy.managers.workflows import WorkflowContentsManager
|
||||
from galaxy.tools.parameters.workflow_utils import NO_REPLACEMENT
|
||||
from galaxy.util import bunch
|
||||
from galaxy.workflow import modules
|
||||
from .workflow_support import (
|
||||
@@ -57,7 +58,7 @@ def test_data_input_compute_runtime_state_default():
|
||||
state, errors = module.compute_runtime_state(module.trans, module.test_step)
|
||||
assert not errors
|
||||
assert "input" in state.inputs
|
||||
assert state.inputs["input"] is None
|
||||
assert state.inputs["input"] is NO_REPLACEMENT
|
||||
|
||||
|
||||
def test_data_input_compute_runtime_state_args():
|
||||
|
||||
Reference in New Issue
Block a user