mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Cleaning up ColumnListParameter and related things.
- Dependencies between parameters and refresh_on_change are now more general. Any parameter can assert a dependency on any other parameter by name, and if a parameter has dependencies it will cause a refresh. - Use "other_values" to get at the value of the associated dataset rather than trying to look at the tool. This means that column lists now work inside grouping constructs. - Eliminated tool.updated_params and use of self.legal_values. These variables are GLOBAL. Everybody using a tool has a reference to the same instance of tool. Thus, if two people are using the same tool at once this can cause serious problems.
This commit is contained in:
@@ -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, "<conditional> must have a child <param>"
|
||||
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 "<param>" 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 ):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 @@
|
||||
</label>
|
||||
|
||||
#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
|
||||
<div style="float: left; width: 250px; margin-right: 10px;">$field.get_html( $prefix )</div>
|
||||
#if $parent_errors.has_key( $param.name ):
|
||||
<div style="float: left; color: red; font-weight: bold; padding-top: 1px; padding-bottom: 3px;">
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
<param name="input"
|
||||
type="data" format="tabular"
|
||||
label="Dataset"/>
|
||||
<param name="xcol" type="integer"
|
||||
<param name="xcol" type="data_column" data_ref="input"
|
||||
value="1" size="30"
|
||||
label="Column for x axis"/>
|
||||
<param name="ycol" type="integer"
|
||||
<param name="ycol" type="data_column" data_ref="input"
|
||||
value="1" size="30"
|
||||
label="Column for y axis"/>
|
||||
<conditional name="series_type">
|
||||
|
||||
Reference in New Issue
Block a user