diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 03c809253d5..19036627367 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -172,7 +172,6 @@ class Tool: self.config_file = config_file self.tool_dir = os.path.dirname( config_file ) self.app = app - self.updated_params = {} # Parse XML element containing configuration self.parse( root ) @@ -408,20 +407,21 @@ class Tool: display = None return display, inputs - def parse_input_elem( self, parent_elem, enctypes ): + def parse_input_elem( self, parent_elem, enctypes, context=None ): """ Parse a parent element whose children are inputs -- these could be groups (repeat, conditional) or param elements. Groups will be parsed recursively. """ rval = odict() + context = ExpressionContext( rval, context ) for elem in parent_elem: # Repeat group if elem.tag == "repeat": group = Repeat() group.name = elem.get( "name" ) group.title = elem.get( "title" ) - group.inputs = self.parse_input_elem( elem, enctypes ) + group.inputs = self.parse_input_elem( elem, enctypes, context ) rval[group.name] = group elif elem.tag == "conditional": group = Conditional() @@ -429,20 +429,22 @@ class Tool: # Should have one child "input" which determines the case input_elem = elem.find( "param" ) assert input_elem is not None, " must have a child " - group.test_param = self.parse_param_elem( input_elem, enctypes ) + group.test_param = self.parse_param_elem( input_elem, enctypes, context ) + # Must refresh when test_param changes + group.test_param.refresh_on_change = True # And a set of possible cases for case_elem in elem.findall( "when" ): case = ConditionalWhen() case.value = case_elem.get( "value" ) - case.inputs = self.parse_input_elem( case_elem, enctypes ) + case.inputs = self.parse_input_elem( case_elem, enctypes, context ) group.cases.append( case ) rval[group.name] = group elif elem.tag == "param": - param = self.parse_param_elem( elem, enctypes ) + param = self.parse_param_elem( elem, enctypes, context ) rval[param.name] = param return rval - def parse_param_elem( self, input_elem, enctypes ): + def parse_param_elem( self, input_elem, enctypes, context ): """ Parse a single "" element and return a ToolParameter instance. Also, if the parameter has a 'required_enctype' add it to the set @@ -452,6 +454,10 @@ class Tool: param_enctype = param.get_required_enctype() if param_enctype: enctypes.add( param_enctype ) + # If parameter depends on any other paramters, we must refresh the + # form when it changes + for name in param.get_dependencies(): + context[ name ].refresh_on_change = True return param def new_state( self, trans ): @@ -461,7 +467,6 @@ class Tool: """ state = DefaultToolState() state.inputs = {} - self.updated_params = {} self.fill_in_new_state( trans, self.inputs_by_page[ 0 ], state.inputs ) return state @@ -662,7 +667,6 @@ class Tool: if error: errors[ input.name ] = error state[input.name] = value - self.updated_params[input.name] = value return errors def check_param( self, trans, param, incoming_value, param_values ): diff --git a/lib/galaxy/tools/parameters.py b/lib/galaxy/tools/parameters.py index b59f03cc167..c5785b83d97 100644 --- a/lib/galaxy/tools/parameters.py +++ b/lib/galaxy/tools/parameters.py @@ -21,8 +21,9 @@ class ToolParameter( object ): moment but in the future should encapsulate more complex parameters (lists of valid choices, validation logic, ...) """ - def __init__( self, tool, param ): + def __init__( self, tool, param, context=None ): self.tool = tool + self.refresh_on_change = False self.name = param.get("name") self.type = param.get("type") self.label = util.xml_text(param, "label") @@ -70,6 +71,12 @@ class ToolParameter( object ): """ return None + def get_dependencies( self ): + """ + Return the names of any other parameters this parameter depends on + """ + return [] + def filter_value( self, value, trans=None, other_values={} ): """ Parse the value returned by the view into a form usable by the tool OR @@ -99,6 +106,13 @@ class ToolParameter( object ): return value else: return self.to_python( value, app ) + + def value_to_display_text( self, value, app ): + """ + Convert a value to a text representation suitable for displaying to + the user + """ + return value def to_param_dict_string( self, value ): return str( value ) @@ -108,7 +122,7 @@ class ToolParameter( object ): validator.validate( value, history ) @classmethod - def build( cls, tool, param): + def build( cls, tool, param ): """Factory method to create parameter of correct type""" param_type = param.get("type") if not param_type or param_type not in parameter_types: @@ -415,7 +429,7 @@ class SelectToolParameter( ToolParameter ): return eval( self.dynamic_options, self.tool.code_namespace, other_values ) else: return self.options - def get_legal_values( self, other_values ): + def get_legal_values( self, trans, other_values ): if self.dynamic_options: return set( v for _, v, _ in eval( self.dynamic_options, self.tool.code_namespace, other_values ) ) else: @@ -431,19 +445,21 @@ class SelectToolParameter( ToolParameter ): field.add_option( text, optval, selected ) return field def from_html( self, value, trans=None, other_values={} ): - legal_values = self.get_legal_values( other_values ) + legal_values = self.get_legal_values( trans, other_values ) if isinstance( value, list ): if not(self.repeat): assert self.multiple, "Multiple values provided but parameter is not expecting multiple values" rval = [] for v in value: v = util.restore_text( v ) - assert v in legal_values + if v not in legal_values: + raise ValueError( "An invalid option was selected, please verify" ) rval.append( v ) return rval else: value = util.restore_text( value ) - assert value in legal_values + if value not in legal_values: + raise ValueError( "An invalid option was selected, please verify" ) return value def to_param_dict_string( self, value ): if value is None: @@ -509,7 +525,7 @@ class GenomeBuildParameter( SelectToolParameter ): last_used_build = trans.history.genome_build for dbkey, build_name in util.dbnames: yield build_name, dbkey, ( dbkey == last_used_build ) - def get_legal_values( self, other_values ): + def get_legal_values( self, trans, other_values ): return set( dbkey for dbkey, _ in util.dbnames ) class ColumnListParameter( SelectToolParameter ): @@ -535,71 +551,47 @@ class ColumnListParameter( SelectToolParameter ): """ def __init__( self, tool, elem ): SelectToolParameter.__init__( self, tool, elem ) - self.tool = tool self.numerical = str_bool( elem.get( "numerical", False )) - self.assoc_dataset = elem.get( "assoc_dataset", None ) - - def get_options( self, trans, other_values ): - columnList = [] - dataset_id = 0 - dataset = None - assoc_dataset = self.tool.get_param( self.assoc_dataset ) - - assert assoc_dataset is not None, "Tools that include a ColumnListParameter must also include a DataToolParameter" - - if len( self.tool.updated_params ) == 0: - """ - This will execute when the tool page is initially loading and the - user has not yet manually selected a dataset. In this case, ad_val - will be a DataToolParameter. - """ - field = assoc_dataset.get_html_field(trans, assoc_dataset, other_values ) - - """We need to make sure there is a dataset of the proper format in the history.""" - some_data = bool( field.options ) - if not some_data: - return columnList - elif len( field.options ) == 1 and field.options[0][0] == 'no data has the proper type': - return columnList - - if dataset_id == 0: - for txt, val, selected in field.options: - if selected: - dataset_id = val - break - if dataset_id == 0: - """This should be the latest dataset in the history""" - dataset_id = str(field.options[-1][1]) - - dataset = assoc_dataset.from_html(dataset_id, trans, other_values ) - - else: - """ - This will execute whenever the user selects a dataset from the input data - select list. Doing this results in executing the tool's update_state function, - so we can retrieve the selected dataset from the tool. - """ - dataset = self.tool.updated_params[self.assoc_dataset] - - assert dataset is not None, "Error retrieving required dataset for ColumnListParameter" - - """Just to be safe...""" + self.data_ref = elem.get( "data_ref", None ) + if self.data_ref is None: + self.data_ref = elem.get( "assoc_dataset", None ) + def get_column_list( self, trans, other_values ): + """ + Generate a select list containing the columns of the associated + dataset (if found). + """ + column_list = [] + # No value indicates a configuration error, the named DataToolParameter + # must preceed this parameter in the config + assert self.data_ref in other_values, "Value for associated DataToolParameter not found" + # Get the value of the associated DataToolParameter (a dataset) + dataset = other_values[ self.data_ref ] + # Check if a dataset is selected + if dataset is None or dataset == '': + # NOTE: Both of these values indicate that no dataset is selected. + # However, 'None' indicates that the dataset is optional + # while '' indicates that it is not. Currently column + # parameters do not work well with optional datasets + return column_list + # Just to be safe... (FIXME: Is this still neccesary?) dataset.set_meta() - + # Generate options if self.numerical: + # If numerical was requsted, filter columns based on metadata for i, col in enumerate( dataset.metadata.column_types ): if col == 'int' or col == 'float': - self.legal_values.add(str( i+1 )) - option = 'c' + str( i+1 ) - columnList.append((option,str( i+1 ),False)) + column_list.append( str( i + 1 ) ) else: - for col in range (0, dataset.metadata.columns): - self.legal_values.add(str(col+1)) - option = 'c' + str(col+1) - columnList.append((option,str(col+1),False)) - - return columnList + column_list = [ str( i + 1 ) for i in range(0, dataset.metadata.columns) ] + return column_list + def get_options( self, trans, other_values ): + column_list = self.get_column_list( trans, other_values ) + return [ ( "c" + col, col, False ) for col in column_list ] + def get_legal_values( self, trans, other_values ): + return set( self.get_column_list( trans, other_values ) ) + def get_dependencies( self ): + return [ self.data_ref ] class DataToolParameter( ToolParameter ): """ @@ -655,11 +647,11 @@ class DataToolParameter( ToolParameter ): if type( value ) != list: value = [ value ] field = form_builder.SelectField( self.name, self.multiple, None, self.refresh_on_change ) if self.dynamic_options: - """Dynamic options for a DataToolParameter specify limits on acceptrable build, id, or extension""" + # Dynamic options for a DataToolParameter specify limits on acceptrable build, id, or extension option_build, option_id, option_extension = \ eval( self.dynamic_options, self.tool.code_namespace, other_values ) - """CRUCIAL: the dataset_collector function needs to be local to DataToolParameter.get_html_field()""" - def dataset_collector( datasets, parent_hid ): + # CRUCIAL: the dataset_collector function needs to be local to DataToolParameter.get_html_field() + def dataset_collector( datasets, parent_hid ): for i, data in enumerate( datasets ): if parent_hid is not None: hid = "%s.%d" % ( parent_hid, i + 1 ) @@ -678,7 +670,6 @@ class DataToolParameter( ToolParameter ): field.add_option( "%s: %s" % ( hid, data.name[:30] ), data.id, selected ) # Also collect children via association object dataset_collector( [ assoc.child for assoc in data.children ], hid ) - dataset_collector( history.datasets, None ) some_data = bool( field.options ) if some_data: @@ -692,6 +683,41 @@ class DataToolParameter( ToolParameter ): field.add_option( "Selection is Optional", 'None', True ) return field + def get_initial_value( self, trans, context ): + """ + NOTE: This is wasteful since dynamic options and dataset collection + happens twice (here and when generating HTML). + """ + assert trans is not None, "DataToolParameter requires a trans" + history = trans.history + assert history is not None, "DataToolParameter requires a history" + if self.dynamic_options: + # Dynamic options for a DataToolParameter specify limits on acceptrable build, id, or extension + option_build, option_id, option_extension = \ + eval( self.dynamic_options, self.tool.code_namespace, other_values ) + most_recent_dataset = [None] + def dataset_collector( datasets ): + for i, data in enumerate( datasets ): + if self.dynamic_options: + if ( isinstance( data.datatype, self.formats ) + and (data.dbkey == option_build) and (data.id != option_id) + and (data.extension in option_extension) + and not data.deleted ): + most_recent_dataset[0] = data + else: + if isinstance( data.datatype, self.formats) and not data.deleted: + most_recent_dataset[0] = data + # Also collect children via association object + dataset_collector( [ assoc.child for assoc in data.children ] ) + dataset_collector( history.datasets ) + most_recent_dataset = most_recent_dataset.pop() + if most_recent_dataset is not None: + return most_recent_dataset + elif self.optional: + return None + else: + return '' + def from_html( self, value, trans, other_values={} ): if not value: raise ValueError( "A data of the appropriate type is required" ) @@ -724,6 +750,9 @@ class DataToolParameter( ToolParameter ): def to_param_dict_string( self, value ): return value.file_name + + def value_to_display_text( self, value, app ): + return "%s: %s" % ( value.hid, value.name ) # class RawToolParameter( ToolParameter ): # """ @@ -781,6 +810,7 @@ parameter_types = dict( text = TextToolParameter, genomebuild = GenomeBuildParameter, select = SelectToolParameter, columnlist = ColumnListParameter, + data_column = ColumnListParameter, hidden = HiddenToolParameter, baseurl = BaseURLToolParameter, file = FileToolParameter, diff --git a/templates/tool_form.tmpl b/templates/tool_form.tmpl index b55f67dd286..2d88eae4e7e 100644 --- a/templates/tool_form.tmpl +++ b/templates/tool_form.tmpl @@ -44,20 +44,15 @@ #set group_errors = $errors.get( $input.name, {} ) #set current_case = $group_state['__current_case__'] #set prefix = $prefix + $input.name + "|" - $row_for_param( $prefix, $input.test_param, $group_state, $group_errors, $context, refresh=True ) + $row_for_param( $prefix, $input.test_param, $group_state, $group_errors, $context ) $do_inputs( $input.cases[$current_case].inputs, $group_state, $group_errors, $prefix, $context ) #else - #try - #set refresh = $input.refresh_on_change - #except - #set refresh = False - #end try - $row_for_param( $prefix, $input, $tool_state, $errors, $context, $refresh ) + $row_for_param( $prefix, $input, $tool_state, $errors, $context ) #end if #end for #end def -#def row_for_param( $prefix, $param, $parent_state, $parent_errors, $context, $refresh=False ) +#def row_for_param( $prefix, $param, $parent_state, $parent_errors, $context ) #if $parent_errors.has_key( $param.name ): #set cls = "form-row form-row-error" #else @@ -69,7 +64,7 @@ #set field = $param.get_html_field( $caller, $parent_state[ $param.name ], $context ) - #set $field.refresh_on_change = $refresh + #set $field.refresh_on_change = $param.refresh_on_change
$field.get_html( $prefix )
#if $parent_errors.has_key( $param.name ):
diff --git a/tools/plotting/xy_plot.xml b/tools/plotting/xy_plot.xml index b84a2a76b60..b4dbfcc9d20 100644 --- a/tools/plotting/xy_plot.xml +++ b/tools/plotting/xy_plot.xml @@ -18,10 +18,10 @@ - -