PageRenderTime 55ms CodeModel.GetById 12ms app.highlight 37ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/galaxy/tools/parameters/output.py

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 536 lines | 485 code | 31 blank | 20 comment | 135 complexity | 1a402fa572bb69e82d120fdd6371d1c3 MD5 | raw file
  1"""
  2Support for dynamically modifying output attributes.
  3"""
  4
  5import logging, os.path, string, re
  6from galaxy import util
  7
  8log = logging.getLogger( __name__ )
  9
 10class ToolOutputActionGroup( object ):
 11    """
 12    Manages a set of tool output dataset actions directives
 13    """
 14    tag = "group"
 15    def __init__( self, parent, config_elem ):
 16        self.parent = parent
 17        self.actions = []
 18        if config_elem:
 19            for elem in config_elem:
 20                if elem.tag == "conditional":
 21                    self.actions.append( ToolOutputActionConditional( self, elem ) )
 22                elif elem.tag == "action":
 23                    self.actions.append( ToolOutputAction.from_elem( self, elem ) )
 24                else:
 25                    log.debug( "Unknown ToolOutputAction tag specified: %s" % elem.tag )
 26    def apply_action( self, output_dataset, other_values ):
 27        for action in self.actions:
 28            action.apply_action( output_dataset, other_values )
 29    @property
 30    def tool( self ):
 31        return self.parent.tool
 32    def __len__( self ):
 33        return len( self.actions )
 34
 35class ToolOutputActionConditionalWhen( ToolOutputActionGroup ):
 36    tag="when"
 37    @classmethod
 38    def from_elem( cls, parent, when_elem ):
 39        """Loads the proper when by attributes of elem"""
 40        when_value = when_elem.get( "value", None )
 41        if when_value is not None:
 42            return ValueToolOutputActionConditionalWhen( parent, when_elem, when_value )
 43        else:
 44            when_value = when_elem.get( "datatype_isinstance", None )
 45            if when_value is not None:
 46                return DatatypeIsInstanceToolOutputActionConditionalWhen( parent, when_elem, when_value )
 47        raise TypeError( "When type not implemented" )
 48    def __init__( self, parent, config_elem, value ):
 49        super( ToolOutputActionConditionalWhen, self ).__init__( parent, config_elem )
 50        self.value = value
 51    def is_case( self, output_dataset, other_values ):
 52        raise TypeError( "Not implemented" )
 53    def get_ref( self, output_dataset, other_values ):
 54        ref = other_values
 55        for ref_name in self.parent.name:
 56            assert ref_name in ref, "Required dependency '%s' not found in incoming values" % ref_name
 57            ref = ref.get( ref_name )
 58        return ref
 59    def apply_action( self, output_dataset, other_values ):
 60        if self.is_case( output_dataset, other_values ):
 61            return super( ToolOutputActionConditionalWhen, self ).apply_action( output_dataset, other_values )
 62
 63class ValueToolOutputActionConditionalWhen( ToolOutputActionConditionalWhen ):
 64    tag = "when value"
 65    def is_case( self, output_dataset, other_values ):
 66        ref = self.get_ref( output_dataset, other_values )
 67        return bool( str( ref ) == self.value )
 68        
 69class DatatypeIsInstanceToolOutputActionConditionalWhen( ToolOutputActionConditionalWhen ):
 70    tag = "when datatype_isinstance"
 71    def __init__( self, parent, config_elem, value ):
 72        super( DatatypeIsInstanceToolOutputActionConditionalWhen, self ).__init__( parent, config_elem, value )
 73        self.value = type( self.tool.app.datatypes_registry.get_datatype_by_extension( value ) )
 74    def is_case( self, output_dataset, other_values ):
 75        ref = self.get_ref( output_dataset, other_values )
 76        return isinstance( ref.datatype, self.value )
 77
 78class ToolOutputActionConditional( object ):
 79    tag = "conditional"
 80    def __init__( self, parent, config_elem ):
 81        self.parent = parent
 82        self.name = config_elem.get( 'name', None )
 83        assert self.name is not None, "Required 'name' attribute missing from ToolOutputActionConditional"
 84        self.name = self.name.split( '.' )
 85        self.cases = []
 86        for when_elem in config_elem.findall( 'when' ):
 87            self.cases.append( ToolOutputActionConditionalWhen.from_elem( self, when_elem ) )
 88    def apply_action( self, output_dataset, other_values ):
 89        for case in self.cases:
 90            case.apply_action( output_dataset, other_values )
 91    @property
 92    def tool( self ):
 93        return self.parent.tool
 94
 95class ToolOutputAction( object ):
 96    tag = "action"
 97    @classmethod
 98    def from_elem( cls, parent, elem ):
 99        """Loads the proper action by the type attribute of elem"""
