/lib/galaxy/tools/__init__.py

https://bitbucket.org/cistrome/cistrome-harvard/ · Python · 3260 lines · 2665 code · 98 blank · 497 comment · 389 complexity · 766fca0cbb3c5eeef2edff23518a8b96 MD5 · raw file

Large files are truncated click here to view the full file

  1. """
  2. Classes encapsulating galaxy tools and tool configuration.
  3. """
  4. import binascii
  5. import glob
  6. import json
  7. import logging
  8. import os
  9. import pipes
  10. import re
  11. import shutil
  12. import sys
  13. import tempfile
  14. import threading
  15. import traceback
  16. import types
  17. import urllib
  18. from math import isinf
  19. from galaxy import eggs
  20. eggs.require( "MarkupSafe" ) # MarkupSafe must load before mako
  21. eggs.require( "Mako" )
  22. eggs.require( "elementtree" )
  23. eggs.require( "Paste" )
  24. eggs.require( "SQLAlchemy >= 0.4" )
  25. from cgi import FieldStorage
  26. from elementtree import ElementTree
  27. from mako.template import Template
  28. from paste import httpexceptions
  29. from sqlalchemy import and_
  30. from galaxy import jobs, model
  31. from galaxy.jobs.error_level import StdioErrorLevel
  32. from galaxy.datatypes.metadata import JobExternalOutputMetadataWrapper
  33. from galaxy.jobs import ParallelismInfo
  34. from galaxy.tools.actions import DefaultToolAction
  35. from galaxy.tools.actions.data_source import DataSourceToolAction
  36. from galaxy.tools.actions.data_manager import DataManagerToolAction
  37. from galaxy.tools.deps import build_dependency_manager
  38. from galaxy.tools.deps.requirements import parse_requirements_from_xml
  39. from galaxy.tools.parameters import check_param, params_from_strings, params_to_strings
  40. from galaxy.tools.parameters.basic import (BaseURLToolParameter,
  41. DataToolParameter, HiddenToolParameter, LibraryDatasetToolParameter,
  42. SelectToolParameter, ToolParameter, UnvalidatedValue,
  43. IntegerToolParameter, FloatToolParameter)
  44. from galaxy.tools.parameters.grouping import Conditional, ConditionalWhen, Repeat, UploadDataset
  45. from galaxy.tools.parameters.input_translation import ToolInputTranslator
  46. from galaxy.tools.parameters.output import ToolOutputActionGroup
  47. from galaxy.tools.parameters.validation import LateValidationError
  48. from galaxy.tools.filters import FilterFactory
  49. from galaxy.tools.test import parse_tests_elem
  50. from galaxy.util import listify, parse_xml, rst_to_html, string_as_bool, string_to_object, xml_text, xml_to_string
  51. from galaxy.util.bunch import Bunch
  52. from galaxy.util.expressions import ExpressionContext
  53. from galaxy.util.hash_util import hmac_new
  54. from galaxy.util.none_like import NoneDataset
  55. from galaxy.util.odict import odict
  56. from galaxy.util.template import fill_template
  57. from galaxy.web import url_for
  58. from galaxy.web.form_builder import SelectField
  59. from galaxy.model.item_attrs import Dictifiable
  60. from galaxy.model import Workflow
  61. from tool_shed.util import shed_util_common as suc
  62. from .loader import load_tool, template_macro_params
  63. from .wrappers import (
  64. ToolParameterValueWrapper,
  65. RawObjectWrapper,
  66. LibraryDatasetValueWrapper,
  67. InputValueWrapper,
  68. SelectToolParameterWrapper,
  69. DatasetFilenameWrapper,
  70. DatasetListWrapper,
  71. )
  72. log = logging.getLogger( __name__ )
  73. WORKFLOW_PARAMETER_REGULAR_EXPRESSION = re.compile( '''\$\{.+?\}''' )
  74. class ToolNotFoundException( Exception ):
  75. pass
  76. def to_dict_helper( obj, kwargs ):
  77. """ Helper function that provides the appropriate kwargs to to_dict an object. """
  78. # Label.to_dict cannot have kwargs.
  79. if isinstance( obj, ToolSectionLabel ):
  80. kwargs = {}
  81. return obj.to_dict( **kwargs )
  82. class ToolBox( object, Dictifiable ):
  83. """Container for a collection of tools"""
  84. def __init__( self, config_filenames, tool_root_dir, app ):
  85. """
  86. Create a toolbox from the config files named by `config_filenames`, using
  87. `tool_root_dir` as the base directory for finding individual tool config files.
  88. """
  89. # The shed_tool_confs list contains dictionaries storing information about the tools defined in each
  90. # shed-related shed_tool_conf.xml file.
  91. self.shed_tool_confs = []
  92. self.tools_by_id = {}
  93. self.workflows_by_id = {}
  94. # In-memory dictionary that defines the layout of the tool panel.
  95. self.tool_panel = odict()
  96. self.index = 0
  97. self.data_manager_tools = odict()
  98. # File that contains the XML section and tool tags from all tool panel config files integrated into a
  99. # single file that defines the tool panel layout. This file can be changed by the Galaxy administrator
  100. # (in a way similar to the single tool_conf.xml file in the past) to alter the layout of the tool panel.
  101. self.integrated_tool_panel_config = os.path.join( app.config.root, 'integrated_tool_panel.xml' )
  102. # In-memory dictionary that defines the layout of the tool_panel.xml file on disk.
  103. self.integrated_tool_panel = odict()
  104. self.integrated_tool_panel_config_has_contents = os.path.exists( self.integrated_tool_panel_config ) and os.stat( self.integrated_tool_panel_config ).st_size > 0
  105. if self.integrated_tool_panel_config_has_contents:
  106. self.load_integrated_tool_panel_keys()
  107. # The following refers to the tool_path config setting for backward compatibility. The shed-related
  108. # (e.g., shed_tool_conf.xml) files include the tool_path attribute within the <toolbox> tag.
  109. self.tool_root_dir = tool_root_dir
  110. self.app = app
  111. self.filter_factory = FilterFactory( self )
  112. self.init_dependency_manager()
  113. config_filenames = listify( config_filenames )
  114. for config_filename in config_filenames:
  115. if os.path.isdir( config_filename ):
  116. directory_contents = sorted( os.listdir( config_filename ) )
  117. directory_config_files = [ config_file for config_file in directory_contents if config_file.endswith( ".xml" ) ]
  118. config_filenames.remove( config_filename )
  119. config_filenames.extend( directory_config_files )
  120. for config_filename in config_filenames:
  121. try:
  122. self.init_tools( config_filename )
  123. except:
  124. log.exception( "Error loading tools defined in config %s", config_filename )
  125. if self.integrated_tool_panel_config_has_contents:
  126. # Load self.tool_panel based on the order in self.integrated_tool_panel.
  127. self.load_tool_panel()
  128. if app.config.update_integrated_tool_panel:
  129. # Write the current in-memory integrated_tool_panel to the integrated_tool_panel.xml file.
  130. # This will cover cases where the Galaxy administrator manually edited one or more of the tool panel
  131. # config files, adding or removing locally developed tools or workflows. The value of integrated_tool_panel
  132. # will be False when things like functional tests are the caller.
  133. self.fix_integrated_tool_panel_dict()
  134. self.write_integrated_tool_panel_config_file()
  135. def fix_integrated_tool_panel_dict( self ):
  136. # HACK: instead of fixing after the fact, I suggest some combination of:
  137. # 1) adjusting init_tools() and called methods to get this right
  138. # 2) redesigning the code and/or data structure used to read/write integrated_tool_panel.xml
  139. for key, value in self.integrated_tool_panel.iteritems():
  140. if isinstance( value, ToolSection ):
  141. for section_key, section_value in value.elems.iteritems():
  142. if section_value is None:
  143. if isinstance( section_value, Tool ):
  144. tool_id = section_key[5:]
  145. value.elems[section_key] = self.tools_by_id.get( tool_id )
  146. elif isinstance( section_value, Workflow ):
  147. workflow_id = section_key[9:]
  148. value.elems[section_key] = self.workflows_by_id.get( workflow_id )
  149. def init_tools( self, config_filename ):
  150. """
  151. Read the configuration file and load each tool. The following tags are currently supported:
  152. .. raw:: xml
  153. <toolbox>
  154. <tool file="data_source/upload.xml"/> # tools outside sections
  155. <label text="Basic Tools" id="basic_tools" /> # labels outside sections
  156. <workflow id="529fd61ab1c6cc36" /> # workflows outside sections
  157. <section name="Get Data" id="getext"> # sections
  158. <tool file="data_source/biomart.xml" /> # tools inside sections
  159. <label text="In Section" id="in_section" /> # labels inside sections
  160. <workflow id="adb5f5c93f827949" /> # workflows inside sections
  161. </section>
  162. </toolbox>
  163. """
  164. if self.app.config.get_bool( 'enable_tool_tags', False ):
  165. log.info("removing all tool tag associations (" + str( self.sa_session.query( self.app.model.ToolTagAssociation ).count() ) + ")" )
  166. self.sa_session.query( self.app.model.ToolTagAssociation ).delete()
  167. self.sa_session.flush()
  168. log.info( "Parsing the tool configuration %s" % config_filename )
  169. tree = parse_xml( config_filename )
  170. root = tree.getroot()
  171. tool_path = root.get( 'tool_path' )
  172. if tool_path:
  173. # We're parsing a shed_tool_conf file since we have a tool_path attribute.
  174. parsing_shed_tool_conf = True
  175. # Keep an in-memory list of xml elements to enable persistence of the changing tool config.
  176. config_elems = []
  177. else:
  178. parsing_shed_tool_conf = False
  179. # Default to backward compatible config setting.
  180. tool_path = self.tool_root_dir
  181. # Only load the panel_dict under certain conditions.
  182. load_panel_dict = not self.integrated_tool_panel_config_has_contents
  183. for _, elem in enumerate( root ):
  184. index = self.index
  185. self.index += 1
  186. if parsing_shed_tool_conf:
  187. config_elems.append( elem )
  188. if elem.tag == 'tool':
  189. self.load_tool_tag_set( elem, self.tool_panel, self.integrated_tool_panel, tool_path, load_panel_dict, guid=elem.get( 'guid' ), index=index )
  190. elif elem.tag == 'workflow':
  191. self.load_workflow_tag_set( elem, self.tool_panel, self.integrated_tool_panel, load_panel_dict, index=index )
  192. elif elem.tag == 'section':
  193. self.load_section_tag_set( elem, tool_path, load_panel_dict, index=index )
  194. elif elem.tag == 'label':
  195. self.load_label_tag_set( elem, self.tool_panel, self.integrated_tool_panel, load_panel_dict, index=index )
  196. if parsing_shed_tool_conf:
  197. shed_tool_conf_dict = dict( config_filename=config_filename,
  198. tool_path=tool_path,
  199. config_elems=config_elems )
  200. self.shed_tool_confs.append( shed_tool_conf_dict )
  201. def get_shed_config_dict_by_filename( self, filename, default=None ):
  202. for shed_config_dict in self.shed_tool_confs:
  203. if shed_config_dict[ 'config_filename' ] == filename:
  204. return shed_config_dict
  205. return default
  206. def __add_tool_to_tool_panel( self, tool, panel_component, section=False ):
  207. # See if a version of this tool is already loaded into the tool panel. The value of panel_component
  208. # will be a ToolSection (if the value of section=True) or self.tool_panel (if section=False).
  209. tool_id = str( tool.id )
  210. tool = self.tools_by_id[ tool_id ]
  211. if section:
  212. panel_dict = panel_component.elems
  213. else:
  214. panel_dict = panel_component
  215. already_loaded = False
  216. loaded_version_key = None
  217. lineage_id = None
  218. for lineage_id in tool.lineage_ids:
  219. if lineage_id in self.tools_by_id:
  220. loaded_version_key = 'tool_%s' % lineage_id
  221. if loaded_version_key in panel_dict:
  222. already_loaded = True
  223. break
  224. if already_loaded:
  225. if tool.lineage_ids.index( tool_id ) > tool.lineage_ids.index( lineage_id ):
  226. key = 'tool_%s' % tool.id
  227. index = panel_dict.keys().index( loaded_version_key )
  228. del panel_dict[ loaded_version_key ]
  229. panel_dict.insert( index, key, tool )
  230. log.debug( "Loaded tool id: %s, version: %s into tool panel." % ( tool.id, tool.version ) )
  231. else:
  232. inserted = False
  233. key = 'tool_%s' % tool.id
  234. # The value of panel_component is the in-memory tool panel dictionary.
  235. for index, integrated_panel_key in enumerate( self.integrated_tool_panel.keys() ):
  236. if key == integrated_panel_key:
  237. panel_dict.insert( index, key, tool )
  238. if not inserted:
  239. inserted = True
  240. if not inserted:
  241. # Check the tool's installed versions.
  242. for lineage_id in tool.lineage_ids:
  243. lineage_id_key = 'tool_%s' % lineage_id
  244. for index, integrated_panel_key in enumerate( self.integrated_tool_panel.keys() ):
  245. if lineage_id_key == integrated_panel_key:
  246. panel_dict.insert( index, key, tool )
  247. if not inserted:
  248. inserted = True
  249. if not inserted:
  250. if tool.guid is None or \
  251. tool.tool_shed is None or \
  252. tool.repository_name is None or \
  253. tool.repository_owner is None or \
  254. tool.installed_changeset_revision is None:
  255. # We have a tool that was not installed from the Tool Shed, but is also not yet defined in
  256. # integrated_tool_panel.xml, so append it to the tool panel.
  257. panel_dict[ key ] = tool
  258. log.debug( "Loaded tool id: %s, version: %s into tool panel.." % ( tool.id, tool.version ) )
  259. else:
  260. # We are in the process of installing the tool.
  261. tool_version = self.__get_tool_version( tool_id )
  262. tool_lineage_ids = tool_version.get_version_ids( self.app, reverse=True )
  263. for lineage_id in tool_lineage_ids:
  264. if lineage_id in self.tools_by_id:
  265. loaded_version_key = 'tool_%s' % lineage_id
  266. if loaded_version_key in panel_dict:
  267. if not already_loaded:
  268. already_loaded = True
  269. if not already_loaded:
  270. # If the tool is not defined in integrated_tool_panel.xml, append it to the tool panel.
  271. panel_dict[ key ] = tool
  272. log.debug( "Loaded tool id: %s, version: %s into tool panel...." % ( tool.id, tool.version ) )
  273. def load_tool_panel( self ):
  274. for key, val in self.integrated_tool_panel.items():
  275. if isinstance( val, Tool ):
  276. tool_id = key.replace( 'tool_', '', 1 )
  277. if tool_id in self.tools_by_id:
  278. self.__add_tool_to_tool_panel( val, self.tool_panel, section=False )
  279. elif isinstance( val, Workflow ):
  280. workflow_id = key.replace( 'workflow_', '', 1 )
  281. if workflow_id in self.workflows_by_id:
  282. workflow = self.workflows_by_id[ workflow_id ]
  283. self.tool_panel[ key ] = workflow
  284. log.debug( "Loaded workflow: %s %s" % ( workflow_id, workflow.name ) )
  285. elif isinstance( val, ToolSectionLabel ):
  286. self.tool_panel[ key ] = val
  287. elif isinstance( val, ToolSection ):
  288. elem = ElementTree.Element( 'section' )
  289. elem.attrib[ 'id' ] = val.id or ''
  290. elem.attrib[ 'name' ] = val.name or ''
  291. elem.attrib[ 'version' ] = val.version or ''
  292. section = ToolSection( elem )
  293. log.debug( "Loading section: %s" % elem.get( 'name' ) )
  294. for section_key, section_val in val.elems.items():
  295. if isinstance( section_val, Tool ):
  296. tool_id = section_key.replace( 'tool_', '', 1 )
  297. if tool_id in self.tools_by_id:
  298. self.__add_tool_to_tool_panel( section_val, section, section=True )
  299. elif isinstance( section_val, Workflow ):
  300. workflow_id = section_key.replace( 'workflow_', '', 1 )
  301. if workflow_id in self.workflows_by_id:
  302. workflow = self.workflows_by_id[ workflow_id ]
  303. section.elems[ section_key ] = workflow
  304. log.debug( "Loaded workflow: %s %s" % ( workflow_id, workflow.name ) )
  305. elif isinstance( section_val, ToolSectionLabel ):
  306. if section_val:
  307. section.elems[ section_key ] = section_val
  308. log.debug( "Loaded label: %s" % ( section_val.text ) )
  309. self.tool_panel[ key ] = section
  310. def load_integrated_tool_panel_keys( self ):
  311. """
  312. Load the integrated tool panel keys, setting values for tools and workflows to None. The values will
  313. be reset when the various tool panel config files are parsed, at which time the tools and workflows are
  314. loaded.
  315. """
  316. tree = parse_xml( self.integrated_tool_panel_config )
  317. root = tree.getroot()
  318. for elem in root:
  319. if elem.tag == 'tool':
  320. key = 'tool_%s' % elem.get( 'id' )
  321. self.integrated_tool_panel[ key ] = None
  322. elif elem.tag == 'workflow':
  323. key = 'workflow_%s' % elem.get( 'id' )
  324. self.integrated_tool_panel[ key ] = None
  325. elif elem.tag == 'section':
  326. section = ToolSection( elem )
  327. for section_elem in elem:
  328. if section_elem.tag == 'tool':
  329. key = 'tool_%s' % section_elem.get( 'id' )
  330. section.elems[ key ] = None
  331. elif section_elem.tag == 'workflow':
  332. key = 'workflow_%s' % section_elem.get( 'id' )
  333. section.elems[ key ] = None
  334. elif section_elem.tag == 'label':
  335. key = 'label_%s' % section_elem.get( 'id' )
  336. section.elems[ key ] = None
  337. key = elem.get( 'id' )
  338. self.integrated_tool_panel[ key ] = section
  339. elif elem.tag == 'label':
  340. key = 'label_%s' % elem.get( 'id' )
  341. self.integrated_tool_panel[ key ] = None
  342. def write_integrated_tool_panel_config_file( self ):
  343. """
  344. Write the current in-memory version of the integrated_tool_panel.xml file to disk. Since Galaxy administrators
  345. use this file to manage the tool panel, we'll not use xml_to_string() since it doesn't write XML quite right.
  346. """
  347. fd, filename = tempfile.mkstemp()
  348. os.write( fd, '<?xml version="1.0"?>\n' )
  349. os.write( fd, '<toolbox>\n' )
  350. for key, item in self.integrated_tool_panel.items():
  351. if item:
  352. if isinstance( item, Tool ):
  353. os.write( fd, ' <tool id="%s" />\n' % item.id )
  354. elif isinstance( item, Workflow ):
  355. os.write( fd, ' <workflow id="%s" />\n' % item.id )
  356. elif isinstance( item, ToolSectionLabel ):
  357. label_id = item.id or ''
  358. label_text = item.text or ''
  359. label_version = item.version or ''
  360. os.write( fd, ' <label id="%s" text="%s" version="%s" />\n' % ( label_id, label_text, label_version ) )
  361. elif isinstance( item, ToolSection ):
  362. section_id = item.id or ''
  363. section_name = item.name or ''
  364. section_version = item.version or ''
  365. os.write( fd, ' <section id="%s" name="%s" version="%s">\n' % ( section_id, section_name, section_version ) )
  366. for section_key, section_item in item.elems.items():
  367. if isinstance( section_item, Tool ):
  368. if section_item:
  369. os.write( fd, ' <tool id="%s" />\n' % section_item.id )
  370. elif isinstance( section_item, Workflow ):
  371. if section_item:
  372. os.write( fd, ' <workflow id="%s" />\n' % section_item.id )
  373. elif isinstance( section_item, ToolSectionLabel ):
  374. if section_item:
  375. label_id = section_item.id or ''
  376. label_text = section_item.text or ''
  377. label_version = section_item.version or ''
  378. os.write( fd, ' <label id="%s" text="%s" version="%s" />\n' % ( label_id, label_text, label_version ) )
  379. os.write( fd, ' </section>\n' )
  380. os.write( fd, '</toolbox>\n' )
  381. os.close( fd )
  382. shutil.move( filename, os.path.abspath( self.integrated_tool_panel_config ) )
  383. os.chmod( self.integrated_tool_panel_config, 0644 )
  384. def get_tool( self, tool_id, tool_version=None, get_all_versions=False ):
  385. """Attempt to locate a tool in the tool box."""
  386. if tool_id in self.tools_by_id and not get_all_versions:
  387. #tool_id exactly matches an available tool by id (which is 'old' tool_id or guid)
  388. return self.tools_by_id[ tool_id ]
  389. #exact tool id match not found, or all versions requested, search for other options, e.g. migrated tools or different versions
  390. rval = []
  391. tv = self.__get_tool_version( tool_id )
  392. if tv:
  393. tool_version_ids = tv.get_version_ids( self.app )
  394. for tool_version_id in tool_version_ids:
  395. if tool_version_id in self.tools_by_id:
  396. rval.append( self.tools_by_id[ tool_version_id ] )
  397. if not rval:
  398. #still no tool, do a deeper search and try to match by old ids
  399. for tool in self.tools_by_id.itervalues():
  400. if tool.old_id == tool_id:
  401. rval.append( tool )
  402. if rval:
  403. if get_all_versions:
  404. return rval
  405. else:
  406. if tool_version:
  407. #return first tool with matching version
  408. for tool in rval:
  409. if tool.version == tool_version:
  410. return tool
  411. #No tool matches by version, simply return the first available tool found
  412. return rval[0]
  413. #We now likely have a Toolshed guid passed in, but no supporting database entries
  414. #If the tool exists by exact id and is loaded then provide exact match within a list
  415. if tool_id in self.tools_by_id:
  416. return[ self.tools_by_id[ tool_id ] ]
  417. return None
  418. def get_loaded_tools_by_lineage( self, tool_id ):
  419. """Get all loaded tools associated by lineage to the tool whose id is tool_id."""
  420. tv = self.__get_tool_version( tool_id )
  421. if tv:
  422. tool_version_ids = tv.get_version_ids( self.app )
  423. available_tool_versions = []
  424. for tool_version_id in tool_version_ids:
  425. if tool_version_id in self.tools_by_id:
  426. available_tool_versions.append( self.tools_by_id[ tool_version_id ] )
  427. return available_tool_versions
  428. else:
  429. if tool_id in self.tools_by_id:
  430. tool = self.tools_by_id[ tool_id ]
  431. return [ tool ]
  432. return []
  433. def __get_tool_version( self, tool_id ):
  434. """Return a ToolVersion if one exists for the tool_id"""
  435. return self.app.install_model.context.query( self.app.install_model.ToolVersion ) \
  436. .filter( self.app.install_model.ToolVersion.table.c.tool_id == tool_id ) \
  437. .first()
  438. def __get_tool_shed_repository( self, tool_shed, name, owner, installed_changeset_revision ):
  439. return self.app.install_model.context.query( self.app.install_model.ToolShedRepository ) \
  440. .filter( and_( self.app.install_model.ToolShedRepository.table.c.tool_shed == tool_shed,
  441. self.app.install_model.ToolShedRepository.table.c.name == name,
  442. self.app.install_model.ToolShedRepository.table.c.owner == owner,
  443. self.app.install_model.ToolShedRepository.table.c.installed_changeset_revision == installed_changeset_revision ) ) \
  444. .first()
  445. def get_tool_components( self, tool_id, tool_version=None, get_loaded_tools_by_lineage=False, set_selected=False ):
  446. """
  447. Retrieve all loaded versions of a tool from the toolbox and return a select list enabling
  448. selection of a different version, the list of the tool's loaded versions, and the specified tool.
  449. """
  450. toolbox = self
  451. tool_version_select_field = None
  452. tools = []
  453. tool = None
  454. # Backwards compatibility for datasource tools that have default tool_id configured, but which
  455. # are now using only GALAXY_URL.
  456. tool_ids = listify( tool_id )
  457. for tool_id in tool_ids:
  458. if get_loaded_tools_by_lineage:
  459. tools = toolbox.get_loaded_tools_by_lineage( tool_id )
  460. else:
  461. tools = toolbox.get_tool( tool_id, tool_version=tool_version, get_all_versions=True )
  462. if tools:
  463. tool = toolbox.get_tool( tool_id, tool_version=tool_version, get_all_versions=False )
  464. if len( tools ) > 1:
  465. tool_version_select_field = self.build_tool_version_select_field( tools, tool.id, set_selected )
  466. break
  467. return tool_version_select_field, tools, tool
  468. def build_tool_version_select_field( self, tools, tool_id, set_selected ):
  469. """Build a SelectField whose options are the ids for the received list of tools."""
  470. options = []
  471. refresh_on_change_values = []
  472. for tool in tools:
  473. options.insert( 0, ( tool.version, tool.id ) )
  474. refresh_on_change_values.append( tool.id )
  475. select_field = SelectField( name='tool_id', refresh_on_change=True, refresh_on_change_values=refresh_on_change_values )
  476. for option_tup in options:
  477. selected = set_selected and option_tup[ 1 ] == tool_id
  478. if selected:
  479. select_field.add_option( 'version %s' % option_tup[ 0 ], option_tup[ 1 ], selected=True )
  480. else:
  481. select_field.add_option( 'version %s' % option_tup[ 0 ], option_tup[ 1 ] )
  482. return select_field
  483. def load_tool_tag_set( self, elem, panel_dict, integrated_panel_dict, tool_path, load_panel_dict, guid=None, index=None ):
  484. try:
  485. path = elem.get( "file" )
  486. repository_id = None
  487. if guid is None:
  488. tool_shed_repository = None
  489. can_load_into_panel_dict = True
  490. else:
  491. # The tool is contained in an installed tool shed repository, so load
  492. # the tool only if the repository has not been marked deleted.
  493. tool_shed = elem.find( "tool_shed" ).text
  494. repository_name = elem.find( "repository_name" ).text
  495. repository_owner = elem.find( "repository_owner" ).text
  496. installed_changeset_revision_elem = elem.find( "installed_changeset_revision" )
  497. if installed_changeset_revision_elem is None:
  498. # Backward compatibility issue - the tag used to be named 'changeset_revision'.
  499. installed_changeset_revision_elem = elem.find( "changeset_revision" )
  500. installed_changeset_revision = installed_changeset_revision_elem.text
  501. tool_shed_repository = self.__get_tool_shed_repository( tool_shed, repository_name, repository_owner, installed_changeset_revision )
  502. if tool_shed_repository:
  503. # Only load tools if the repository is not deactivated or uninstalled.
  504. can_load_into_panel_dict = not tool_shed_repository.deleted
  505. repository_id = self.app.security.encode_id( tool_shed_repository.id )
  506. else:
  507. # If there is not yet a tool_shed_repository record, we're in the process of installing
  508. # a new repository, so any included tools can be loaded into the tool panel.
  509. can_load_into_panel_dict = True
  510. tool = self.load_tool( os.path.join( tool_path, path ), guid=guid, repository_id=repository_id )
  511. key = 'tool_%s' % str( tool.id )
  512. if can_load_into_panel_dict:
  513. if guid is not None:
  514. tool.tool_shed = tool_shed
  515. tool.repository_name = repository_name
  516. tool.repository_owner = repository_owner
  517. tool.installed_changeset_revision = installed_changeset_revision
  518. tool.guid = guid
  519. tool.version = elem.find( "version" ).text
  520. # Make sure the tool has a tool_version.
  521. if not self.__get_tool_version( tool.id ):
  522. tool_version = self.app.install_model.ToolVersion( tool_id=tool.id, tool_shed_repository=tool_shed_repository )
  523. self.app.install_model.context.add( tool_version )
  524. self.app.install_model.context.flush()
  525. # Load the tool's lineage ids.
  526. tool.lineage_ids = tool.tool_version.get_version_ids( self.app )
  527. if self.app.config.get_bool( 'enable_tool_tags', False ):
  528. tag_names = elem.get( "tags", "" ).split( "," )
  529. for tag_name in tag_names:
  530. if tag_name == '':
  531. continue
  532. tag = self.sa_session.query( self.app.model.Tag ).filter_by( name=tag_name ).first()
  533. if not tag:
  534. tag = self.app.model.Tag( name=tag_name )
  535. self.sa_session.add( tag )
  536. self.sa_session.flush()
  537. tta = self.app.model.ToolTagAssociation( tool_id=tool.id, tag_id=tag.id )
  538. self.sa_session.add( tta )
  539. self.sa_session.flush()
  540. else:
  541. for tagged_tool in tag.tagged_tools:
  542. if tagged_tool.tool_id == tool.id:
  543. break
  544. else:
  545. tta = self.app.model.ToolTagAssociation( tool_id=tool.id, tag_id=tag.id )
  546. self.sa_session.add( tta )
  547. self.sa_session.flush()
  548. # Allow for the same tool to be loaded into multiple places in the tool panel. We have to handle
  549. # the case where the tool is contained in a repository installed from the tool shed, and the Galaxy
  550. # administrator has retrieved updates to the installed repository. In this case, the tool may have
  551. # been updated, but the version was not changed, so the tool should always be reloaded here. We used
  552. # to only load the tool if it's it was not found in self.tools_by_id, but performing that check did
  553. # not enable this scenario.
  554. self.tools_by_id[ tool.id ] = tool
  555. if load_panel_dict:
  556. self.__add_tool_to_tool_panel( tool, panel_dict, section=isinstance( panel_dict, ToolSection ) )
  557. # Always load the tool into the integrated_panel_dict, or it will not be included in the integrated_tool_panel.xml file.
  558. if key in integrated_panel_dict or index is None:
  559. integrated_panel_dict[ key ] = tool
  560. else:
  561. integrated_panel_dict.insert( index, key, tool )
  562. except:
  563. log.exception( "Error reading tool from path: %s" % path )
  564. def load_workflow_tag_set( self, elem, panel_dict, integrated_panel_dict, load_panel_dict, index=None ):
  565. try:
  566. # TODO: should id be encoded?
  567. workflow_id = elem.get( 'id' )
  568. workflow = self.load_workflow( workflow_id )
  569. self.workflows_by_id[ workflow_id ] = workflow
  570. key = 'workflow_' + workflow_id
  571. if load_panel_dict:
  572. panel_dict[ key ] = workflow
  573. # Always load workflows into the integrated_panel_dict.
  574. if key in integrated_panel_dict or index is None:
  575. integrated_panel_dict[ key ] = workflow
  576. else:
  577. integrated_panel_dict.insert( index, key, workflow )
  578. except:
  579. log.exception( "Error loading workflow: %s" % workflow_id )
  580. def load_label_tag_set( self, elem, panel_dict, integrated_panel_dict, load_panel_dict, index=None ):
  581. label = ToolSectionLabel( elem )
  582. key = 'label_' + label.id
  583. if load_panel_dict:
  584. panel_dict[ key ] = label
  585. if key in integrated_panel_dict or index is None:
  586. integrated_panel_dict[ key ] = label
  587. else:
  588. integrated_panel_dict.insert( index, key, label )
  589. def load_section_tag_set( self, elem, tool_path, load_panel_dict, index=None ):
  590. key = elem.get( "id" )
  591. if key in self.tool_panel:
  592. section = self.tool_panel[ key ]
  593. elems = section.elems
  594. else:
  595. section = ToolSection( elem )
  596. elems = section.elems
  597. if key in self.integrated_tool_panel:
  598. integrated_section = self.integrated_tool_panel[ key ]
  599. integrated_elems = integrated_section.elems
  600. else:
  601. integrated_section = ToolSection( elem )
  602. integrated_elems = integrated_section.elems
  603. for sub_index, sub_elem in enumerate( elem ):
  604. if sub_elem.tag == 'tool':
  605. self.load_tool_tag_set( sub_elem, elems, integrated_elems, tool_path, load_panel_dict, guid=sub_elem.get( 'guid' ), index=sub_index )
  606. elif sub_elem.tag == 'workflow':
  607. self.load_workflow_tag_set( sub_elem, elems, integrated_elems, load_panel_dict, index=sub_index )
  608. elif sub_elem.tag == 'label':
  609. self.load_label_tag_set( sub_elem, elems, integrated_elems, load_panel_dict, index=sub_index )
  610. if load_panel_dict:
  611. self.tool_panel[ key ] = section
  612. # Always load sections into the integrated_tool_panel.
  613. if key in self.integrated_tool_panel or index is None:
  614. self.integrated_tool_panel[ key ] = integrated_section
  615. else:
  616. self.integrated_tool_panel.insert( index, key, integrated_section )
  617. def load_tool( self, config_file, guid=None, repository_id=None, **kwds ):
  618. """Load a single tool from the file named by `config_file` and return an instance of `Tool`."""
  619. # Parse XML configuration file and get the root element
  620. tree = load_tool( config_file )
  621. root = tree.getroot()
  622. # Allow specifying a different tool subclass to instantiate
  623. if root.find( "type" ) is not None:
  624. type_elem = root.find( "type" )
  625. module = type_elem.get( 'module', 'galaxy.tools' )
  626. cls = type_elem.get( 'class' )
  627. mod = __import__( module, globals(), locals(), [cls] )
  628. ToolClass = getattr( mod, cls )
  629. elif root.get( 'tool_type', None ) is not None:
  630. ToolClass = tool_types.get( root.get( 'tool_type' ) )
  631. else:
  632. ToolClass = Tool
  633. return ToolClass( config_file, root, self.app, guid=guid, repository_id=repository_id, **kwds )
  634. def reload_tool_by_id( self, tool_id ):
  635. """
  636. Attempt to reload the tool identified by 'tool_id', if successful
  637. replace the old tool.
  638. """
  639. if tool_id not in self.tools_by_id:
  640. message = "No tool with id %s" % tool_id
  641. status = 'error'
  642. else:
  643. old_tool = self.tools_by_id[ tool_id ]
  644. new_tool = self.load_tool( old_tool.config_file )
  645. # The tool may have been installed from a tool shed, so set the tool shed attributes.
  646. # Since the tool version may have changed, we don't override it here.
  647. new_tool.id = old_tool.id
  648. new_tool.guid = old_tool.guid
  649. new_tool.tool_shed = old_tool.tool_shed
  650. new_tool.repository_name = old_tool.repository_name
  651. new_tool.repository_owner = old_tool.repository_owner
  652. new_tool.installed_changeset_revision = old_tool.installed_changeset_revision
  653. new_tool.old_id = old_tool.old_id
  654. # Replace old_tool with new_tool in self.tool_panel
  655. tool_key = 'tool_' + tool_id
  656. for key, val in self.tool_panel.items():
  657. if key == tool_key:
  658. self.tool_panel[ key ] = new_tool
  659. break
  660. elif key.startswith( 'section' ):
  661. if tool_key in val.elems:
  662. self.tool_panel[ key ].elems[ tool_key ] = new_tool
  663. break
  664. self.tools_by_id[ tool_id ] = new_tool
  665. message = "Reloaded the tool:<br/>"
  666. message += "<b>name:</b> %s<br/>" % old_tool.name
  667. message += "<b>id:</b> %s<br/>" % old_tool.id
  668. message += "<b>version:</b> %s" % old_tool.version
  669. status = 'done'
  670. return message, status
  671. def remove_tool_by_id( self, tool_id ):
  672. """
  673. Attempt to remove the tool identified by 'tool_id'.
  674. """
  675. if tool_id not in self.tools_by_id:
  676. message = "No tool with id %s" % tool_id
  677. status = 'error'
  678. else:
  679. tool = self.tools_by_id[ tool_id ]
  680. del self.tools_by_id[ tool_id ]
  681. tool_key = 'tool_' + tool_id
  682. for key, val in self.tool_panel.items():
  683. if key == tool_key:
  684. del self.tool_panel[ key ]
  685. break
  686. elif key.startswith( 'section' ):
  687. if tool_key in val.elems:
  688. del self.tool_panel[ key ].elems[ tool_key ]
  689. break
  690. if tool_id in self.data_manager_tools:
  691. del self.data_manager_tools[ tool_id ]
  692. #TODO: do we need to manually remove from the integrated panel here?
  693. message = "Removed the tool:<br/>"
  694. message += "<b>name:</b> %s<br/>" % tool.name
  695. message += "<b>id:</b> %s<br/>" % tool.id
  696. message += "<b>version:</b> %s" % tool.version
  697. status = 'done'
  698. return message, status
  699. def load_workflow( self, workflow_id ):
  700. """
  701. Return an instance of 'Workflow' identified by `id`,
  702. which is encoded in the tool panel.
  703. """
  704. id = self.app.security.decode_id( workflow_id )
  705. stored = self.app.model.context.query( self.app.model.StoredWorkflow ).get( id )
  706. return stored.latest_workflow
  707. def init_dependency_manager( self ):
  708. self.dependency_manager = build_dependency_manager( self.app.config )
  709. @property
  710. def sa_session( self ):
  711. """
  712. Returns a SQLAlchemy session
  713. """
  714. return self.app.model.context
  715. def to_dict( self, trans, in_panel=True, **kwds ):
  716. """
  717. to_dict toolbox.
  718. """
  719. context = Bunch( toolbox=self, trans=trans, **kwds )
  720. if in_panel:
  721. panel_elts = [ val for val in self.tool_panel.itervalues() ]
  722. filters = self.filter_factory.build_filters( trans, **kwds )
  723. filtered_panel_elts = []
  724. for index, elt in enumerate( panel_elts ):
  725. elt = _filter_for_panel( elt, filters, context )
  726. if elt:
  727. filtered_panel_elts.append( elt )
  728. panel_elts = filtered_panel_elts
  729. # Produce panel.
  730. rval = []
  731. kwargs = dict(
  732. trans=trans,
  733. link_details=True
  734. )
  735. for elt in panel_elts:
  736. rval.append( to_dict_helper( elt, kwargs ) )
  737. else:
  738. tools = []
  739. for id, tool in self.tools_by_id.items():
  740. tools.append( tool.to_dict( trans, link_details=True ) )
  741. rval = tools
  742. return rval
  743. def _filter_for_panel( item, filters, context ):
  744. """
  745. Filters tool panel elements so that only those that are compatible
  746. with provided filters are kept.
  747. """
  748. def _apply_filter( filter_item, filter_list ):
  749. for filter_method in filter_list:
  750. if not filter_method( context, filter_item ):
  751. return False
  752. return True
  753. if isinstance( item, Tool ):
  754. if _apply_filter( item, filters[ 'tool' ] ):
  755. return item
  756. elif isinstance( item, ToolSectionLabel ):
  757. if _apply_filter( item, filters[ 'label' ] ):
  758. return item
  759. elif isinstance( item, ToolSection ):
  760. # Filter section item-by-item. Only show a label if there are
  761. # non-filtered tools below it.
  762. if _apply_filter( item, filters[ 'section' ] ):
  763. cur_label_key = None
  764. tools_under_label = False
  765. filtered_elems = item.elems.copy()
  766. for key, section_item in item.elems.items():
  767. if isinstance( section_item, Tool ):
  768. # Filter tool.
  769. if _apply_filter( section_item, filters[ 'tool' ] ):
  770. tools_under_label = True
  771. else:
  772. del filtered_elems[ key ]
  773. elif isinstance( section_item, ToolSectionLabel ):
  774. # If there is a label and it does not have tools,
  775. # remove it.
  776. if ( cur_label_key and not tools_under_label ) or not _apply_filter( section_item, filters[ 'label' ] ):
  777. del filtered_elems[ cur_label_key ]
  778. # Reset attributes for new label.
  779. cur_label_key = key
  780. tools_under_label = False
  781. # Handle last label.
  782. if cur_label_key and not tools_under_label:
  783. del filtered_elems[ cur_label_key ]
  784. # Only return section if there are elements.
  785. if len( filtered_elems ) != 0:
  786. copy = item.copy()
  787. copy.elems = filtered_elems
  788. return copy
  789. return None
  790. class ToolSection( object, Dictifiable ):
  791. """
  792. A group of tools with similar type/purpose that will be displayed as a
  793. group in the user interface.
  794. """
  795. dict_collection_visible_keys = ( 'id', 'name', 'version' )
  796. def __init__( self, elem=None ):
  797. f = lambda elem, val: elem is not None and elem.get( val ) or ''
  798. self.name = f( elem, 'name' )
  799. self.id = f( elem, 'id' )
  800. self.version = f( elem, 'version' )
  801. self.elems = odict()
  802. def copy( self ):
  803. copy = ToolSection()
  804. copy.name = self.name
  805. copy.id = self.id
  806. copy.version = self.version
  807. copy.elems = self.elems.copy()
  808. return copy
  809. def to_dict( self, trans, link_details=False ):
  810. """ Return a dict that includes section's attributes. """
  811. section_dict = super( ToolSection, self ).to_dict()
  812. section_elts = []
  813. kwargs = dict(
  814. trans=trans,
  815. link_details=link_details
  816. )
  817. for elt in self.elems.values():
  818. section_elts.append( to_dict_helper( elt, kwargs ) )
  819. section_dict[ 'elems' ] = section_elts
  820. return section_dict
  821. class ToolSectionLabel( object, Dictifiable ):
  822. """
  823. A label for a set of tools that can be displayed above groups of tools
  824. and sections in the user interface
  825. """
  826. dict_collection_visible_keys = ( 'id', 'text', 'version' )
  827. def __init__( self, elem ):
  828. self.text = elem.get( "text" )
  829. self.id = elem.get( "id" )
  830. self.version = elem.get( "version" ) or ''
  831. class DefaultToolState( object ):
  832. """
  833. Keeps track of the state of a users interaction with a tool between
  834. requests. The default tool state keeps track of the current page (for
  835. multipage "wizard" tools) and the values of all
  836. """
  837. def __init__( self ):
  838. self.page = 0
  839. self.rerun_remap_job_id = None
  840. self.inputs = None
  841. def encode( self, tool, app, secure=True ):
  842. """
  843. Convert the data to a string
  844. """
  845. # Convert parameters to a dictionary of strings, and save curent
  846. # page in that dict
  847. value = params_to_strings( tool.inputs, self.inputs, app )
  848. value["__page__"] = self.page
  849. value["__rerun_remap_job_id__"] = self.rerun_remap_job_id
  850. value = json.dumps( value )
  851. # Make it secure
  852. if secure:
  853. a = hmac_new( app.config.tool_secret, value )
  854. b = binascii.hexlify( value )
  855. return "%s:%s" % ( a, b )
  856. else:
  857. return value
  858. def decode( self, value, tool, app, secure=True ):
  859. """
  860. Restore the state from a string
  861. """
  862. if secure:
  863. # Extract and verify hash
  864. a, b = value.split( ":" )
  865. value = binascii.unhexlify( b )
  866. test = hmac_new( app.config.tool_secret, value )
  867. assert a == test
  868. # Restore from string
  869. values = json_fix( json.loads( value ) )
  870. self.page = values.pop( "__page__" )
  871. if '__rerun_remap_job_id__' in values:
  872. self.rerun_remap_job_id = values.pop( "__rerun_remap_job_id__" )
  873. else:
  874. self.rerun_remap_job_id = None
  875. self.inputs = params_from_strings( tool.inputs, values, app, ignore_errors=True )
  876. class ToolOutput( object, Dictifiable ):
  877. """
  878. Represents an output datasets produced by a tool. For backward
  879. compatibility this behaves as if it were the tuple::
  880. (format, metadata_source, parent)
  881. """
  882. dict_collection_visible_keys = ( 'name', 'format', 'label', 'hidden' )
  883. def __init__( self, name, format=None, format_source=None, metadata_source=None,
  884. parent=None, label=None, filters=None, actions=None, hidden=False ):
  885. self.name = name
  886. self.format = format
  887. self.format_source = format_source
  888. self.metadata_source = metadata_source
  889. self.parent = parent
  890. self.label = label
  891. self.filters = filters or []
  892. self.actions = actions
  893. self.hidden = hidden
  894. # Tuple emulation
  895. def __len__( self ):
  896. return 3
  897. def __getitem__( self, index ):
  898. if index == 0:
  899. return self.format
  900. elif index == 1:
  901. return self.metadata_source
  902. elif index == 2:
  903. return self.parent
  904. else:
  905. raise IndexError( index )
  906. def __iter__( self ):
  907. return iter( ( self.format, self.metadata_source, self.parent ) )
  908. class Tool( object, Dictifiable ):
  909. """
  910. Represents a computational tool that can be executed through Galaxy.
  911. """
  912. tool_type = 'default'
  913. requires_setting_metadata = True
  914. default_tool_action = DefaultToolAction
  915. dict_collection_visible_keys = ( 'id', 'name', 'version', 'description' )
  916. def __init__( self, config_file, root, app, guid=None, repository_id=None ):
  917. """Load a tool from the config named by `config_file`"""
  918. # Determine the full path of the directory where the tool config is
  919. self.config_file = config_file
  920. self.tool_dir = os.path.dirname( config_file )
  921. self.app = app
  922. self.repository_id = repository_id
  923. #setup initial attribute values
  924. self.inputs = odict()
  925. self.stdio_exit_codes = list()
  926. self.stdio_regexes = list()
  927. self.inputs_by_page = list()
  928. self.display_by_page = list()
  929. self.action = '/tool_runner/index'
  930. self.target = 'galaxy_main'
  931. self.method = 'post'
  932. self.check_values = True
  933. self.nginx_upload = False
  934. self.input_required = False
  935. self.display_interface = True
  936. self.require_login = False
  937. self.rerun = False
  938. # Define a place to keep track of all input These
  939. # differ from the inputs dictionary in that inputs can be page
  940. # elements like conditionals, but input_params are basic form
  941. # parameters like SelectField objects. This enables us to more
  942. # easily ensure that parameter dependencies like index files or
  943. # tool_data_table_conf.xml entries exist.
  944. self.input_params = []
  945. # Attributes of tools installed from Galaxy tool sheds.
  946. self.tool_shed = None
  947. self.repository_name = None
  948. self.repository_owner = None
  949. self.installed_changeset_revision = None
  950. # The tool.id value will be the value of guid, but we'll keep the
  951. # guid attribute since it is useful to have.
  952. self.guid = guid
  953. self