root/galaxy-central/lib/galaxy/tools/parameters/dynamic_options.py

リビジョン 2, 22.7 KB (コミッタ: hatakeyama, 14 年 前)

import galaxy-central

行番号 
1"""
2Support for generating the options for a SelectToolParameter dynamically (based
3on the values of other parameters or other aspects of the current state)
4"""
5
6import operator, sys, os, logging
7import basic, validation
8from galaxy.util import string_as_bool
9
10log = logging.getLogger(__name__)
11
12class Filter( object ):
13    """
14    A filter takes the current options list and modifies it.
15    """
16    @classmethod
17    def from_element( cls, d_option, elem ):
18        """Loads the proper filter by the type attribute of elem"""
19        type = elem.get( 'type', None )
20        assert type is not None, "Required 'type' attribute missing from filter"
21        return filter_types[type.strip()]( d_option, elem )
22    def __init__( self, d_option, elem ):
23        self.dynamic_option = d_option
24        self.elem = elem
25    def get_dependency_name( self ):
26        """Returns the name of any depedencies, otherwise None"""
27        return None
28    def filter_options( self, options, trans, other_values ):
29        """Returns a list of options after the filter is applied"""
30        raise TypeError( "Abstract Method" )
31
32class StaticValueFilter( Filter ):
33    """
34    Filters a list of options on a column by a static value.
35   
36    Type: static_value
37   
38    Required Attributes:
39        value: static value to compare to
40        column: column in options to compare with
41    Optional Attributes:
42        keep: Keep columns matching value (True)
43              Discard columns matching value (False)
44    """
45    def __init__( self, d_option, elem ):
46        Filter.__init__( self, d_option, elem )
47        self.value = elem.get( "value", None )
48        assert self.value is not None, "Required 'value' attribute missing from filter"
49        column = elem.get( "column", None )
50        assert column is not None, "Required 'column' attribute missing from filter, when loading from file"
51        self.column = d_option.column_spec_to_index( column )
52        self.keep = string_as_bool( elem.get( "keep", 'True' ) )
53    def filter_options( self, options, trans, other_values ):
54        rval = []
55        for fields in options:
56            if ( self.keep and fields[self.column] == self.value ) or ( not self.keep and fields[self.column] != self.value ):
57                rval.append( fields )
58        return rval
59
60class DataMetaFilter( Filter ):
61    """
62    Filters a list of options on a column by a dataset metadata value.
63   
64    Type: data_meta
65   
66    When no 'from_' source has been specified in the <options> tag, this will populate the options list with (meta_value, meta_value, False).
67    Otherwise, options which do not match the metadata value in the column are discarded.
68   
69    Required Attributes:
70        ref: Name of input dataset
71        key: Metadata key to use for comparison
72        column: column in options to compare with (not required when not associated with input options)
73    Optional Attributes:
74        multiple: Option values are multiple, split column by separator (True)
75        separator: When multiple split by this (,)
76    """
77    def __init__( self, d_option, elem ):
78        Filter.__init__( self, d_option, elem )
79        self.ref_name = elem.get( "ref", None )
80        assert self.ref_name is not None, "Required 'ref' attribute missing from filter"
81        d_option.has_dataset_dependencies = True
82        self.key = elem.get( "key", None )
83        assert self.key is not None, "Required 'key' attribute missing from filter"
84        self.column = elem.get( "column", None )
85        if self.column is None:
86            assert self.dynamic_option.file_fields is None and self.dynamic_option.dataset_ref_name is None, "Required 'column' attribute missing from filter, when loading from file"
87        else:
88            self.column = d_option.column_spec_to_index( self.column )
89        self.multiple = string_as_bool( elem.get( "multiple", "False" ) )
90        self.separator = elem.get( "separator", "," )
91    def get_dependency_name( self ):
92        return self.ref_name
93    def filter_options( self, options, trans, other_values ):
94        def compare_meta_value( file_value, dataset_value ):
95            if isinstance( dataset_value, list ):
96                if self.multiple:
97                    file_value = file_value.split( self.separator )
98                    for value in dataset_value:
99                        if value not in file_value:
100                            return False
101                    return True
102                return file_value in dataset_value
103            if self.multiple:
104                return dataset_value in file_value.split( self.separator )
105            return file_value == dataset_value
106        assert self.ref_name in other_values or ( trans is not None and trans.workflow_building_mode), "Required dependency '%s' not found in incoming values" % self.ref_name
107        ref = other_values.get( self.ref_name, None )
108        if not isinstance( ref, self.dynamic_option.tool_param.tool.app.model.HistoryDatasetAssociation ):
109            return [] #not a valid dataset
110        meta_value = ref.metadata.get( self.key, None )
111        if meta_value is None: #assert meta_value is not None, "Required metadata value '%s' not found in referenced dataset" % self.key
112            return [ ( disp_name, basic.UnvalidatedValue( optval ), selected ) for disp_name, optval, selected in options ]
113       
114        if self.column is not None:
115            rval = []
116            for fields in options:
117                if compare_meta_value( fields[self.column], meta_value ):
118                    rval.append( fields )
119            return rval
120        else:
121            if not isinstance( meta_value, list ):
122                meta_value = [meta_value]
123            for value in meta_value:
124                options.append( ( value, value, False ) )
125            return options
126
127class ParamValueFilter( Filter ):
128    """
129    Filters a list of options on a column by the value of another input.
130   
131    Type: param_value
132   
133    Required Attributes:
134        ref: Name of input value
135        column: column in options to compare with
136    Optional Attributes:
137        keep: Keep columns matching value (True)
138              Discard columns matching value (False)
139        ref_attribute: Period (.) separated attribute chain of input (ref) to use as value for filter
140    """
141    def __init__( self, d_option, elem ):
142        Filter.__init__( self, d_option, elem )
143        self.ref_name = elem.get( "ref", None )
144        assert self.ref_name is not None, "Required 'ref' attribute missing from filter"
145        column = elem.get( "column", None )
146        assert column is not None, "Required 'column' attribute missing from filter"
147        self.column = d_option.column_spec_to_index( column )
148        self.keep = string_as_bool( elem.get( "keep", 'True' ) )
149        self.ref_attribute = elem.get( "ref_attribute", None )
150        if self.ref_attribute:
151            self.ref_attribute = self.ref_attribute.split( '.' )
152        else:
153            self.ref_attribute = []
154    def get_dependency_name( self ):
155        return self.ref_name
156    def filter_options( self, options, trans, other_values ):
157        if trans is not None and trans.workflow_building_mode: return []
158        assert self.ref_name in other_values, "Required dependency '%s' not found in incoming values" % self.ref_name
159        ref = other_values.get( self.ref_name, None )
160        for ref_attribute in self.ref_attribute:
161            if not hasattr( ref, ref_attribute ):
162                return [] #ref does not have attribute, so we cannot filter, return empty list
163            ref = getattr( ref, ref_attribute )
164        ref = str( ref )
165        rval = []
166        for fields in options:
167            if ( self.keep and fields[self.column] == ref ) or ( not self.keep and fields[self.column] != ref ):
168                rval.append( fields )
169        return rval
170
171class UniqueValueFilter( Filter ):
172    """
173    Filters a list of options to be unique by a column value.
174   
175    Type: unique_value
176   
177    Required Attributes:
178        column: column in options to compare with
179    """
180    def __init__( self, d_option, elem ):
181        Filter.__init__( self, d_option, elem )
182        column = elem.get( "column", None )
183        assert column is not None, "Required 'column' attribute missing from filter"
184        self.column = d_option.column_spec_to_index( column )
185    def get_dependency_name( self ):
186        return self.dynamic_option.dataset_ref_name
187    def filter_options( self, options, trans, other_values ):
188        rval = []
189        skip_list = []
190        for fields in options:
191            if fields[self.column] not in skip_list:
192                rval.append( fields )
193                skip_list.append( fields[self.column] )
194        return rval
195
196class MultipleSplitterFilter( Filter ):
197    """
198    Turns a single line of options into multiple lines, by splitting a column and creating a line for each item.
199   
200    Type: multiple_splitter
201   
202    Required Attributes:
203        column: column in options to compare with
204    Optional Attributes:
205        separator: Split column by this (,)
206    """
207    def __init__( self, d_option, elem ):
208        Filter.__init__( self, d_option, elem )
209        self.separator = elem.get( "separator", "," )
210        columns = elem.get( "column", None )
211        assert columns is not None, "Required 'columns' attribute missing from filter"
212        self.columns = [ d_option.column_spec_to_index( column ) for column in columns.split( "," ) ]
213    def filter_options( self, options, trans, other_values ):
214        rval = []
215        for fields in options:
216            for column in self.columns:
217                for field in fields[column].split( self.separator ):
218                    rval.append( fields[0:column] + [field] + fields[column+1:] )
219        return rval
220       
221class AttributeValueSplitterFilter( Filter ):
222    """
223    Filters a list of attribute-value pairs to be unique attribute names.
224
225    Type: attribute_value_splitter
226
227    Required Attributes:
228        column: column in options to compare with
229    Optional Attributes:
230        pair_separator: Split column by this (,)
231        name_val_separator: Split name-value pair by this ( whitespace )
232    """
233    def __init__( self, d_option, elem ):
234        Filter.__init__( self, d_option, elem )
235        self.pair_separator = elem.get( "pair_separator", "," )
236        self.name_val_separator = elem.get( "name_val_separator", None )
237        self.columns = elem.get( "column", None )
238        assert self.columns is not None, "Required 'columns' attribute missing from filter"
239        self.columns = [ int ( column ) for column in self.columns.split( "," ) ]
240    def filter_options( self, options, trans, other_values ):
241        attr_names = []
242        rval = []
243        for fields in options:
244            for column in self.columns:
245                for pair in fields[column].split( self.pair_separator ):
246                    ary = pair.split( self.name_val_separator )
247                    if len( ary ) == 2:
248                        name, value = ary
249                        if name not in attr_names:
250                            rval.append( fields[0:column] + [name] + fields[column:] )
251                            attr_names.append( name )
252        return rval
253
254
255class AdditionalValueFilter( Filter ):
256    """
257    Adds a single static value to an options list.
258   
259    Type: add_value
260   
261    Required Attributes:
262        value: value to appear in select list
263    Optional Attributes:
264        name: Display name to appear in select list (value)
265        index: Index of option list to add value (APPEND)
266    """
267    def __init__( self, d_option, elem ):
268        Filter.__init__( self, d_option, elem )
269        self.value = elem.get( "value", None )
270        assert self.value is not None, "Required 'value' attribute missing from filter"
271        self.name = elem.get( "name", None )
272        if self.name is None:
273            self.name = self.value
274        self.index = elem.get( "index", None )
275        if self.index is not None:
276            self.index = int( self.index )
277    def filter_options( self, options, trans, other_values ):
278        rval = list( options )
279        add_value = []
280        for i in range( self.dynamic_option.largest_index + 1 ):
281            add_value.append( "" )
282        add_value[self.dynamic_option.columns['value']] = self.value
283        add_value[self.dynamic_option.columns['name']] = self.name
284        if self.index is not None:
285            rval.insert( self.index, add_value )
286        else:
287            rval.append( add_value )
288        return rval
289
290class RemoveValueFilter( Filter ):
291    """
292    Removes a value from an options list.
293   
294    Type: remove_value
295   
296    Required Attributes:
297        value: value to remove from select list
298            or
299        ref: param to refer to
300            or
301        meta_ref: dataset to refer to
302        key: metadata key to compare to
303    """
304    def __init__( self, d_option, elem ):
305        Filter.__init__( self, d_option, elem )
306        self.value = elem.get( "value", None )
307        self.ref_name = elem.get( "ref", None )
308        self.meta_ref = elem.get( "meta_ref", None )
309        self.metadata_key = elem.get( "key", None )
310        assert self.value is not None or ( ( self.ref_name is not None or self.meta_ref is not None )and self.metadata_key is not None ), ValueError( "Required 'value' or 'ref' and 'key' attributes missing from filter" )
311        self.multiple = string_as_bool( elem.get( "multiple", "False" ) )
312        self.separator = elem.get( "separator", "," )
313    def filter_options( self, options, trans, other_values ):
314        if trans is not None and trans.workflow_building_mode: return options
315        assert self.value is not None or ( self.ref_name is not None and self.ref_name in other_values ) or (self.meta_ref is not None and self.meta_ref in other_values ) or ( trans is not None and trans.workflow_building_mode), Exception( "Required dependency '%s' or '%s' not found in incoming values" % ( self.ref_name, self.meta_ref ) )
316        def compare_value( option_value, filter_value ):
317            if isinstance( filter_value, list ):
318                if self.multiple:
319                    option_value = option_value.split( self.separator )
320                    for value in filter_value:
321                        if value not in filter_value:
322                            return False
323                    return True
324                return option_value in filter_value
325            if self.multiple:
326                return filter_value in option_value.split( self.separator )
327            return option_value == filter_value
328        value = self.value
329        if value is None:
330            if self.ref_name is not None:
331                value = other_values.get( self.ref_name )
332            else:
333                data_ref = other_values.get( self.meta_ref )
334                if not isinstance( data_ref, self.dynamic_option.tool_param.tool.app.model.HistoryDatasetAssociation ):
335                    return options #cannot modify options
336                value = data_ref.metadata.get( self.metadata_key, None )
337        return [ ( disp_name, optval, selected ) for disp_name, optval, selected in options if not compare_value( optval, value ) ]
338
339class SortByColumnFilter( Filter ):
340    """
341    Sorts an options list by a column
342   
343    Type: sort_by
344   
345    Required Attributes:
346        column: column to sort by
347    """
348    def __init__( self, d_option, elem ):
349        Filter.__init__( self, d_option, elem )
350        column = elem.get( "column", None )
351        assert column is not None, "Required 'column' attribute missing from filter"
352        self.column = d_option.column_spec_to_index( column )
353    def filter_options( self, options, trans, other_values ):
354        rval = []
355        for i, fields in enumerate( options ):
356            for j in range( 0, len( rval ) ):
357                if fields[self.column] < rval[j][self.column]:
358                    rval.insert( j, fields )
359                    break
360            else:
361                rval.append( fields )
362        return rval
363
364
365filter_types = dict( data_meta = DataMetaFilter,
366                     param_value = ParamValueFilter,
367                     static_value = StaticValueFilter,
368                     unique_value = UniqueValueFilter,
369                     multiple_splitter = MultipleSplitterFilter,
370                     attribute_value_splitter = AttributeValueSplitterFilter,
371                     add_value = AdditionalValueFilter,
372                     remove_value = RemoveValueFilter,
373                     sort_by = SortByColumnFilter )
374
375class DynamicOptions( object ):
376    """Handles dynamically generated SelectToolParameter options"""
377    def __init__( self, elem, tool_param  ):
378        def load_from_parameter( from_parameter, transform_lines = None ):
379            obj = self.tool_param
380            for field in from_parameter.split( '.' ):
381                obj = getattr( obj, field )
382            if transform_lines:
383                obj = eval( transform_lines )
384            return self.parse_file_fields( obj )
385        self.tool_param = tool_param
386        self.columns = {}
387        self.filters = []
388        self.file_fields = None
389        self.largest_index = 0
390        self.dataset_ref_name = None
391        # True if the options generation depends on one or more other parameters
392        # that are dataset inputs
393        self.has_dataset_dependencies = False
394        self.validators = []
395        self.converter_safe = True
396       
397        # Parse the <options> tag
398        self.separator = elem.get( 'separator', '\t' )
399        self.line_startswith = elem.get( 'startswith', None )
400        data_file = elem.get( 'from_file', None )
401        dataset_file = elem.get( 'from_dataset', None )
402        from_parameter = elem.get( 'from_parameter', None )
403        tool_data_table_name = elem.get( 'from_data_table', None )
404       
405        # Options are defined from a data table loaded by the app
406        self.tool_data_table = None
407        if tool_data_table_name:
408            app = tool_param.tool.app
409            assert tool_data_table_name in app.tool_data_tables, \
410                "Data table named '%s' is required by tool but not configured" % tool_data_table_name
411            self.tool_data_table = app.tool_data_tables[ tool_data_table_name ]
412            # Column definitions are optional, but if provided override those from the table
413            if elem.find( "column" ) is not None:
414                self.parse_column_definitions( elem )
415            else:
416                self.columns = self.tool_data_table.columns
417           
418        # Options are defined by parsing tabular text data from an data file
419        # on disk, a dataset, or the value of another parameter
420        elif data_file is not None or dataset_file is not None or from_parameter is not None:
421            self.parse_column_definitions( elem )
422            if data_file is not None:
423                data_file = data_file.strip()
424                if not os.path.isabs( data_file ):
425                    data_file = os.path.join( self.tool_param.tool.app.config.tool_data_path, data_file )
426                self.file_fields = self.parse_file_fields( open( data_file ) )
427            elif dataset_file is not None:
428                self.dataset_ref_name = dataset_file
429                self.has_dataset_dependencies = True
430                self.converter_safe = False
431            elif from_parameter is not None:
432                transform_lines = elem.get( 'transform_lines', None )
433                self.file_fields = list( load_from_parameter( from_parameter, transform_lines ) )
434       
435        # Load filters
436        for filter_elem in elem.findall( 'filter' ):
437            self.filters.append( Filter.from_element( self, filter_elem ) )
438       
439        # Load Validators
440        for validator in elem.findall( 'validator' ):
441            self.validators.append( validation.Validator.from_element( self.tool_param, validator ) )
442           
443    def parse_column_definitions( self, elem ):
444        for column_elem in elem.findall( 'column' ):
445            name = column_elem.get( 'name', None )
446            assert name is not None, "Required 'name' attribute missing from column def"
447            index = column_elem.get( 'index', None )
448            assert index is not None, "Required 'index' attribute missing from column def"
449            index = int( index )
450            self.columns[name] = index
451            if index > self.largest_index:
452                self.largest_index = index
453        assert 'value' in self.columns, "Required 'value' column missing from column def"
454        if 'name' not in self.columns:
455            self.columns['name'] = self.columns['value']
456   
457    def parse_file_fields( self, reader ):
458        rval = []
459        for line in reader:
460            if line.startswith( '#' ) or ( self.line_startswith and not line.startswith( self.line_startswith ) ):
461                continue
462            line = line.rstrip( "\n\r" )
463            if line:
464                fields = line.split( self.separator )
465                if self.largest_index < len( fields ):
466                    rval.append( fields )
467        return rval
468   
469    def get_dependency_names( self ):
470        """
471        Return the names of parameters these options depend on -- both data
472        and other param types.
473        """
474        rval = []
475        if self.dataset_ref_name:
476            rval.append( self.dataset_ref_name )
477        for filter in self.filters:
478            depend = filter.get_dependency_name()
479            if depend:
480                rval.append( depend )
481        return rval
482   
483    def get_fields( self, trans, other_values ):
484        if self.dataset_ref_name:
485            dataset = other_values.get( self.dataset_ref_name, None )
486            assert dataset is not None, "Required dataset '%s' missing from input" % self.dataset_ref_name
487            if not dataset: return [] #no valid dataset in history
488            options = self.parse_file_fields( open( dataset.file_name ) )
489        elif self.tool_data_table:
490            options = self.tool_data_table.get_fields()
491        else:
492            options = list( self.file_fields )
493        for filter in self.filters:
494            options = filter.filter_options( options, trans, other_values )
495        return options
496   
497    def get_options( self, trans, other_values ):
498        rval = []
499        if self.file_fields is not None or self.tool_data_table is not None or self.dataset_ref_name is not None:
500            options = self.get_fields( trans, other_values )
501            for fields in options:
502                rval.append( ( fields[self.columns['name']], fields[self.columns['value']], False ) )
503        else:
504            for filter in self.filters:
505                rval = filter.filter_options( rval, trans, other_values )
506        return rval
507   
508    def column_spec_to_index( self, column_spec ):
509        """
510        Convert a column specification (as read from the config file), to an
511        index. A column specification can just be a number, a column name, or
512        a column alias.
513        """
514        # Name?
515        if column_spec in self.columns:
516            return self.columns[column_spec]
517        # Int?
518        return int( column_spec )
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。