100        action_type = elem.get( 'type', None )
101        assert action_type is not None, "Required 'type' attribute missing from ToolOutputAction"
102        return action_types[ action_type ]( parent, elem )
103    def __init__( self, parent, elem ):
104        self.parent = parent
105        self.default = elem.get( 'default', None )
106        option_elem = elem.find( 'option' )
107        self.option = ToolOutputActionOption.from_elem( self, option_elem )
108    def apply_action( self, output_dataset, other_values ):
109        raise TypeError( "Not implemented" )
110    @property
111    def tool( self ):
112        return self.parent.tool
113
114class ToolOutputActionOption( object ):
115    tag = "object"
116    @classmethod
117    def from_elem( cls, parent, elem ):
118        """Loads the proper action by the type attribute of elem"""
119        if elem is None:
120            option_type = NullToolOutputActionOption.tag # no ToolOutputActionOption's have been defined, use implicit NullToolOutputActionOption
121        else:
122            option_type = elem.get( 'type', None )
123        assert option_type is not None, "Required 'type' attribute missing from ToolOutputActionOption"
124        return option_types[ option_type ]( parent, elem )
125    def __init__( self, parent, elem ):
126        self.parent = parent
127        self.filters = []
128        if elem is not None:
129            for filter_elem in elem.findall( 'filter' ):
130                self.filters.append( ToolOutputActionOptionFilter.from_elem( self, filter_elem ) )
131    def get_value( self, other_values ):
132        raise TypeError( "Not implemented" )
133    @property
134    def tool( self ):
135        return self.parent.tool
136
137class NullToolOutputActionOption( ToolOutputActionOption ):
138    tag = "null_option"
139    def get_value( self, other_values ):
140        return None
141
142class FromFileToolOutputActionOption( ToolOutputActionOption ):
143    tag = "from_file"
144    def __init__( self, parent, elem ):
145        super( FromFileToolOutputActionOption, self ).__init__( parent, elem )
146        self.name = elem.get( 'name', None )
147        assert self.name is not None, "Required 'name' attribute missing from FromFileToolOutputActionOption"
148        self.column = elem.get( 'column', None )
149        assert self.column is not None, "Required 'column' attribute missing from FromFileToolOutputActionOption"
150        self.column = int( self.column )
151        self.offset = elem.get( 'offset', -1 )
152        self.offset = int( self.offset )
153        self.separator = elem.get( 'separator', '\t' )
154        self.options = []
155        data_file = self.name
156        if not os.path.isabs( data_file ):
157            data_file = os.path.join( self.tool.app.config.tool_data_path, data_file )
158        for line in open( data_file ):
159            self.options.append( line.rstrip( '\n\r' ).split( self.separator ) )
160    def get_value( self, other_values ):
161        options = self.options
162        for filter in self.filters:
163            options = filter.filter_options( options, other_values )
164        try:
165            if options:
166                return str( options[ self.offset ][ self.column ] )
167        except Exception, e:
168            log.debug( "Error in FromFileToolOutputActionOption get_value: %s" % e )
169        return None
170
171class FromParamToolOutputActionOption( ToolOutputActionOption ):
172    tag = "from_param"
173    def __init__( self, parent, elem ):
174        super( FromParamToolOutputActionOption, self ).__init__( parent, elem )
175        self.name = elem.get( 'name', None )
176        assert self.name is not None, "Required 'name' attribute missing from FromFileToolOutputActionOption"
177        self.name = self.name.split( '.' )
178        self.column = elem.get( 'column', 0 )
179        self.column = int( self.column )
180        self.offset = elem.get( 'offset', -1 )
181        self.offset = int( self.offset )
182        self.param_attribute = elem.get( 'param_attribute', [] )
183        if self.param_attribute:
184            self.param_attribute = self.param_attribute.split( '.' )
185    def get_value( self, other_values ):
186        value = other_values
187        for ref_name in self.name:
188            assert ref_name in value, "Required dependency '%s' not found in incoming values" % ref_name
189            value = value.get( ref_name )
190        for attr_name in self.param_attribute:
191            # if the value is a list from a repeat tag you can access the first element of the repeat with
192            # artifical 'first' attribute_name. For example: .. param_attribute="first.input_mate1.ext"
193            if isinstance(value, list) and attr_name == 'first':
194                value = value[0]
195            elif isinstance(value, dict):
196                value = value[ attr_name ]
197            else:
198                value = getattr( value, attr_name )
199        options = [ [ str( value ) ] ]
200        for filter in self.filters:
201            options = filter.filter_options( options, other_values )
202        try:
203            if options:
204                return str( options[ self.offset ][ self.column ] )
205        except Exception, e:
206            log.debug( "Error in FromParamToolOutputActionOption get_value: %s" % e )
207        return None
208
209class FromDataTableOutputActionOption( ToolOutputActionOption ):
210    tag = "from_data_table"
211    #TODO: allow accessing by column 'name' not just index
212    def __init__( self, parent, elem ):
213        super( FromDataTableOutputActionOption, self ).__init__( parent, elem )
214        self.name = elem.get( 'name', None )
215        assert self.name is not None, "Required 'name' attribute missing from FromDataTableOutputActionOption"
216        self.missing_tool_data_table_name = None
217        if self.name in self.tool.app.tool_data_tables:
218            self.options = self.tool.app.tool_data_tables[ self.name ].get_fields()
219            self.column = elem.get( 'column', None )
220            assert self.column is not None, "Required 'column' attribute missing from FromDataTableOutputActionOption"
221            self.column = int( self.column )
222            self.offset = elem.get( 'offset', -1 )
223            self.offset = int( self.offset )
224        else:
225            self.options = []
226            self.missing_tool_data_table_name = self.name
227    def get_value( self, other_values ):
228        if self.options:
229            options = self.options
230        else:
231            options = []
232        for filter in self.filters:
233            options = filter.filter_options( options, other_values )
234        try:
235            if options:
236                return str( options[ self.offset ][ self.column ] )
237        except Exception, e:
238            log.debug( "Error in FromDataTableOutputActionOption get_value: %s" % e )
239        return None
240
241class MetadataToolOutputAction( ToolOutputAction ):
242    tag = "metadata"
243    def __init__( self, parent, elem ):
244        super( MetadataToolOutputAction, self ).__init__( parent, elem )
245        self.name = elem.get( 'name', None )
246        assert self.name is not None, "Required 'name' attribute missing from MetadataToolOutputAction"
247    def apply_action( self, output_dataset, other_values ):
248        value = self.option.get_value( other_values )
249        if value is None and self.default is not None:
250            value = self.default
251        if value is not None:
252            setattr( output_dataset.metadata, self.name, value )
253
254class FormatToolOutputAction( ToolOutputAction ):
255    tag = "format"
256    def __init__( self, parent, elem ):
257        super( FormatToolOutputAction, self ).__init__( parent, elem )
258        self.default = elem.get( 'default', None )
259        
260    def apply_action( self, output_dataset, other_values ):
261        value = self.option.get_value( other_values )
262        if value is None and self.default is not None:
263            value = self.default
264        if value is not None:
265            output_dataset.extension = value
266
267class ToolOutputActionOptionFilter( object ):
268    tag = "filter"
269    @classmethod
270    def from_elem( cls, parent, elem ):
271        """Loads the proper action by the type attribute of elem"""
272        filter_type = elem.get( 'type', None )
273        assert filter_type is not None, "Required 'type' attribute missing from ToolOutputActionOptionFilter"
274        return filter_types[ filter_type ]( parent, elem )
275    def __init__( self, parent, elem ):
276        self.parent = parent
277    def filter_options( self, options, other_values ):
278        raise TypeError( "Not implemented" )
279    @property
280    def tool( self ):
281        return self.parent.tool
282
283class ParamValueToolOutputActionOptionFilter( ToolOutputActionOptionFilter ):
284    tag = "param_value"
285    def __init__( self, parent, elem ):
286        super( ParamValueToolOutputActionOptionFilter, self ).__init__( parent, elem )
287        self.ref = elem.get( 'ref', None )
288        if self.ref:
289            self.ref = self.ref.split( '.' )
290        self.value = elem.get( 'value', None )
291        assert self.ref != self.value, "Required 'ref' or 'value' attribute missing from ParamValueToolOutputActionOptionFilter"
292        self.column = elem.get( 'column', None )
293        assert self.column is not None, "Required 'column' attribute missing from ParamValueToolOutputActionOptionFilter"
294        self.column = int( self.column )
295        self.keep = util.string_as_bool( elem.get( "keep", 'True' ) )
296        self.compare = parse_compare_type( elem.get( 'compare', None ) )
297        self.cast = parse_cast_attribute( elem.get( "cast", None ) )
298        self.param_attribute = elem.get( 'param_attribute', [] )
299        if self.param_attribute:
300            self.param_attribute = self.param_attribute.split( '.' )
301    def filter_options( self, options, other_values ):
302        if self.ref:
303            #find ref value
304            value = other_values
305            for ref_name in self.ref:
306                assert ref_name in value, "Required dependency '%s' not found in incoming values" % ref_name
307                value = value.get( ref_name )
308            for attr_name in self.param_attribute:
309                value = getattr( value, attr_name )
310            value = str( value )
311        else:
312            value = self.value
313        value = self.cast( value )
314        rval = []
315        for fields in options:
316            try:
317                if self.keep == ( self.compare( self.cast( fields[self.column] ), value ) ):
318                    rval.append( fields )
319            except Exception, e:
320                log.debug(e)
321                continue #likely a bad cast or column out of range
322        return rval
323
324class InsertColumnToolOutputActionOptionFilter( ToolOutputActionOptionFilter ):
325    tag = "insert_column"
326    def __init__( self, parent, elem ):
327        super( InsertColumnToolOutputActionOptionFilter, self ).__init__( parent, elem )
328        self.ref = elem.get( 'ref', None )
329        if self.ref:
330            self.ref = self.ref.split( '.' )
331        self.value = elem.get( 'value', None )
332        assert self.ref != self.value, "Required 'ref' or 'value' attribute missing from InsertColumnToolOutputActionOptionFilter"
333        self.column = elem.get( 'column', None ) #None is append
334        if self.column:
335            self.column = int( self.column )
336        self.iterate = util.string_as_bool( elem.get( "iterate", 'False' ) )
337    def filter_options( self, options, other_values ):
338        if self.ref:
339            #find ref value
340            value = other_values
341            for ref_name in self.ref:
342                assert ref_name in value, "Required dependency '%s' not found in incoming values" % ref_name
343                value = value.get( ref_name )
344            value = str( value )
345        else:
346            value = self.value
347        if self.iterate:
348            value = int( value )
349        rval = []
350        for fields in options:
351            if self.column is None:
352                rval.append( fields + [ str( value ) ] )
353            else:
354                fields = list( fields )
355                fields.insert( self.column, str( value ) )
356                rval.append( fields )
357            if self.iterate:
358                value += 1
359        return rval
360
361class MultipleSplitterFilter( ToolOutputActionOptionFilter ):
362    tag = "multiple_splitter"
363    def __init__( self, parent, elem ):
364        super( MultipleSplitterFilter, self ).__init__( parent, elem )
365        self.column = elem.get( 'column', None )
366        assert self.column is not None, "Required 'column' attribute missing from MultipleSplitterFilter"
367        self.column = int( self.column )
368        self.separator = elem.get( "separator", "," )
369    def filter_options( self, options, other_values ):
370        rval = []
371        for fields in options:
372            for field in fields[self.column].split( self.separator ):
373                rval.append( fields[0:self.column] + [field] + fields[self.column+1:] )
374        return rval
375
376class ColumnStripFilter( ToolOutputActionOptionFilter ):
377    tag = "column_strip"
378    def __init__( self, parent, elem ):
379        super( ColumnStripFilter, self ).__init__( parent, elem )
380        self.column = elem.get( 'column', None )
381        assert self.column is not None, "Required 'column' attribute missing from ColumnStripFilter"
382        self.column = int( self.column )
383        self.strip = elem.get( "strip", None )
384    def filter_options( self, options, other_values ):
385        rval = []
386        for fields in options:
387            rval.append( fields[0:self.column] + [ fields[self.column].strip( self.strip ) ] + fields[self.column+1:] )
388        return rval
389
390class ColumnReplaceFilter( ToolOutputActionOptionFilter ):
391    tag = "column_replace"
392    def __init__( self, parent, elem ):
393        super( ColumnReplaceFilter, self ).__init__( parent, elem )
394        self.old_column = elem.get( 'old_column', None )
395        self.old_value = elem.get( "old_value", None )
396        self.new_value = elem.get( "new_value", None )
397        self.new_column = elem.get( 'new_column', None )
398        assert ( bool( self.old_column ) ^ bool( self.old_value ) and bool( self.new_column ) ^ bool( self.new_value ) ), "Required 'old_column' or 'old_value' and 'new_column' or 'new_value' attribute missing from ColumnReplaceFilter"
399        self.column = elem.get( 'column', None )
400        assert self.column is not None, "Required 'column' attribute missing from ColumnReplaceFilter"
401        self.column = int( self.column )
402        if self.old_column is not None:
403            self.old_column = int( self.old_column )
404        if self.new_column is not None:
405            self.new_column = int( self.new_column )
406    def filter_options( self, options, other_values ):
407        rval = []
408        for fields in options:
409            if self.old_column:
410                old_value = fields[self.old_column]
411            else:
412                old_value = self.old_value
413            if self.new_column:
414                new_value = fields[self.new_column]
415            else:
416                new_value = self.new_value
417            rval.append( fields[0:self.column] + [ fields[self.column].replace( old_value, new_value ) ] + fields[self.column+1:] )
418        return rval
419
420class MetadataValueFilter( ToolOutputActionOptionFilter ):
421    tag = "metadata_value"
422    def __init__( self, parent, elem ):
423        super( MetadataValueFilter, self ).__init__( parent, elem )
424        self.ref = elem.get( 'ref', None )
425        assert self.ref is not None, "Required 'ref' attribute missing from MetadataValueFilter"
426        self.ref = self.ref.split( '.' )
427        self.name = elem.get( 'name', None )
428        assert self.name is not None, "Required 'name' attribute missing from MetadataValueFilter"
429        self.column = elem.get( 'column', None )
430        assert self.column is not None, "Required 'column' attribute missing from MetadataValueFilter"
431        self.column = int( self.column )
432        self.keep = util.string_as_bool( elem.get( "keep", 'True' ) )
433        self.compare = parse_compare_type( elem.get( 'compare', None ) )
434    def filter_options( self, options, other_values ):
435        ref = other_values
436        for ref_name in self.ref:
437            assert ref_name in ref, "Required dependency '%s' not found in incoming values" % ref_name
438            ref = ref.get( ref_name )
439        value = str( getattr( ref.metadata, self.name ) )
440        rval = []
441        for fields in options:
442            if self.keep == ( self.compare( fields[self.column], value ) ): 
443                rval.append( fields )
444        return rval
445
446class BooleanFilter( ToolOutputActionOptionFilter ):
447    tag = "boolean"
448    def __init__( self, parent, elem ):
449        super( BooleanFilter, self ).__init__( parent, elem )
450        self.column = elem.get( 'column', None )
451        assert self.column is not None, "Required 'column' attribute missing from BooleanFilter"
452        self.column = int( self.column )
453        self.keep = util.string_as_bool( elem.get( "keep", 'True' ) )
454        self.cast = parse_cast_attribute( elem.get( "cast", None ) )
455    def filter_options( self, options, other_values ):
456        rval = []
457        for fields in options:
458            try:
459                value = fields[self.column]
460                value = self.cast( value )
461            except:
462                value = False #unable to cast or access value; treat as false
463            if self.keep == bool( value ): 
464                rval.append( fields )
465        return rval
466
467class StringFunctionFilter( ToolOutputActionOptionFilter ):
468    tag = "string_function"
469    def __init__( self, parent, elem ):
470        super( StringFunctionFilter, self ).__init__( parent, elem )
471        self.column = elem.get( 'column', None )
472        assert self.column is not None, "Required 'column' attribute missing from StringFunctionFilter"
473        self.column = int( self.column )
474        self.function = elem.get( "name", None )
475        assert self.function in [ 'lower', 'upper' ], "Required function 'name' missing or invalid from StringFunctionFilter" #add function names as needed
476        self.function = getattr( string, self.function )
477    def filter_options( self, options, other_values ):
478        rval = []
479        for fields in options:
480            rval.append( fields[0:self.column] + [ self.function( fields[self.column] ) ] + fields[self.column+1:] )
481        return rval
482
483#tag to class lookups
484action_types = {}
485for action_type in [ MetadataToolOutputAction, FormatToolOutputAction ]:
486    action_types[ action_type.tag ] = action_type
487
488option_types = {}
489for option_type in [ NullToolOutputActionOption, FromFileToolOutputActionOption, FromParamToolOutputActionOption, FromDataTableOutputActionOption ]:
490    option_types[ option_type.tag ] = option_type
491    
492filter_types = {}
493for filter_type in [ ParamValueToolOutputActionOptionFilter, InsertColumnToolOutputActionOptionFilter, MultipleSplitterFilter, ColumnStripFilter, MetadataValueFilter, BooleanFilter, StringFunctionFilter, ColumnReplaceFilter ]:
494    filter_types[ filter_type.tag ] = filter_type
495
496
497#helper classes
498#determine cast function
499def parse_cast_attribute( cast ):
500    if cast == 'string_as_bool':
501        cast = util.string_as_bool
502    elif cast == 'int':
503        cast = int
504    elif cast == 'str':
505        cast = str
506    else:
507        cast = lambda x: x #return value as-is
508    return cast
509#comparison
510def parse_compare_type( compare ):
511    if compare is None: compare = 'eq'
512    assert compare in compare_types, "Invalid compare type specified: %s" % compare
513    return compare_types[ compare ]
514def compare_eq( value1, value2  ):
515    return value1 == value2
516def compare_neq( value1, value2  ):
517    return value1 != value2
518def compare_gt( value1, value2  ):
519    return value1 > value2
520def compare_gte( value1, value2  ):
521    return value1 >= value2
522def compare_lt( value1, value2  ):
523    return value1 < value2
524def compare_lte( value1, value2  ):
525    return value1 <= value2
526def compare_in( value1, value2 ):
527    return value1 in value2
528def compare_startswith( value1, value2 ):
529    return value1.startswith( value2 )
530def compare_endswith( value1, value2 ):
531    return value1.endswith( value2 )
532def compare_re_search( value1, value2 ):
533    #checks pattern=value2 in value1
534    return bool( re.search( value2, value1 ) )
535    
536compare_types = { 'eq':compare_eq, 'neq':compare_neq, 'gt':compare_gt, 'gte':compare_gte, 'lt':compare_lt, 'lte':compare_lte, 'in':compare_in, 'startswith':compare_startswith, 'endswith':compare_endswith, "re_search":compare_re_search }