[2] | 1 | from elementtree.ElementTree import Element |
---|
| 2 | |
---|
| 3 | from galaxy import web |
---|
| 4 | from galaxy.tools.parameters import DataToolParameter, DummyDataset, RuntimeValue, check_param, visit_input_values |
---|
| 5 | from galaxy.tools import DefaultToolState |
---|
| 6 | from galaxy.tools.parameters.grouping import Repeat, Conditional |
---|
| 7 | from galaxy.util.bunch import Bunch |
---|
| 8 | from galaxy.util.json import from_json_string, to_json_string |
---|
| 9 | from galaxy.jobs.actions.post import ActionBox |
---|
| 10 | from galaxy.model import PostJobAction |
---|
| 11 | |
---|
| 12 | class WorkflowModule( object ): |
---|
| 13 | |
---|
| 14 | def __init__( self, trans ): |
---|
| 15 | self.trans = trans |
---|
| 16 | |
---|
| 17 | ## ---- Creating modules from various representations --------------------- |
---|
| 18 | |
---|
| 19 | @classmethod |
---|
| 20 | def new( Class, trans, tool_id=None ): |
---|
| 21 | """ |
---|
| 22 | Create a new instance of the module with default state |
---|
| 23 | """ |
---|
| 24 | return Class( trans ) |
---|
| 25 | @classmethod |
---|
| 26 | def from_dict( Class, trans, d ): |
---|
| 27 | """ |
---|
| 28 | Create a new instance of the module initialized from values in the |
---|
| 29 | dictionary `d`. |
---|
| 30 | """ |
---|
| 31 | return Class( trans ) |
---|
| 32 | @classmethod |
---|
| 33 | def from_workflow_step( Class, trans, step ): |
---|
| 34 | return Class( trans ) |
---|
| 35 | |
---|
| 36 | ## ---- Saving in various forms ------------------------------------------ |
---|
| 37 | |
---|
| 38 | def save_to_step( self, step ): |
---|
| 39 | step.type = self.type |
---|
| 40 | |
---|
| 41 | ## ---- General attributes ----------------------------------------------- |
---|
| 42 | |
---|
| 43 | def get_type( self ): |
---|
| 44 | return self.type |
---|
| 45 | def get_name( self ): |
---|
| 46 | return self.name |
---|
| 47 | def get_tool_id( self ): |
---|
| 48 | return None |
---|
| 49 | def get_tooltip( self ): |
---|
| 50 | return None |
---|
| 51 | |
---|
| 52 | ## ---- Configuration time ----------------------------------------------- |
---|
| 53 | |
---|
| 54 | def get_state( self ): |
---|
| 55 | return None |
---|
| 56 | def get_errors( self ): |
---|
| 57 | return None |
---|
| 58 | def get_data_inputs( self ): |
---|
| 59 | return [] |
---|
| 60 | def get_data_outputs( self ): |
---|
| 61 | return [] |
---|
| 62 | def update_state( self ): |
---|
| 63 | pass |
---|
| 64 | def get_config_form( self ): |
---|
| 65 | raise TypeError( "Abstract method" ) |
---|
| 66 | |
---|
| 67 | def check_and_update_state( self ): |
---|
| 68 | """ |
---|
| 69 | If the state is not in sync with the current implementation of the |
---|
| 70 | module, try to update. Returns a list of messages to be displayed |
---|
| 71 | """ |
---|
| 72 | pass |
---|
| 73 | |
---|
| 74 | ## ---- Run time --------------------------------------------------------- |
---|
| 75 | |
---|
| 76 | def get_runtime_inputs( self ): |
---|
| 77 | raise TypeError( "Abstract method" ) |
---|
| 78 | def get_runtime_state( self ): |
---|
| 79 | raise TypeError( "Abstract method" ) |
---|
| 80 | def encode_runtime_state( self, trans, state ): |
---|
| 81 | raise TypeError( "Abstract method" ) |
---|
| 82 | def decode_runtime_state( self, trans, string ): |
---|
| 83 | raise TypeError( "Abstract method" ) |
---|
| 84 | def update_runtime_state( self, trans, state, values ): |
---|
| 85 | raise TypeError( "Abstract method" ) |
---|
| 86 | |
---|
| 87 | def execute( self, trans, state ): |
---|
| 88 | raise TypeError( "Abstract method" ) |
---|
| 89 | |
---|
| 90 | class InputDataModule( WorkflowModule ): |
---|
| 91 | type = "data_input" |
---|
| 92 | name = "Input dataset" |
---|
| 93 | |
---|
| 94 | @classmethod |
---|
| 95 | def new( Class, trans, tool_id=None ): |
---|
| 96 | module = Class( trans ) |
---|
| 97 | module.state = dict( name="Input Dataset" ) |
---|
| 98 | return module |
---|
| 99 | @classmethod |
---|
| 100 | def from_dict( Class, trans, d, secure=True ): |
---|
| 101 | module = Class( trans ) |
---|
| 102 | state = from_json_string( d["tool_state"] ) |
---|
| 103 | module.state = dict( name=state.get( "name", "Input Dataset" ) ) |
---|
| 104 | return module |
---|
| 105 | @classmethod |
---|
| 106 | def from_workflow_step( Class, trans, step ): |
---|
| 107 | module = Class( trans ) |
---|
| 108 | module.state = dict( name="Input Dataset" ) |
---|
| 109 | if step.tool_inputs and "name" in step.tool_inputs: |
---|
| 110 | module.state['name'] = step.tool_inputs[ 'name' ] |
---|
| 111 | return module |
---|
| 112 | def save_to_step( self, step ): |
---|
| 113 | step.type = self.type |
---|
| 114 | step.tool_id = None |
---|
| 115 | step.tool_inputs = self.state |
---|
| 116 | |
---|
| 117 | def get_data_inputs( self ): |
---|
| 118 | return [] |
---|
| 119 | def get_data_outputs( self ): |
---|
| 120 | return [ dict( name='output', extensions=['input'] ) ] |
---|
| 121 | def get_config_form( self ): |
---|
| 122 | form = web.FormBuilder( title=self.name ) \ |
---|
| 123 | .add_text( "name", "Name", value=self.state['name'] ) |
---|
| 124 | return self.trans.fill_template( "workflow/editor_generic_form.mako", |
---|
| 125 | module=self, form=form ) |
---|
| 126 | def get_state( self, secure=True ): |
---|
| 127 | return to_json_string( self.state ) |
---|
| 128 | |
---|
| 129 | def update_state( self, incoming ): |
---|
| 130 | self.state['name'] = incoming.get( 'name', 'Input Dataset' ) |
---|
| 131 | |
---|
| 132 | def get_runtime_inputs( self ): |
---|
| 133 | label = self.state.get( "name", "Input Dataset" ) |
---|
| 134 | return dict( input=DataToolParameter( None, Element( "param", name="input", label=label, type="data", format="data" ) ) ) |
---|
| 135 | def get_runtime_state( self ): |
---|
| 136 | state = DefaultToolState() |
---|
| 137 | state.inputs = dict( input=None ) |
---|
| 138 | return state |
---|
| 139 | def encode_runtime_state( self, trans, state ): |
---|
| 140 | fake_tool = Bunch( inputs = self.get_runtime_inputs() ) |
---|
| 141 | return state.encode( fake_tool, trans.app ) |
---|
| 142 | def decode_runtime_state( self, trans, string ): |
---|
| 143 | fake_tool = Bunch( inputs = self.get_runtime_inputs() ) |
---|
| 144 | state = DefaultToolState() |
---|
| 145 | state.decode( string, fake_tool, trans.app ) |
---|
| 146 | return state |
---|
| 147 | def update_runtime_state( self, trans, state, values ): |
---|
| 148 | errors = {} |
---|
| 149 | for name, param in self.get_runtime_inputs().iteritems(): |
---|
| 150 | value, error = check_param( trans, param, values.get( name, None ), values ) |
---|
| 151 | state.inputs[ name ] = value |
---|
| 152 | if error: |
---|
| 153 | errors[ name ] = error |
---|
| 154 | return errors |
---|
| 155 | |
---|
| 156 | def execute( self, trans, state ): |
---|
| 157 | return None, dict( output=state.inputs['input']) |
---|
| 158 | |
---|
| 159 | class ToolModule( WorkflowModule ): |
---|
| 160 | |
---|
| 161 | type = "tool" |
---|
| 162 | |
---|
| 163 | def __init__( self, trans, tool_id ): |
---|
| 164 | self.trans = trans |
---|
| 165 | self.tool_id = tool_id |
---|
| 166 | self.tool = trans.app.toolbox.tools_by_id[ tool_id ] |
---|
| 167 | self.post_job_actions = {} |
---|
| 168 | self.workflow_outputs = [] |
---|
| 169 | self.state = None |
---|
| 170 | self.errors = None |
---|
| 171 | |
---|
| 172 | @classmethod |
---|
| 173 | def new( Class, trans, tool_id=None ): |
---|
| 174 | module = Class( trans, tool_id ) |
---|
| 175 | module.state = module.tool.new_state( trans, all_pages=True ) |
---|
| 176 | return module |
---|
| 177 | |
---|
| 178 | @classmethod |
---|
| 179 | def from_dict( Class, trans, d, secure=True ): |
---|
| 180 | tool_id = d['tool_id'] |
---|
| 181 | module = Class( trans, tool_id ) |
---|
| 182 | module.state = DefaultToolState() |
---|
| 183 | module.state.decode( d["tool_state"], module.tool, module.trans.app, secure=secure ) |
---|
| 184 | module.errors = d.get( "tool_errors", None ) |
---|
| 185 | module.post_job_actions = d.get("post_job_actions", {}) |
---|
| 186 | module.workflow_outputs = d.get("workflow_outputs", []) |
---|
| 187 | return module |
---|
| 188 | |
---|
| 189 | @classmethod |
---|
| 190 | def from_workflow_step( Class, trans, step ): |
---|
| 191 | tool_id = step.tool_id |
---|
| 192 | module = Class( trans, tool_id ) |
---|
| 193 | module.state = DefaultToolState() |
---|
| 194 | module.state.inputs = module.tool.params_from_strings( step.tool_inputs, trans.app, ignore_errors=True ) |
---|
| 195 | module.errors = step.tool_errors |
---|
| 196 | # module.post_job_actions = step.post_job_actions |
---|
| 197 | module.workflow_outputs = step.workflow_outputs |
---|
| 198 | pjadict = {} |
---|
| 199 | for pja in step.post_job_actions: |
---|
| 200 | pjadict[pja.action_type] = pja |
---|
| 201 | module.post_job_actions = pjadict |
---|
| 202 | return module |
---|
| 203 | |
---|
| 204 | def save_to_step( self, step ): |
---|
| 205 | step.type = self.type |
---|
| 206 | step.tool_id = self.tool_id |
---|
| 207 | step.tool_version = self.get_tool_version() |
---|
| 208 | step.tool_inputs = self.tool.params_to_strings( self.state.inputs, self.trans.app ) |
---|
| 209 | step.tool_errors = self.errors |
---|
| 210 | for k, v in self.post_job_actions.iteritems(): |
---|
| 211 | # Must have action_type, step. output and a_args are optional. |
---|
| 212 | if 'output_name' in v: |
---|
| 213 | output_name = v['output_name'] |
---|
| 214 | else: |
---|
| 215 | output_name = None |
---|
| 216 | if 'action_arguments' in v: |
---|
| 217 | action_arguments = v['action_arguments'] |
---|
| 218 | else: |
---|
| 219 | action_arguments = None |
---|
| 220 | n_p = PostJobAction(v['action_type'], step, output_name, action_arguments) |
---|
| 221 | |
---|
| 222 | def get_name( self ): |
---|
| 223 | return self.tool.name |
---|
| 224 | def get_tool_id( self ): |
---|
| 225 | return self.tool_id |
---|
| 226 | def get_tool_version( self ): |
---|
| 227 | return self.tool.version |
---|
| 228 | def get_state( self, secure=True ): |
---|
| 229 | return self.state.encode( self.tool, self.trans.app, secure=secure ) |
---|
| 230 | def get_errors( self ): |
---|
| 231 | return self.errors |
---|
| 232 | def get_tooltip( self ): |
---|
| 233 | return self.tool.help |
---|
| 234 | |
---|
| 235 | def get_data_inputs( self ): |
---|
| 236 | data_inputs = [] |
---|
| 237 | def callback( input, value, prefixed_name, prefixed_label ): |
---|
| 238 | if isinstance( input, DataToolParameter ): |
---|
| 239 | data_inputs.append( dict( |
---|
| 240 | name=prefixed_name, |
---|
| 241 | label=prefixed_label, |
---|
| 242 | extensions=input.extensions ) ) |
---|
| 243 | visit_input_values( self.tool.inputs, self.state.inputs, callback ) |
---|
| 244 | return data_inputs |
---|
| 245 | def get_data_outputs( self ): |
---|
| 246 | data_outputs = [] |
---|
| 247 | for name, tool_output in self.tool.outputs.iteritems(): |
---|
| 248 | formats = [ tool_output.format ] |
---|
| 249 | for change_elem in tool_output.change_format: |
---|
| 250 | for when_elem in change_elem.findall( 'when' ): |
---|
| 251 | format = when_elem.get( 'format', None ) |
---|
| 252 | if format and format not in formats: |
---|
| 253 | formats.append( format ) |
---|
| 254 | data_outputs.append( dict( name=name, extensions=formats ) ) |
---|
| 255 | return data_outputs |
---|
| 256 | |
---|
| 257 | def get_post_job_actions( self ): |
---|
| 258 | return self.post_job_actions |
---|
| 259 | |
---|
| 260 | def get_config_form( self ): |
---|
| 261 | self.add_dummy_datasets() |
---|
| 262 | return self.trans.fill_template( "workflow/editor_tool_form.mako", |
---|
| 263 | tool=self.tool, values=self.state.inputs, errors=( self.errors or {} ) ) |
---|
| 264 | |
---|
| 265 | def update_state( self, incoming ): |
---|
| 266 | # Build a callback that handles setting an input to be required at |
---|
| 267 | # runtime. We still process all other parameters the user might have |
---|
| 268 | # set. We also need to make sure all datasets have a dummy value |
---|
| 269 | # for dependencies to see |
---|
| 270 | |
---|
| 271 | self.post_job_actions = ActionBox.handle_incoming(incoming) |
---|
| 272 | |
---|
| 273 | make_runtime_key = incoming.get( 'make_runtime', None ) |
---|
| 274 | make_buildtime_key = incoming.get( 'make_buildtime', None ) |
---|
| 275 | def item_callback( trans, key, input, value, error, old_value, context ): |
---|
| 276 | # Dummy value for Data parameters |
---|
| 277 | if isinstance( input, DataToolParameter ): |
---|
| 278 | return DummyDataset(), None |
---|
| 279 | # Deal with build/runtime (does not apply to Data parameters) |
---|
| 280 | if key == make_buildtime_key: |
---|
| 281 | return input.get_initial_value( trans, context ), None |
---|
| 282 | elif isinstance( old_value, RuntimeValue ): |
---|
| 283 | return old_value, None |
---|
| 284 | elif key == make_runtime_key: |
---|
| 285 | return RuntimeValue(), None |
---|
| 286 | else: |
---|
| 287 | return value, error |
---|
| 288 | # Update state using incoming values |
---|
| 289 | errors = self.tool.update_state( self.trans, self.tool.inputs, self.state.inputs, incoming, item_callback=item_callback ) |
---|
| 290 | self.errors = errors or None |
---|
| 291 | |
---|
| 292 | def check_and_update_state( self ): |
---|
| 293 | return self.tool.check_and_update_param_values( self.state.inputs, self.trans ) |
---|
| 294 | |
---|
| 295 | def add_dummy_datasets( self, connections=None): |
---|
| 296 | if connections: |
---|
| 297 | # Store onnections by input name |
---|
| 298 | input_connections_by_name = \ |
---|
| 299 | dict( ( conn.input_name, conn ) for conn in connections ) |
---|
| 300 | else: |
---|
| 301 | input_connections_by_name = {} |
---|
| 302 | # Any connected input needs to have value DummyDataset (these |
---|
| 303 | # are not persisted so we need to do it every time) |
---|
| 304 | def callback( input, value, prefixed_name, prefixed_label ): |
---|
| 305 | if isinstance( input, DataToolParameter ): |
---|
| 306 | if connections is None or prefixed_name in input_connections_by_name: |
---|
| 307 | return DummyDataset() |
---|
| 308 | visit_input_values( self.tool.inputs, self.state.inputs, callback ) |
---|
| 309 | |
---|
| 310 | |
---|
| 311 | class WorkflowModuleFactory( object ): |
---|
| 312 | def __init__( self, module_types ): |
---|
| 313 | self.module_types = module_types |
---|
| 314 | def new( self, trans, type, tool_id=None ): |
---|
| 315 | """ |
---|
| 316 | Return module for type and (optional) tool_id intialized with |
---|
| 317 | new / default state. |
---|
| 318 | """ |
---|
| 319 | assert type in self.module_types |
---|
| 320 | return self.module_types[type].new( trans, tool_id ) |
---|
| 321 | def from_dict( self, trans, d, **kwargs ): |
---|
| 322 | """ |
---|
| 323 | Return module initialized from the data in dictionary `d`. |
---|
| 324 | """ |
---|
| 325 | type = d['type'] |
---|
| 326 | assert type in self.module_types |
---|
| 327 | return self.module_types[type].from_dict( trans, d, **kwargs ) |
---|
| 328 | def from_workflow_step( self, trans, step ): |
---|
| 329 | """ |
---|
| 330 | Return module initializd from the WorkflowStep object `step`. |
---|
| 331 | """ |
---|
| 332 | type = step.type |
---|
| 333 | return self.module_types[type].from_workflow_step( trans, step ) |
---|
| 334 | |
---|
| 335 | module_factory = WorkflowModuleFactory( dict( data_input=InputDataModule, tool=ToolModule ) ) |
---|