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

https://bitbucket.org/cistrome/cistrome-harvard/ · Python · 536 lines · 485 code · 31 blank · 20 comment · 94 complexity · 1a402fa572bb69e82d120fdd6371d1c3 MD5 · raw file

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