mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Very basic support for exporting and importing workflows among instances
This commit is contained in:
@@ -203,7 +203,7 @@ class DefaultToolState( object ):
|
||||
def __init__( self ):
|
||||
self.page = 0
|
||||
self.inputs = None
|
||||
def encode( self, tool, app ):
|
||||
def encode( self, tool, app, secure=True ):
|
||||
"""
|
||||
Convert the data to a string
|
||||
"""
|
||||
@@ -213,18 +213,22 @@ class DefaultToolState( object ):
|
||||
value["__page__"] = self.page
|
||||
value = simplejson.dumps( value )
|
||||
# Make it secure
|
||||
a = hmac_new( app.config.tool_secret, value )
|
||||
b = binascii.hexlify( value )
|
||||
return "%s:%s" % ( a, b )
|
||||
def decode( self, value, tool, app ):
|
||||
if secure:
|
||||
a = hmac_new( app.config.tool_secret, value )
|
||||
b = binascii.hexlify( value )
|
||||
return "%s:%s" % ( a, b )
|
||||
else:
|
||||
return value
|
||||
def decode( self, value, tool, app, secure=True ):
|
||||
"""
|
||||
Restore the state from a string
|
||||
"""
|
||||
# Extract and verify hash
|
||||
a, b = value.split( ":" )
|
||||
value = binascii.unhexlify( b )
|
||||
test = hmac_new( app.config.tool_secret, value )
|
||||
assert a == test
|
||||
if secure:
|
||||
# Extract and verify hash
|
||||
a, b = value.split( ":" )
|
||||
value = binascii.unhexlify( b )
|
||||
test = hmac_new( app.config.tool_secret, value )
|
||||
assert a == test
|
||||
# Restore from string
|
||||
values = json_fix( simplejson.loads( value ) )
|
||||
self.page = values.pop( "__page__" )
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
The Galaxy web application.
|
||||
"""
|
||||
|
||||
from framework import expose, json, require_login, require_admin, url_for, error, form, FormBuilder
|
||||
from framework import expose, json, json_pretty, require_login, require_admin, url_for, error, form, FormBuilder
|
||||
from framework.base import httpexceptions
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ from galaxy import model
|
||||
from galaxy.model.mapping import desc
|
||||
from galaxy.model.orm import *
|
||||
|
||||
import urllib2
|
||||
|
||||
class StoredWorkflowListGrid( grids.Grid ):
|
||||
class StepsColumn( grids.GridColumn ):
|
||||
def get_value(self, trans, grid, workflow):
|
||||
@@ -621,7 +623,6 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno
|
||||
step_dict['position'] = step.position
|
||||
# Add to return value
|
||||
data['steps'][step.order_index] = step_dict
|
||||
print data['upgrade_messages']
|
||||
return data
|
||||
|
||||
@web.json
|
||||
@@ -660,7 +661,6 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno
|
||||
workflow.has_errors = True
|
||||
# Stick this in the step temporarily
|
||||
step.temp_input_connections = step_dict['input_connections']
|
||||
|
||||
# Save step annotation.
|
||||
annotation = step_dict[ 'annotation' ]
|
||||
if annotation:
|
||||
@@ -698,6 +698,155 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno
|
||||
rval['name'] = workflow.name
|
||||
return rval
|
||||
|
||||
@web.json_pretty
|
||||
def export_workflow( self, trans, id ):
|
||||
"""
|
||||
Get the latest Workflow for the StoredWorkflow identified by `id` and
|
||||
encode it as a json string that can be imported back into Galaxy
|
||||
|
||||
This has slightly different information than the above. In particular,
|
||||
it does not attempt to decode forms and build UIs, it just stores
|
||||
the raw state.
|
||||
"""
|
||||
user = trans.get_user()
|
||||
id = trans.security.decode_id( id )
|
||||
trans.workflow_building_mode = True
|
||||
# Load encoded workflow from database
|
||||
stored = trans.sa_session.query( model.StoredWorkflow ).get( id )
|
||||
self.security_check( trans.get_user(), stored, False, True )
|
||||
workflow = stored.latest_workflow
|
||||
# Pack workflow data into a dictionary and return
|
||||
data = {}
|
||||
data['name'] = workflow.name
|
||||
data['steps'] = {}
|
||||
# For each step, rebuild the form and encode the state
|
||||
for step in workflow.steps:
|
||||
# Load from database representation
|
||||
module = module_factory.from_workflow_step( trans, step )
|
||||
# Get user annotation.
|
||||
step_annotation = self.get_item_annotation_obj( trans, trans.user, step )
|
||||
annotation_str = ""
|
||||
if step_annotation:
|
||||
annotation_str = step_annotation.annotation
|
||||
# Pack attributes into plain dictionary
|
||||
step_dict = {
|
||||
'id': step.order_index,
|
||||
'type': module.type,
|
||||
'tool_id': module.get_tool_id(),
|
||||
'name': module.get_name(),
|
||||
'tool_state': module.get_state( secure=False ),
|
||||
'tool_errors': module.get_errors(),
|
||||
## 'data_inputs': module.get_data_inputs(),
|
||||
## 'data_outputs': module.get_data_outputs(),
|
||||
'annotation' : annotation_str
|
||||
}
|
||||
# Connections
|
||||
input_connections = step.input_connections
|
||||
if step.type is None or step.type == 'tool':
|
||||
# Determine full (prefixed) names of valid input datasets
|
||||
data_input_names = {}
|
||||
def callback( input, value, prefixed_name, prefixed_label ):
|
||||
if isinstance( input, DataToolParameter ):
|
||||
data_input_names[ prefixed_name ] = True
|
||||
visit_input_values( module.tool.inputs, module.state.inputs, callback )
|
||||
# Filter
|
||||
# FIXME: this removes connection without displaying a message currently!
|
||||
input_connections = [ conn for conn in input_connections if conn.input_name in data_input_names ]
|
||||
# Encode input connections as dictionary
|
||||
input_conn_dict = {}
|
||||
for conn in input_connections:
|
||||
input_conn_dict[ conn.input_name ] = \
|
||||
dict( id=conn.output_step.order_index, output_name=conn.output_name )
|
||||
step_dict['input_connections'] = input_conn_dict
|
||||
# Position
|
||||
step_dict['position'] = step.position
|
||||
# Add to return value
|
||||
data['steps'][step.order_index] = step_dict
|
||||
return data
|
||||
|
||||
@web.expose
|
||||
def import_workflow( self, trans, workflow_text=None, url=None ):
|
||||
if workflow_text is None and url is None:
|
||||
return form( url_for(), "Import Workflow", submit_text="Import" ) \
|
||||
.add_text( "url", "URL to load workflow from", "" ) \
|
||||
.add_input( "textarea", "Encoded workflow (as generated by export workflow)", "workflow_text", "" )
|
||||
if url:
|
||||
# Load workflow from external URL
|
||||
# NOTE: blocks the web thread.
|
||||
try:
|
||||
workflow_data = urllib2.urlopen( url ).read()
|
||||
except Exception, e:
|
||||
return trans.show_error_message( "Failed to open URL %s<br><br>Message: %s" % ( url, str( e ) ) )
|
||||
else:
|
||||
workflow_data = workflow_text
|
||||
# Convert incoming workflow data from json
|
||||
try:
|
||||
data = simplejson.loads( workflow_data )
|
||||
except Exception, e:
|
||||
return trans.show_error_message( "Data at '%s' does not appear to be a Galaxy workflow<br><br>Message: %s" % ( url, str( e ) ) )
|
||||
# Put parameters in workflow mode
|
||||
trans.workflow_building_mode = True
|
||||
# Create new workflow from incoming data
|
||||
workflow = model.Workflow()
|
||||
# Just keep the last name (user can rename later)
|
||||
workflow.name = data['name']
|
||||
# Assume no errors until we find a step that has some
|
||||
workflow.has_errors = False
|
||||
# Create each step
|
||||
steps = []
|
||||
# The editor will provide ids for each step that we don't need to save,
|
||||
# but do need to use to make connections
|
||||
steps_by_external_id = {}
|
||||
# First pass to build step objects and populate basic values
|
||||
for key, step_dict in data['steps'].iteritems():
|
||||
# Create the model class for the step
|
||||
step = model.WorkflowStep()
|
||||
steps.append( step )
|
||||
steps_by_external_id[ step_dict['id' ] ] = step
|
||||
# FIXME: Position should be handled inside module
|
||||
step.position = step_dict['position']
|
||||
module = module_factory.from_dict( trans, step_dict, secure=False )
|
||||
module.save_to_step( step )
|
||||
if step.tool_errors:
|
||||
workflow.has_errors = True
|
||||
# Stick this in the step temporarily
|
||||
step.temp_input_connections = step_dict['input_connections']
|
||||
# Save step annotation.
|
||||
annotation = step_dict[ 'annotation' ]
|
||||
if annotation:
|
||||
annotation = sanitize_html( annotation, 'utf-8', 'text/html' )
|
||||
self.add_item_annotation( trans, step, annotation )
|
||||
# Second pass to deal with connections between steps
|
||||
for step in steps:
|
||||
# Input connections
|
||||
for input_name, conn_dict in step.temp_input_connections.iteritems():
|
||||
if conn_dict:
|
||||
conn = model.WorkflowStepConnection()
|
||||
conn.input_step = step
|
||||
conn.input_name = input_name
|
||||
conn.output_name = conn_dict['output_name']
|
||||
conn.output_step = steps_by_external_id[ conn_dict['id'] ]
|
||||
del step.temp_input_connections
|
||||
# Order the steps if possible
|
||||
attach_ordered_steps( workflow, steps )
|
||||
# Connect up
|
||||
stored = model.StoredWorkflow()
|
||||
stored.name = workflow.name
|
||||
workflow.stored_workflow = stored
|
||||
stored.latest_workflow = workflow
|
||||
stored.user = trans.user
|
||||
# Persist
|
||||
trans.sa_session.add( stored )
|
||||
trans.sa_session.flush()
|
||||
# Return something informative
|
||||
errors = []
|
||||
if workflow.has_errors:
|
||||
return trans.show_warn_message( "Imported, but some steps in this workflow have validation errors" )
|
||||
if workflow.has_cycles:
|
||||
return trans.show_warn_message( "Imported, but this workflow contains cycles" )
|
||||
else:
|
||||
return trans.show_message( "Workflow '%s' imported" % workflow.name )
|
||||
|
||||
@web.json
|
||||
def get_datatypes( self, trans ):
|
||||
ext_to_class_name = dict()
|
||||
|
||||
@@ -66,6 +66,15 @@ def json( func ):
|
||||
decorator.exposed = True
|
||||
return decorator
|
||||
|
||||
def json_pretty( func ):
|
||||
def decorator( self, trans, *args, **kwargs ):
|
||||
trans.response.set_content_type( "text/javascript" )
|
||||
return simplejson.dumps( func( self, trans, *args, **kwargs ), indent=4, sort_keys=True )
|
||||
if not hasattr(func, '_orig'):
|
||||
decorator._orig = func
|
||||
decorator.exposed = True
|
||||
return decorator
|
||||
|
||||
def require_login( verb="perform this action", use_panels=False, webapp='galaxy' ):
|
||||
def argcatcher( func ):
|
||||
def decorator( self, trans, *args, **kwargs ):
|
||||
|
||||
@@ -96,7 +96,7 @@ class InputDataModule( WorkflowModule ):
|
||||
module.state = dict( name="Input Dataset" )
|
||||
return module
|
||||
@classmethod
|
||||
def from_dict( Class, trans, d ):
|
||||
def from_dict( Class, trans, d, secure=False ):
|
||||
module = Class( trans )
|
||||
state = from_json_string( d["tool_state"] )
|
||||
module.state = dict( name=state.get( "name", "Input Dataset" ) )
|
||||
@@ -172,11 +172,11 @@ class ToolModule( WorkflowModule ):
|
||||
module.state = module.tool.new_state( trans, all_pages=True )
|
||||
return module
|
||||
@classmethod
|
||||
def from_dict( Class, trans, d ):
|
||||
def from_dict( Class, trans, d, secure=False ):
|
||||
tool_id = d['tool_id']
|
||||
module = Class( trans, tool_id )
|
||||
module.state = DefaultToolState()
|
||||
module.state.decode( d["tool_state"], module.tool, module.trans.app )
|
||||
module.state.decode( d["tool_state"], module.tool, module.trans.app, secure=False )
|
||||
module.errors = d.get( "tool_errors", None )
|
||||
return module
|
||||
|
||||
@@ -199,8 +199,8 @@ class ToolModule( WorkflowModule ):
|
||||
return self.tool.name
|
||||
def get_tool_id( self ):
|
||||
return self.tool_id
|
||||
def get_state( self ):
|
||||
return self.state.encode( self.tool, self.trans.app )
|
||||
def get_state( self, secure=True ):
|
||||
return self.state.encode( self.tool, self.trans.app, secure=secure )
|
||||
def get_errors( self ):
|
||||
return self.errors
|
||||
def get_tooltip( self ):
|
||||
@@ -278,13 +278,13 @@ class WorkflowModuleFactory( object ):
|
||||
"""
|
||||
assert type in self.module_types
|
||||
return self.module_types[type].new( trans, tool_id )
|
||||
def from_dict( self, trans, d ):
|
||||
def from_dict( self, trans, d, **kwargs ):
|
||||
"""
|
||||
Return module initialized from the data in dictionary `d`.
|
||||
"""
|
||||
type = d['type']
|
||||
assert type in self.module_types
|
||||
return self.module_types[type].from_dict( trans, d )
|
||||
return self.module_types[type].from_dict( trans, d, **kwargs )
|
||||
def from_workflow_step( self, trans, step ):
|
||||
"""
|
||||
Return module initializd from the WorkflowStep object `step`.
|
||||
|
||||
+5
-1
@@ -60,7 +60,11 @@
|
||||
</label>
|
||||
%endif
|
||||
<div class="form-row-input">
|
||||
<input type="${input.type}" name="${input.name}" value="${input.value}" size="40">
|
||||
%if input.type == 'textarea':
|
||||
<textarea name="${input.name}" cols="40">${input.value}</textarea>
|
||||
%else:
|
||||
<input type="${input.type}" name="${input.name}" value="${input.value}" size="40">
|
||||
%endif
|
||||
</div>
|
||||
%if input.error:
|
||||
<div class="form-row-error-message">${input.error}</div>
|
||||
|
||||
Reference in New Issue
Block a user