Skip to content

Commit

Permalink
Allow workflow import of workflows with connections that don't have m…
Browse files Browse the repository at this point in the history
…atching empty tool state entries.

There is a long conversation in the added doc string.
  • Loading branch information
jmchilton committed Oct 6, 2018
1 parent c7fa286 commit d010077
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 7 deletions.
62 changes: 61 additions & 1 deletion lib/galaxy/workflow/modules.py
Expand Up @@ -78,7 +78,8 @@ def __init__(self, trans, content_id=None, **kwds):
@classmethod
def from_dict(Class, trans, d, **kwds):
module = Class(trans, **kwds)
module.recover_state(d.get("tool_state"), **kwds)
input_connections = d.get("input_connections", {})
module.recover_state(d.get("tool_state"), input_connections=input_connections, **kwds)
module.label = d.get("label")
return module

Expand Down Expand Up @@ -867,8 +868,67 @@ def recover_state(self, state, **kwds):
super(ToolModule, self).recover_state(state, **kwds)
if kwds.get("fill_defaults", False) and self.tool:
self.compute_runtime_state(self.trans, step_updates=None)
self.augment_tool_state_for_input_connections(**kwds)
self.tool.check_and_update_param_values(self.state.inputs, self.trans, workflow_building_mode=True)

def augment_tool_state_for_input_connections(self, **kwds):
"""Update tool state to accommodate specified input connections.
Top-level and conditional inputs will automatically get populated with connected
data outputs at runtime, but if there are not enough repeat instances in the tool
state - the runtime replacement code will never visit the input elements it needs
to in order to connect the data parameters to the tool state. This code then
populates the required repeat instances in the tool state in order for these
instances to be visited and inputs properly connected at runtime. I believe
this should be run before check_and_update_param_values in recover_state so non-data
parameters are properly populated with default values. The need to populate
defaults is why this is done here instead of at runtime - but this might also
be needed at runtime at some point (for workflows installed before their corresponding
tools?).
See the test case test_inputs_to_steps for an example of a workflow test
case that exercises this code.
"""

# Ensure any repeats defined only by input_connections are populated.
input_connections = kwds.get("input_connections", {})
expected_replacement_keys = input_connections.keys()

def augment(expected_replacement_key, inputs, inputs_state):
if "|" not in expected_replacement_key:
return

prefix, rest = expected_replacement_key.split("|", 1)
if "_" not in prefix:
return

repeat_name, index = prefix.rsplit("_", 1)
if not index.isdigit():
return

index = int(index)
repeat = self.tool.inputs[repeat_name]
if repeat.type != "repeat":
return

if repeat_name not in inputs_states:
inputs_states[repeat_name] = []

repeat_values = inputs_states[repeat_name]
repeat_instance_state = None
while index >= len(repeat_values):
repeat_instance_state = {"__index__": len(repeat_values)}
repeat_values.append(repeat_instance_state)

if repeat_instance_state:
# TODO: untest branch - no test case for nested repeats yet...
augment(rest, repeat.inputs, repeat_instance_state)

for expected_replacement_key in expected_replacement_keys:
inputs_states = self.state.inputs
inputs = self.tool.inputs
augment(expected_replacement_key, inputs, inputs_states)

def get_runtime_state(self):
state = DefaultToolState()
state.inputs = self.state.inputs
Expand Down
9 changes: 3 additions & 6 deletions test/api/test_workflows_from_yaml.py
Expand Up @@ -129,12 +129,9 @@ def test_inputs_to_steps(self):
steps:
- tool_id: cat1
label: first_cat
state:
input1:
$link: input1
queries:
- input2:
$link: input1
connect:
input1: input1
queries_0|input2: input1
test_data:
input1: "hello world"
Expand Down

0 comments on commit d010077

Please sign in to comment.