PageRenderTime 36ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/galaxy/model/__init__.py

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 3547 lines | 3472 code | 38 blank | 37 comment | 18 complexity | ec9af105465b68c9ba85c2dae5583df1 MD5 | raw file
  1. """
  2. Galaxy data model classes
  3. Naming: try to use class names that have a distinct plural form so that
  4. the relationship cardinalities are obvious (e.g. prefer Dataset to Data)
  5. """
  6. from galaxy import eggs
  7. eggs.require("pexpect")
  8. import codecs
  9. import errno
  10. import logging
  11. import operator
  12. import os
  13. import pexpect
  14. import json
  15. import socket
  16. import time
  17. from uuid import UUID, uuid4
  18. from string import Template
  19. from itertools import ifilter
  20. from itertools import chain
  21. import galaxy.datatypes
  22. import galaxy.datatypes.registry
  23. import galaxy.security.passwords
  24. from galaxy.datatypes.metadata import MetadataCollection
  25. from galaxy.model.item_attrs import Dictifiable, UsesAnnotations
  26. from galaxy.security import get_permitted_actions
  27. from galaxy.util import is_multi_byte, nice_size, Params, restore_text, send_mail
  28. from galaxy.util import ready_name_for_url
  29. from galaxy.util.bunch import Bunch
  30. from galaxy.util.hash_util import new_secure_hash
  31. from galaxy.util.directory_hash import directory_hash_id
  32. from galaxy.web.framework.helpers import to_unicode
  33. from galaxy.web.form_builder import (AddressField, CheckboxField, HistoryField,
  34. PasswordField, SelectField, TextArea, TextField, WorkflowField,
  35. WorkflowMappingField)
  36. from sqlalchemy.orm import object_session
  37. from sqlalchemy.orm import joinedload
  38. from sqlalchemy.sql.expression import func
  39. from sqlalchemy import not_
  40. log = logging.getLogger( __name__ )
  41. datatypes_registry = galaxy.datatypes.registry.Registry()
  42. # Default Value Required for unit tests
  43. datatypes_registry.load_datatypes()
  44. # When constructing filters with in for a fixed set of ids, maximum
  45. # number of items to place in the IN statement. Different databases
  46. # are going to have different limits so it is likely best to not let
  47. # this be unlimited - filter in Python if over this limit.
  48. MAX_IN_FILTER_LENGTH = 100
  49. class NoConverterException(Exception):
  50. def __init__(self, value):
  51. self.value = value
  52. def __str__(self):
  53. return repr(self.value)
  54. class ConverterDependencyException(Exception):
  55. def __init__(self, value):
  56. self.value = value
  57. def __str__(self):
  58. return repr(self.value)
  59. def set_datatypes_registry( d_registry ):
  60. """
  61. Set up datatypes_registry
  62. """
  63. global datatypes_registry
  64. datatypes_registry = d_registry
  65. class HasName:
  66. def get_display_name( self ):
  67. """
  68. These objects have a name attribute can be either a string or a unicode
  69. object. If string, convert to unicode object assuming 'utf-8' format.
  70. """
  71. name = self.name
  72. if isinstance(name, str):
  73. name = unicode(name, 'utf-8')
  74. return name
  75. class User( object, Dictifiable ):
  76. use_pbkdf2 = True
  77. """
  78. Data for a Galaxy user or admin and relations to their
  79. histories, credentials, and roles.
  80. """
  81. # attributes that will be accessed and returned when calling to_dict( view='collection' )
  82. dict_collection_visible_keys = ( 'id', 'email' )
  83. # attributes that will be accessed and returned when calling to_dict( view='element' )
  84. dict_element_visible_keys = ( 'id', 'email', 'username', 'total_disk_usage', 'nice_total_disk_usage' )
  85. def __init__( self, email=None, password=None ):
  86. self.email = email
  87. self.password = password
  88. self.external = False
  89. self.deleted = False
  90. self.purged = False
  91. self.active = False
  92. self.activation_token = None
  93. self.username = None
  94. # Relationships
  95. self.histories = []
  96. self.credentials = []
  97. #? self.roles = []
  98. def set_password_cleartext( self, cleartext ):
  99. """
  100. Set user password to the digest of `cleartext`.
  101. """
  102. if User.use_pbkdf2:
  103. self.password = galaxy.security.passwords.hash_password( cleartext )
  104. else:
  105. self.password = new_secure_hash( text_type=cleartext )
  106. def check_password( self, cleartext ):
  107. """
  108. Check if `cleartext` matches user password when hashed.
  109. """
  110. return galaxy.security.passwords.check_password( cleartext, self.password )
  111. def all_roles( self ):
  112. """
  113. Return a unique list of Roles associated with this user or any of their groups.
  114. """
  115. roles = [ ura.role for ura in self.roles ]
  116. for group in [ uga.group for uga in self.groups ]:
  117. for role in [ gra.role for gra in group.roles ]:
  118. if role not in roles:
  119. roles.append( role )
  120. return roles
  121. def get_disk_usage( self, nice_size=False ):
  122. """
  123. Return byte count of disk space used by user or a human-readable
  124. string if `nice_size` is `True`.
  125. """
  126. rval = 0
  127. if self.disk_usage is not None:
  128. rval = self.disk_usage
  129. if nice_size:
  130. rval = galaxy.datatypes.data.nice_size( rval )
  131. return rval
  132. def set_disk_usage( self, bytes ):
  133. """
  134. Manually set the disk space used by a user to `bytes`.
  135. """
  136. self.disk_usage = bytes
  137. total_disk_usage = property( get_disk_usage, set_disk_usage )
  138. @property
  139. def nice_total_disk_usage( self ):
  140. """
  141. Return byte count of disk space used in a human-readable string.
  142. """
  143. return self.get_disk_usage( nice_size=True )
  144. def calculate_disk_usage( self ):
  145. """
  146. Return byte count total of disk space used by all non-purged, non-library
  147. HDAs in non-purged histories.
  148. """
  149. # maintain a list so that we don't double count
  150. dataset_ids = []
  151. total = 0
  152. # this can be a huge number and can run out of memory, so we avoid the mappers
  153. db_session = object_session( self )
  154. for history in db_session.query( History ).enable_eagerloads( False ).filter_by( user_id=self.id, purged=False ).yield_per( 1000 ):
  155. for hda in db_session.query( HistoryDatasetAssociation ).enable_eagerloads( False ).filter_by( history_id=history.id, purged=False ).yield_per( 1000 ):
  156. #TODO: def hda.counts_toward_disk_usage():
  157. # return ( not self.dataset.purged and not self.dataset.library_associations )
  158. if not hda.dataset.id in dataset_ids and not hda.dataset.purged and not hda.dataset.library_associations:
  159. dataset_ids.append( hda.dataset.id )
  160. total += hda.dataset.get_total_size()
  161. return total
  162. @staticmethod
  163. def user_template_environment( user ):
  164. """
  165. >>> env = User.user_template_environment(None)
  166. >>> env['__user_email__']
  167. 'Anonymous'
  168. >>> env['__user_id__']
  169. 'Anonymous'
  170. >>> user = User('foo@example.com')
  171. >>> user.id = 6
  172. >>> user.username = 'foo2'
  173. >>> env = User.user_template_environment(user)
  174. >>> env['__user_id__']
  175. '6'
  176. >>> env['__user_name__']
  177. 'foo2'
  178. """
  179. if user:
  180. user_id = '%d' % user.id
  181. user_email = str( user.email )
  182. user_name = str( user.username )
  183. else:
  184. user = None
  185. user_id = 'Anonymous'
  186. user_email = 'Anonymous'
  187. user_name = 'Anonymous'
  188. environment = {}
  189. environment[ '__user__' ] = user
  190. environment[ '__user_id__' ] = environment[ 'userId' ] = user_id
  191. environment[ '__user_email__' ] = environment[ 'userEmail' ] = user_email
  192. environment[ '__user_name__' ] = user_name
  193. return environment
  194. @staticmethod
  195. def expand_user_properties( user, in_string ):
  196. """
  197. """
  198. environment = User.user_template_environment( user )
  199. return Template( in_string ).safe_substitute( environment )
  200. class Job( object, Dictifiable ):
  201. dict_collection_visible_keys = [ 'id', 'state', 'exit_code', 'update_time', 'create_time' ]
  202. dict_element_visible_keys = [ 'id', 'state', 'exit_code', 'update_time', 'create_time' ]
  203. """
  204. A job represents a request to run a tool given input datasets, tool
  205. parameters, and output datasets.
  206. """
  207. states = Bunch( NEW = 'new',
  208. UPLOAD = 'upload',
  209. WAITING = 'waiting',
  210. QUEUED = 'queued',
  211. RUNNING = 'running',
  212. OK = 'ok',
  213. ERROR = 'error',
  214. PAUSED = 'paused',
  215. DELETED = 'deleted',
  216. DELETED_NEW = 'deleted_new' )
  217. # Please include an accessor (get/set pair) for any new columns/members.
  218. def __init__( self ):
  219. self.session_id = None
  220. self.user_id = None
  221. self.tool_id = None
  222. self.tool_version = None
  223. self.command_line = None
  224. self.param_filename = None
  225. self.parameters = []
  226. self.input_datasets = []
  227. self.output_datasets = []
  228. self.input_library_datasets = []
  229. self.output_library_datasets = []
  230. self.state = Job.states.NEW
  231. self.info = None
  232. self.job_runner_name = None
  233. self.job_runner_external_id = None
  234. self.destination_id = None
  235. self.destination_params = None
  236. self.post_job_actions = []
  237. self.imported = False
  238. self.handler = None
  239. self.exit_code = None
  240. @property
  241. def finished( self ):
  242. states = self.states
  243. return self.state in [
  244. states.OK,
  245. states.ERROR,
  246. states.DELETED,
  247. states.DELETED_NEW,
  248. ]
  249. # TODO: Add accessors for members defined in SQL Alchemy for the Job table and
  250. # for the mapper defined to the Job table.
  251. def get_external_output_metadata( self ):
  252. """
  253. The external_output_metadata is currently a reference from Job to
  254. JobExternalOutputMetadata. It exists for a job but not a task.
  255. """
  256. return self.external_output_metadata
  257. def get_session_id( self ):
  258. return self.session_id
  259. def get_user_id( self ):
  260. return self.user_id
  261. def get_tool_id( self ):
  262. return self.tool_id
  263. def get_tool_version( self ):
  264. return self.tool_version
  265. def get_command_line( self ):
  266. return self.command_line
  267. def get_param_filename( self ):
  268. return self.param_filename
  269. def get_parameters( self ):
  270. return self.parameters
  271. def get_input_datasets( self ):
  272. return self.input_datasets
  273. def get_output_datasets( self ):
  274. return self.output_datasets
  275. def get_input_library_datasets( self ):
  276. return self.input_library_datasets
  277. def get_output_library_datasets( self ):
  278. return self.output_library_datasets
  279. def get_state( self ):
  280. return self.state
  281. def get_info( self ):
  282. return self.info
  283. def get_job_runner_name( self ):
  284. # This differs from the Task class in that job_runner_name is
  285. # accessed instead of task_runner_name. Note that the field
  286. # runner_name is not the same thing.
  287. return self.job_runner_name
  288. def get_job_runner_external_id( self ):
  289. # This is different from the Task just in the member accessed:
  290. return self.job_runner_external_id
  291. def get_post_job_actions( self ):
  292. return self.post_job_actions
  293. def get_imported( self ):
  294. return self.imported
  295. def get_handler( self ):
  296. return self.handler
  297. def get_params( self ):
  298. return self.params
  299. def get_user( self ):
  300. # This is defined in the SQL Alchemy mapper as a relation to the User.
  301. return self.user
  302. def get_id( self ):
  303. # This is defined in the SQL Alchemy's Job table (and not in the model).
  304. return self.id
  305. def get_tasks( self ):
  306. # The tasks member is pert of a reference in the SQL Alchemy schema:
  307. return self.tasks
  308. def get_id_tag( self ):
  309. """
  310. Return a tag that can be useful in identifying a Job.
  311. This returns the Job's get_id
  312. """
  313. return "%s" % self.id;
  314. def set_session_id( self, session_id ):
  315. self.session_id = session_id
  316. def set_user_id( self, user_id ):
  317. self.user_id = user_id
  318. def set_tool_id( self, tool_id ):
  319. self.tool_id = tool_id
  320. def set_tool_version( self, tool_version ):
  321. self.tool_version = tool_version
  322. def set_command_line( self, command_line ):
  323. self.command_line = command_line
  324. def set_param_filename( self, param_filename ):
  325. self.param_filename = param_filename
  326. def set_parameters( self, parameters ):
  327. self.parameters = parameters
  328. def set_input_datasets( self, input_datasets ):
  329. self.input_datasets = input_datasets
  330. def set_output_datasets( self, output_datasets ):
  331. self.output_datasets = output_datasets
  332. def set_input_library_datasets( self, input_library_datasets ):
  333. self.input_library_datasets = input_library_datasets
  334. def set_output_library_datasets( self, output_library_datasets ):
  335. self.output_library_datasets = output_library_datasets
  336. def set_info( self, info ):
  337. self.info = info
  338. def set_runner_name( self, job_runner_name ):
  339. self.job_runner_name = job_runner_name
  340. def set_runner_external_id( self, job_runner_external_id ):
  341. self.job_runner_external_id = job_runner_external_id
  342. def set_post_job_actions( self, post_job_actions ):
  343. self.post_job_actions = post_job_actions
  344. def set_imported( self, imported ):
  345. self.imported = imported
  346. def set_handler( self, handler ):
  347. self.handler = handler
  348. def set_params( self, params ):
  349. self.params = params
  350. def add_parameter( self, name, value ):
  351. self.parameters.append( JobParameter( name, value ) )
  352. def add_input_dataset( self, name, dataset ):
  353. self.input_datasets.append( JobToInputDatasetAssociation( name, dataset ) )
  354. def add_output_dataset( self, name, dataset ):
  355. self.output_datasets.append( JobToOutputDatasetAssociation( name, dataset ) )
  356. def add_input_library_dataset( self, name, dataset ):
  357. self.input_library_datasets.append( JobToInputLibraryDatasetAssociation( name, dataset ) )
  358. def add_output_library_dataset( self, name, dataset ):
  359. self.output_library_datasets.append( JobToOutputLibraryDatasetAssociation( name, dataset ) )
  360. def add_post_job_action(self, pja):
  361. self.post_job_actions.append( PostJobActionAssociation( pja, self ) )
  362. def set_state( self, state ):
  363. """
  364. This is the only set method that performs extra work. In this case, the
  365. state is propagated down to datasets.
  366. """
  367. self.state = state
  368. # For historical reasons state propogates down to datasets
  369. for da in self.output_datasets:
  370. da.dataset.state = state
  371. def get_param_values( self, app, ignore_errors=False ):
  372. """
  373. Read encoded parameter values from the database and turn back into a
  374. dict of tool parameter values.
  375. """
  376. param_dict = dict( [ ( p.name, p.value ) for p in self.parameters ] )
  377. tool = app.toolbox.get_tool( self.tool_id )
  378. param_dict = tool.params_from_strings( param_dict, app, ignore_errors=ignore_errors )
  379. return param_dict
  380. def check_if_output_datasets_deleted( self ):
  381. """
  382. Return true if all of the output datasets associated with this job are
  383. in the deleted state
  384. """
  385. for dataset_assoc in self.output_datasets:
  386. dataset = dataset_assoc.dataset
  387. # only the originator of the job can delete a dataset to cause
  388. # cancellation of the job, no need to loop through history_associations
  389. if not dataset.deleted:
  390. return False
  391. return True
  392. def mark_deleted( self, track_jobs_in_database=False ):
  393. """
  394. Mark this job as deleted, and mark any output datasets as discarded.
  395. """
  396. if track_jobs_in_database:
  397. self.state = Job.states.DELETED_NEW
  398. else:
  399. self.state = Job.states.DELETED
  400. self.info = "Job output deleted by user before job completed."
  401. for dataset_assoc in self.output_datasets:
  402. dataset = dataset_assoc.dataset
  403. dataset.deleted = True
  404. dataset.state = dataset.states.DISCARDED
  405. for dataset in dataset.dataset.history_associations:
  406. # propagate info across shared datasets
  407. dataset.deleted = True
  408. dataset.blurb = 'deleted'
  409. dataset.peek = 'Job deleted'
  410. dataset.info = 'Job output deleted by user before job completed'
  411. def to_dict( self, view='collection' ):
  412. rval = super( Job, self ).to_dict( view=view )
  413. rval['tool_id'] = self.tool_id
  414. if view == 'element':
  415. param_dict = dict( [ ( p.name, p.value ) for p in self.parameters ] )
  416. rval['params'] = param_dict
  417. input_dict = {}
  418. for i in self.input_datasets:
  419. if i.dataset is not None:
  420. input_dict[i.name] = {"id" : i.dataset.id, "src" : "hda"}
  421. for i in self.input_library_datasets:
  422. if i.dataset is not None:
  423. input_dict[i.name] = {"id" : i.dataset.id, "src" : "ldda"}
  424. for k in input_dict:
  425. if k in param_dict:
  426. del param_dict[k]
  427. rval['inputs'] = input_dict
  428. output_dict = {}
  429. for i in self.output_datasets:
  430. if i.dataset is not None:
  431. output_dict[i.name] = {"id" : i.dataset.id, "src" : "hda"}
  432. for i in self.output_library_datasets:
  433. if i.dataset is not None:
  434. output_dict[i.name] = {"id" : i.dataset.id, "src" : "ldda"}
  435. rval['outputs'] = output_dict
  436. return rval
  437. class Task( object ):
  438. """
  439. A task represents a single component of a job.
  440. """
  441. states = Bunch( NEW = 'new',
  442. WAITING = 'waiting',
  443. QUEUED = 'queued',
  444. RUNNING = 'running',
  445. OK = 'ok',
  446. ERROR = 'error',
  447. DELETED = 'deleted' )
  448. # Please include an accessor (get/set pair) for any new columns/members.
  449. def __init__( self, job, working_directory, prepare_files_cmd ):
  450. self.command_line = None
  451. self.parameters = []
  452. self.state = Task.states.NEW
  453. self.info = None
  454. self.working_directory = working_directory
  455. self.task_runner_name = None
  456. self.task_runner_external_id = None
  457. self.job = job
  458. self.stdout = ""
  459. self.stderr = ""
  460. self.exit_code = None
  461. self.prepare_input_files_cmd = prepare_files_cmd
  462. def get_param_values( self, app ):
  463. """
  464. Read encoded parameter values from the database and turn back into a
  465. dict of tool parameter values.
  466. """
  467. param_dict = dict( [ ( p.name, p.value ) for p in self.parent_job.parameters ] )
  468. tool = app.toolbox.get_tool( self.tool_id )
  469. param_dict = tool.params_from_strings( param_dict, app )
  470. return param_dict
  471. def get_id( self ):
  472. # This is defined in the SQL Alchemy schema:
  473. return self.id
  474. def get_id_tag( self ):
  475. """
  476. Return an id tag suitable for identifying the task.
  477. This combines the task's job id and the task's own id.
  478. """
  479. return "%s_%s" % ( self.job.get_id(), self.get_id() )
  480. def get_command_line( self ):
  481. return self.command_line
  482. def get_parameters( self ):
  483. return self.parameters
  484. def get_state( self ):
  485. return self.state
  486. def get_info( self ):
  487. return self.info
  488. def get_working_directory( self ):
  489. return self.working_directory
  490. def get_task_runner_name( self ):
  491. return self.task_runner_name
  492. def get_task_runner_external_id( self ):
  493. return self.task_runner_external_id
  494. def get_job( self ):
  495. return self.job
  496. def get_stdout( self ):
  497. return self.stdout
  498. def get_stderr( self ):
  499. return self.stderr
  500. def get_prepare_input_files_cmd( self ):
  501. return self.prepare_input_files_cmd
  502. # The following accessors are for members that are in the Job class but
  503. # not in the Task class. So they can either refer to the parent Job
  504. # or return None, depending on whether Tasks need to point to the parent
  505. # (e.g., for a session) or never use the member (e.g., external output
  506. # metdata). These can be filled in as needed.
  507. def get_external_output_metadata( self ):
  508. """
  509. The external_output_metadata is currently a backref to
  510. JobExternalOutputMetadata. It exists for a job but not a task,
  511. and when a task is cancelled its corresponding parent Job will
  512. be cancelled. So None is returned now, but that could be changed
  513. to self.get_job().get_external_output_metadata().
  514. """
  515. return None
  516. def get_job_runner_name( self ):
  517. """
  518. Since runners currently access Tasks the same way they access Jobs,
  519. this method just refers to *this* instance's runner.
  520. """
  521. return self.task_runner_name
  522. def get_job_runner_external_id( self ):
  523. """
  524. Runners will use the same methods to get information about the Task
  525. class as they will about the Job class, so this method just returns
  526. the task's external id.
  527. """
  528. # TODO: Merge into get_runner_external_id.
  529. return self.task_runner_external_id
  530. def get_session_id( self ):
  531. # The Job's galaxy session is equal to the Job's session, so the
  532. # Job's session is the same as the Task's session.
  533. return self.get_job().get_session_id()
  534. def set_id( self, id ):
  535. # This is defined in the SQL Alchemy's mapper and not here.
  536. # This should never be called.
  537. self.id = id
  538. def set_command_line( self, command_line ):
  539. self.command_line = command_line
  540. def set_parameters( self, parameters ):
  541. self.parameters = parameters
  542. def set_state( self, state ):
  543. self.state = state
  544. def set_info( self, info ):
  545. self.info = info
  546. def set_working_directory( self, working_directory ):
  547. self.working_directory = working_directory
  548. def set_task_runner_name( self, task_runner_name ):
  549. self.task_runner_name = task_runner_name
  550. def set_job_runner_external_id( self, task_runner_external_id ):
  551. # This method is available for runners that do not want/need to
  552. # differentiate between the kinds of Runnable things (Jobs and Tasks)
  553. # that they're using.
  554. log.debug( "Task %d: Set external id to %s"
  555. % ( self.id, task_runner_external_id ) )
  556. self.task_runner_external_id = task_runner_external_id
  557. def set_task_runner_external_id( self, task_runner_external_id ):
  558. self.task_runner_external_id = task_runner_external_id
  559. def set_job( self, job ):
  560. self.job = job
  561. def set_stdout( self, stdout ):
  562. self.stdout = stdout
  563. def set_stderr( self, stderr ):
  564. self.stderr = stderr
  565. def set_prepare_input_files_cmd( self, prepare_input_files_cmd ):
  566. self.prepare_input_files_cmd = prepare_input_files_cmd
  567. class JobParameter( object ):
  568. def __init__( self, name, value ):
  569. self.name = name
  570. self.value = value
  571. class JobToInputDatasetAssociation( object ):
  572. def __init__( self, name, dataset ):
  573. self.name = name
  574. self.dataset = dataset
  575. class JobToOutputDatasetAssociation( object ):
  576. def __init__( self, name, dataset ):
  577. self.name = name
  578. self.dataset = dataset
  579. class JobToInputLibraryDatasetAssociation( object ):
  580. def __init__( self, name, dataset ):
  581. self.name = name
  582. self.dataset = dataset
  583. class JobToOutputLibraryDatasetAssociation( object ):
  584. def __init__( self, name, dataset ):
  585. self.name = name
  586. self.dataset = dataset
  587. class PostJobAction( object ):
  588. def __init__( self, action_type, workflow_step, output_name = None, action_arguments = None):
  589. self.action_type = action_type
  590. self.output_name = output_name
  591. self.action_arguments = action_arguments
  592. self.workflow_step = workflow_step
  593. class PostJobActionAssociation( object ):
  594. def __init__(self, pja, job):
  595. self.job = job
  596. self.post_job_action = pja
  597. class JobExternalOutputMetadata( object ):
  598. def __init__( self, job = None, dataset = None ):
  599. self.job = job
  600. if isinstance( dataset, galaxy.model.HistoryDatasetAssociation ):
  601. self.history_dataset_association = dataset
  602. elif isinstance( dataset, galaxy.model.LibraryDatasetDatasetAssociation ):
  603. self.library_dataset_dataset_association = dataset
  604. @property
  605. def dataset( self ):
  606. if self.history_dataset_association:
  607. return self.history_dataset_association
  608. elif self.library_dataset_dataset_association:
  609. return self.library_dataset_dataset_association
  610. return None
  611. class JobExportHistoryArchive( object ):
  612. def __init__( self, job=None, history=None, dataset=None, compressed=False, \
  613. history_attrs_filename=None, datasets_attrs_filename=None,
  614. jobs_attrs_filename=None ):
  615. self.job = job
  616. self.history = history
  617. self.dataset = dataset
  618. self.compressed = compressed
  619. self.history_attrs_filename = history_attrs_filename
  620. self.datasets_attrs_filename = datasets_attrs_filename
  621. self.jobs_attrs_filename = jobs_attrs_filename
  622. @property
  623. def up_to_date( self ):
  624. """ Return False, if a new export should be generated for corresponding
  625. history.
  626. """
  627. job = self.job
  628. return job.state not in [ Job.states.ERROR, Job.states.DELETED ] \
  629. and job.update_time > self.history.update_time
  630. @property
  631. def ready( self ):
  632. return self.job.state == Job.states.OK
  633. @property
  634. def preparing( self ):
  635. return self.job.state in [ Job.states.RUNNING, Job.states.QUEUED, Job.states.WAITING ]
  636. @property
  637. def export_name( self ):
  638. # Stream archive.
  639. hname = ready_name_for_url( self.history.name )
  640. hname = "Galaxy-History-%s.tar" % ( hname )
  641. if self.compressed:
  642. hname += ".gz"
  643. return hname
  644. class JobImportHistoryArchive( object ):
  645. def __init__( self, job=None, history=None, archive_dir=None ):
  646. self.job = job
  647. self.history = history
  648. self.archive_dir=archive_dir
  649. class GenomeIndexToolData( object ):
  650. def __init__( self, job=None, params=None, dataset=None, deferred_job=None, \
  651. transfer_job=None, fasta_path=None, created_time=None, modified_time=None, \
  652. dbkey=None, user=None, indexer=None ):
  653. self.job = job
  654. self.dataset = dataset
  655. self.fasta_path = fasta_path
  656. self.user = user
  657. self.indexer = indexer
  658. self.created_time = created_time
  659. self.modified_time = modified_time
  660. self.deferred = deferred_job
  661. self.transfer = transfer_job
  662. class DeferredJob( object ):
  663. states = Bunch( NEW = 'new',
  664. WAITING = 'waiting',
  665. QUEUED = 'queued',
  666. RUNNING = 'running',
  667. OK = 'ok',
  668. ERROR = 'error' )
  669. def __init__( self, state=None, plugin=None, params=None ):
  670. self.state = state
  671. self.plugin = plugin
  672. self.params = params
  673. def get_check_interval( self ):
  674. if not hasattr( self, '_check_interval' ):
  675. self._check_interval = None
  676. return self._check_interval
  677. def set_check_interval( self, seconds ):
  678. self._check_interval = seconds
  679. check_interval = property( get_check_interval, set_check_interval )
  680. def get_last_check( self ):
  681. if not hasattr( self, '_last_check' ):
  682. self._last_check = 0
  683. return self._last_check
  684. def set_last_check( self, seconds ):
  685. try:
  686. self._last_check = int( seconds )
  687. except:
  688. self._last_check = time.time()
  689. last_check = property( get_last_check, set_last_check )
  690. @property
  691. def is_check_time( self ):
  692. if self.check_interval is None:
  693. return True
  694. elif ( int( time.time() ) - self.last_check ) > self.check_interval:
  695. return True
  696. else:
  697. return False
  698. class Group( object, Dictifiable ):
  699. dict_collection_visible_keys = ( 'id', 'name' )
  700. dict_element_visible_keys = ( 'id', 'name' )
  701. def __init__( self, name = None ):
  702. self.name = name
  703. self.deleted = False
  704. class UserGroupAssociation( object ):
  705. def __init__( self, user, group ):
  706. self.user = user
  707. self.group = group
  708. class History( object, Dictifiable, UsesAnnotations, HasName ):
  709. dict_collection_visible_keys = ( 'id', 'name', 'published', 'deleted' )
  710. dict_element_visible_keys = ( 'id', 'name', 'published', 'deleted', 'genome_build', 'purged', 'importable', 'slug' )
  711. default_name = 'Unnamed history'
  712. def __init__( self, id=None, name=None, user=None ):
  713. self.id = id
  714. self.name = name or History.default_name
  715. self.deleted = False
  716. self.purged = False
  717. self.importing = False
  718. self.genome_build = None
  719. self.published = False
  720. # Relationships
  721. self.user = user
  722. self.datasets = []
  723. self.galaxy_sessions = []
  724. self.tags = []
  725. def _next_hid( self ):
  726. # this is overriden in mapping.py db_next_hid() method
  727. if len( self.datasets ) == 0:
  728. return 1
  729. else:
  730. last_hid = 0
  731. for dataset in self.datasets:
  732. if dataset.hid > last_hid:
  733. last_hid = dataset.hid
  734. return last_hid + 1
  735. def add_galaxy_session( self, galaxy_session, association=None ):
  736. if association is None:
  737. self.galaxy_sessions.append( GalaxySessionToHistoryAssociation( galaxy_session, self ) )
  738. else:
  739. self.galaxy_sessions.append( association )
  740. def add_dataset( self, dataset, parent_id=None, genome_build=None, set_hid=True, quota=True ):
  741. if isinstance( dataset, Dataset ):
  742. dataset = HistoryDatasetAssociation(dataset=dataset)
  743. object_session( self ).add( dataset )
  744. object_session( self ).flush()
  745. elif not isinstance( dataset, HistoryDatasetAssociation ):
  746. raise TypeError, ( "You can only add Dataset and HistoryDatasetAssociation instances to a history" +
  747. " ( you tried to add %s )." % str( dataset ) )
  748. if parent_id:
  749. for data in self.datasets:
  750. if data.id == parent_id:
  751. dataset.hid = data.hid
  752. break
  753. else:
  754. if set_hid:
  755. dataset.hid = self._next_hid()
  756. else:
  757. if set_hid:
  758. dataset.hid = self._next_hid()
  759. if quota and self.user:
  760. self.user.total_disk_usage += dataset.quota_amount( self.user )
  761. dataset.history = self
  762. if genome_build not in [None, '?']:
  763. self.genome_build = genome_build
  764. self.datasets.append( dataset )
  765. return dataset
  766. def copy( self, name=None, target_user=None, activatable=False, all_datasets=False ):
  767. """
  768. Return a copy of this history using the given `name` and `target_user`.
  769. If `activatable`, copy only non-deleted datasets. If `all_datasets`, copy
  770. non-deleted, deleted, and purged datasets.
  771. """
  772. # Create new history.
  773. if not name:
  774. name = self.name
  775. if not target_user:
  776. target_user = self.user
  777. quota = True
  778. if target_user == self.user:
  779. quota = False
  780. new_history = History( name=name, user=target_user )
  781. db_session = object_session( self )
  782. db_session.add( new_history )
  783. db_session.flush()
  784. # Copy annotation.
  785. self.copy_item_annotation( db_session, self.user, self, target_user, new_history )
  786. # Copy Tags
  787. new_history.copy_tags_from(target_user=target_user, source_history=self)
  788. # Copy HDAs.
  789. if activatable:
  790. hdas = self.activatable_datasets
  791. elif all_datasets:
  792. hdas = self.datasets
  793. else:
  794. hdas = self.active_datasets
  795. for hda in hdas:
  796. # Copy HDA.
  797. new_hda = hda.copy( copy_children=True )
  798. new_history.add_dataset( new_hda, set_hid = False, quota=quota )
  799. db_session.add( new_hda )
  800. db_session.flush()
  801. # Copy annotation.
  802. self.copy_item_annotation( db_session, self.user, hda, target_user, new_hda )
  803. new_history.hid_counter = self.hid_counter
  804. db_session.add( new_history )
  805. db_session.flush()
  806. return new_history
  807. @property
  808. def activatable_datasets( self ):
  809. # This needs to be a list
  810. return [ hda for hda in self.datasets if not hda.dataset.deleted ]
  811. def to_dict( self, view='collection', value_mapper = None ):
  812. # Get basic value.
  813. rval = super( History, self ).to_dict( view=view, value_mapper=value_mapper )
  814. # Add tags.
  815. tags_str_list = []
  816. for tag in self.tags:
  817. tag_str = tag.user_tname
  818. if tag.value is not None:
  819. tag_str += ":" + tag.user_value
  820. tags_str_list.append( tag_str )
  821. rval[ 'tags' ] = tags_str_list
  822. return rval
  823. def set_from_dict( self, new_data ):
  824. #AKA: set_api_value
  825. """
  826. Set object attributes to the values in dictionary new_data limiting
  827. to only those keys in dict_element_visible_keys.
  828. Returns a dictionary of the keys, values that have been changed.
  829. """
  830. # precondition: keys are proper, values are parsed and validated
  831. changed = {}
  832. # unknown keys are ignored here
  833. for key in [ k for k in new_data.keys() if k in self.dict_element_visible_keys ]:
  834. new_val = new_data[ key ]
  835. old_val = self.__getattribute__( key )
  836. if new_val == old_val:
  837. continue
  838. self.__setattr__( key, new_val )
  839. changed[ key ] = new_val
  840. return changed
  841. @property
  842. def latest_export( self ):
  843. exports = self.exports
  844. return exports and exports[ 0 ]
  845. @property
  846. def get_disk_size_bytes( self ):
  847. return self.get_disk_size( nice_size=False )
  848. def unhide_datasets( self ):
  849. for dataset in self.datasets:
  850. dataset.mark_unhidden()
  851. def resume_paused_jobs( self ):
  852. for dataset in self.datasets:
  853. job = dataset.creating_job
  854. if job is not None and job.state == Job.states.PAUSED:
  855. job.set_state(Job.states.NEW)
  856. def get_disk_size( self, nice_size=False ):
  857. # unique datasets only
  858. db_session = object_session( self )
  859. rval = db_session.query(
  860. func.sum( db_session.query( HistoryDatasetAssociation.dataset_id, Dataset.total_size ).join( Dataset )
  861. .filter( HistoryDatasetAssociation.table.c.history_id == self.id )
  862. .filter( HistoryDatasetAssociation.purged != True )
  863. .filter( Dataset.purged != True )
  864. .distinct().subquery().c.total_size ) ).first()[0]
  865. if rval is None:
  866. rval = 0
  867. if nice_size:
  868. rval = galaxy.datatypes.data.nice_size( rval )
  869. return rval
  870. @property
  871. def active_datasets_children_and_roles( self ):
  872. if not hasattr(self, '_active_datasets_children_and_roles'):
  873. db_session = object_session( self )
  874. query = db_session.query( HistoryDatasetAssociation ).filter( HistoryDatasetAssociation.table.c.history_id == self.id ). \
  875. filter( not_( HistoryDatasetAssociation.deleted ) ). \
  876. order_by( HistoryDatasetAssociation.table.c.hid.asc() ). \
  877. options(
  878. joinedload("children"),
  879. joinedload("dataset"),
  880. joinedload("dataset.actions"),
  881. joinedload("dataset.actions.role"),
  882. )
  883. self._active_datasets_children_and_roles = query.all()
  884. return self._active_datasets_children_and_roles
  885. def contents_iter( self, **kwds ):
  886. """
  887. Fetch filtered list of contents of history.
  888. """
  889. default_contents_types = [
  890. 'dataset',
  891. ]
  892. types = kwds.get('types', default_contents_types)
  893. iters = []
  894. if 'dataset' in types:
  895. iters.append( self.__dataset_contents_iter( **kwds ) )
  896. return galaxy.util.merge_sorted_iterables( operator.attrgetter( "hid" ), *iters )
  897. def __dataset_contents_iter(self, **kwds):
  898. return self.__filter_contents( HistoryDatasetAssociation, **kwds )
  899. def __filter_contents( self, content_class, **kwds ):
  900. db_session = object_session( self )
  901. assert db_session != None
  902. query = db_session.query( content_class ).filter( content_class.table.c.history_id == self.id )
  903. query = query.order_by( content_class.table.c.hid.asc() )
  904. python_filter = None
  905. deleted = galaxy.util.string_as_bool_or_none( kwds.get( 'deleted', None ) )
  906. if deleted is not None:
  907. query = query.filter( content_class.deleted == deleted )
  908. visible = galaxy.util.string_as_bool_or_none( kwds.get( 'visible', None ) )
  909. if visible is not None:
  910. query = query.filter( content_class.visible == visible )
  911. if 'ids' in kwds:
  912. ids = kwds['ids']
  913. max_in_filter_length = kwds.get('max_in_filter_length', MAX_IN_FILTER_LENGTH)
  914. if len(ids) < max_in_filter_length:
  915. query = query.filter( content_class.id.in_(ids) )
  916. else:
  917. python_filter = lambda content: content.id in ids
  918. if python_filter:
  919. return ifilter(python_filter, query)
  920. else:
  921. return query
  922. def copy_tags_from(self,target_user,source_history):
  923. for src_shta in source_history.tags:
  924. new_shta = src_shta.copy()
  925. new_shta.user = target_user
  926. self.tags.append(new_shta)
  927. class HistoryUserShareAssociation( object ):
  928. def __init__( self ):
  929. self.history = None
  930. self.user = None
  931. class UserRoleAssociation( object ):
  932. def __init__( self, user, role ):
  933. self.user = user
  934. self.role = role
  935. class GroupRoleAssociation( object ):
  936. def __init__( self, group, role ):
  937. self.group = group
  938. self.role = role
  939. class Role( object, Dictifiable ):
  940. dict_collection_visible_keys = ( 'id', 'name' )
  941. dict_element_visible_keys = ( 'id', 'name', 'description', 'type' )
  942. private_id = None
  943. types = Bunch(
  944. PRIVATE = 'private',
  945. SYSTEM = 'system',
  946. USER = 'user',
  947. ADMIN = 'admin',
  948. SHARING = 'sharing'
  949. )
  950. def __init__( self, name="", description="", type="system", deleted=False ):
  951. self.name = name
  952. self.description = description
  953. self.type = type
  954. self.deleted = deleted
  955. class UserQuotaAssociation( object, Dictifiable ):
  956. dict_element_visible_keys = ( 'user', )
  957. def __init__( self, user, quota ):
  958. self.user = user
  959. self.quota = quota
  960. class GroupQuotaAssociation( object, Dictifiable ):
  961. dict_element_visible_keys = ( 'group', )
  962. def __init__( self, group, quota ):
  963. self.group = group
  964. self.quota = quota
  965. class Quota( object, Dictifiable ):
  966. dict_collection_visible_keys = ( 'id', 'name' )
  967. dict_element_visible_keys = ( 'id', 'name', 'description', 'bytes', 'operation', 'display_amount', 'default', 'users', 'groups' )
  968. valid_operations = ( '+', '-', '=' )
  969. def __init__( self, name="", description="", amount=0, operation="=" ):
  970. self.name = name
  971. self.description = description
  972. if amount is None:
  973. self.bytes = -1
  974. else:
  975. self.bytes = amount
  976. self.operation = operation
  977. def get_amount( self ):
  978. if self.bytes == -1:
  979. return None
  980. return self.bytes
  981. def set_amount( self, amount ):
  982. if amount is None:
  983. self.bytes = -1
  984. else:
  985. self.bytes = amount
  986. amount = property( get_amount, set_amount )
  987. @property
  988. def display_amount( self ):
  989. if self.bytes == -1:
  990. return "unlimited"
  991. else:
  992. return nice_size( self.bytes )
  993. class DefaultQuotaAssociation( Quota, Dictifiable ):
  994. dict_element_visible_keys = ( 'type', )
  995. types = Bunch(
  996. UNREGISTERED = 'unregistered',
  997. REGISTERED = 'registered'
  998. )
  999. def __init__( self, type, quota ):
  1000. assert type in self.types.__dict__.values(), 'Invalid type'
  1001. self.type = type
  1002. self.quota = quota
  1003. class DatasetPermissions( object ):
  1004. def __init__( self, action, dataset, role ):
  1005. self.action = action
  1006. self.dataset = dataset
  1007. self.role = role
  1008. class LibraryPermissions( object ):
  1009. def __init__( self, action, library_item, role ):
  1010. self.action = action
  1011. if isinstance( library_item, Library ):
  1012. self.library = library_item
  1013. else:
  1014. raise "Invalid Library specified: %s" % library_item.__class__.__name__
  1015. self.role = role
  1016. class LibraryFolderPermissions( object ):
  1017. def __init__( self, action, library_item, role ):
  1018. self.action = action
  1019. if isinstance( library_item, LibraryFolder ):
  1020. self.folder = library_item
  1021. else:
  1022. raise "Invalid LibraryFolder specified: %s" % library_item.__class__.__name__
  1023. self.role = role
  1024. class LibraryDatasetPermissions( object ):
  1025. def __init__( self, action, library_item, role ):
  1026. self.action = action
  1027. if isinstance( library_item, LibraryDataset ):
  1028. self.library_dataset = library_item
  1029. else:
  1030. raise "Invalid LibraryDataset specified: %s" % library_item.__class__.__name__
  1031. self.role = role
  1032. class LibraryDatasetDatasetAssociationPermissions( object ):
  1033. def __init__( self, action, library_item, role ):
  1034. self.action = action
  1035. if isinstance( library_item, LibraryDatasetDatasetAssociation ):
  1036. self.library_dataset_dataset_association = library_item
  1037. else:
  1038. raise "Invalid LibraryDatasetDatasetAssociation specified: %s" % library_item.__class__.__name__
  1039. self.role = role
  1040. class DefaultUserPermissions( object ):
  1041. def __init__( self, user, action, role ):
  1042. self.user = user
  1043. self.action = action
  1044. self.role = role
  1045. class DefaultHistoryPermissions( object ):
  1046. def __init__( self, history, action, role ):
  1047. self.history = history
  1048. self.action = action
  1049. self.role = role
  1050. class Dataset( object ):
  1051. states = Bunch( NEW = 'new',
  1052. UPLOAD = 'upload',
  1053. QUEUED = 'queued',
  1054. RUNNING = 'running',
  1055. OK = 'ok',
  1056. EMPTY = 'empty',
  1057. ERROR = 'error',
  1058. DISCARDED = 'discarded',
  1059. PAUSED = 'paused',
  1060. SETTING_METADATA = 'setting_metadata',
  1061. FAILED_METADATA = 'failed_metadata' )
  1062. conversion_messages = Bunch( PENDING = "pending",
  1063. NO_DATA = "no data",
  1064. NO_CHROMOSOME = "no chromosome",
  1065. NO_CONVERTER = "no converter",
  1066. NO_TOOL = "no tool",
  1067. DATA = "data",
  1068. ERROR = "error",
  1069. OK = "ok" )
  1070. permitted_actions = get_permitted_actions( filter='DATASET' )
  1071. file_path = "/tmp/"
  1072. object_store = None # This get initialized in mapping.py (method init) by app.py
  1073. engine = None
  1074. def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True, uuid=None ):
  1075. self.id = id
  1076. self.state = state
  1077. self.deleted = False
  1078. self.purged = False
  1079. self.purgable = purgable
  1080. self.external_filename = external_filename
  1081. self._extra_files_path = extra_files_path
  1082. self.file_size = file_size
  1083. if uuid is None:
  1084. self.uuid = uuid4()
  1085. else:
  1086. self.uuid = UUID(str(uuid))
  1087. def get_file_name( self ):
  1088. if not self.external_filename:
  1089. assert self.id is not None, "ID must be set before filename used (commit the object)"
  1090. assert self.object_store is not None, "Object Store has not been initialized for dataset %s" % self.id
  1091. filename = self.object_store.get_filename( self )
  1092. return filename
  1093. else:
  1094. filename = self.external_filename
  1095. # Make filename absolute
  1096. return os.path.abspath( filename )
  1097. def set_file_name ( self, filename ):
  1098. if not filename:
  1099. self.external_filename = None
  1100. else:
  1101. self.external_filename = filename
  1102. file_name = property( get_file_name, set_file_name )
  1103. @property
  1104. def extra_files_path( self ):
  1105. return self.object_store.get_filename( self, dir_only=True, extra_dir=self._extra_files_path or "dataset_%d_files" % self.id )
  1106. def _calculate_size( self ):
  1107. if self.external_filename:
  1108. try:
  1109. return os.path.getsize(self.external_filename)
  1110. except OSError:
  1111. return 0
  1112. else:
  1113. return self.object_store.size(self)
  1114. def get_size( self, nice_size=False ):
  1115. """Returns the size of the data on disk"""
  1116. if self.file_size:
  1117. if nice_size:
  1118. return galaxy.datatypes.data.nice_size( self.file_size )
  1119. else:
  1120. return self.file_size
  1121. else:
  1122. if nice_size:
  1123. return galaxy.datatypes.data.nice_size( self._calculate_size() )
  1124. else:
  1125. return self._calculate_size()
  1126. def set_size( self ):
  1127. """Returns the size of the data on disk"""
  1128. if not self.file_size:
  1129. self.file_size = self._calculate_size()
  1130. def get_total_size( self ):
  1131. if self.total_size is not None:
  1132. return self.total_size
  1133. if self.file_size:
  1134. # for backwards compatibility, set if unset
  1135. self.set_total_size()
  1136. db_session = object_session( self )
  1137. db_session.flush()
  1138. return self.total_size
  1139. return 0
  1140. def set_total_size( self ):
  1141. if self.file_size is None:
  1142. self.set_size()
  1143. self.total_size = self.file_size or 0
  1144. if self.object_store.exists(self, extra_dir=self._extra_files_path or "dataset_%d_files" % self.id, dir_only=True):
  1145. for root, dirs, files in os.walk( self.extra_files_path ):
  1146. self.total_size += sum( [ os.path.getsize( os.path.join( root, file ) ) for file in files if os.path.exists( os.path.join( root, file ) ) ] )
  1147. def has_data( self ):
  1148. """Detects whether there is any data"""
  1149. return self.get_size() > 0
  1150. def mark_deleted( self, include_children=True ):
  1151. self.deleted = True
  1152. def is_multi_byte( self ):
  1153. if not self.has_data():
  1154. return False
  1155. try:
  1156. return is_multi_byte( codecs.open( self.file_name, 'r', 'utf-8' ).read( 100 ) )
  1157. except UnicodeDecodeError:
  1158. return False
  1159. # FIXME: sqlalchemy will replace this
  1160. def _delete(self):
  1161. """Remove the file that corresponds to this data"""
  1162. self.object_store.delete(self)
  1163. @property
  1164. def user_can_purge( self ):
  1165. return self.purged == False \
  1166. and not bool( self.library_associations ) \
  1167. and len( self.history_associations ) == len( self.purged_history_associations )
  1168. def full_delete( self ):
  1169. """Remove the file and extra files, marks deleted and purged"""
  1170. # os.unlink( self.file_name )
  1171. self.object_store.delete(self)
  1172. if self.object_store.exists(self, extra_dir=self._extra_files_path or "dataset_%d_files" % self.id, dir_only=True):
  1173. self.object_store.delete(self, entire_dir=True, extra_dir=self._extra_files_path or "dataset_%d_files" % self.id, dir_only=True)
  1174. # if os.path.exists( self.extra_files_path ):
  1175. # shutil.rmtree( self.extra_files_path )
  1176. # TODO: purge metadata files
  1177. self.deleted = True
  1178. self.purged = True
  1179. def get_access_roles( self, trans ):
  1180. roles = []
  1181. for dp in self.actions:
  1182. if dp.action == trans.app.security_agent.permitted_actions.DATASET_ACCESS.action:
  1183. roles.append( dp.role )
  1184. return roles
  1185. def get_manage_permissions_roles( self, trans ):
  1186. roles = []
  1187. for dp in self.actions:
  1188. if dp.action == trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action:
  1189. roles.append( dp.role )
  1190. return roles
  1191. def has_manage_permissions_roles( self, trans ):
  1192. for dp in self.actions:
  1193. if dp.action == trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action:
  1194. return True
  1195. return False
  1196. class DatasetInstance( object ):
  1197. """A base class for all 'dataset instances', HDAs, LDAs, etc"""
  1198. states = Dataset.states
  1199. conversion_messages = Dataset.conversion_messages
  1200. permitted_actions = Dataset.permitted_actions
  1201. def __init__( self, id=None, hid=None, name=None, info=None, blurb=None, peek=None, tool_version=None, extension=None,
  1202. dbkey=None, metadata=None, history=None, dataset=None, deleted=False, designation=None,
  1203. parent_id=None, validation_errors=None, visible=True, create_dataset=False, sa_session=None, extended_metadata=None ):
  1204. self.name = name or "Unnamed dataset"
  1205. self.id = id
  1206. self.info = info
  1207. self.blurb = blurb
  1208. self.peek = peek
  1209. self.tool_version = tool_version
  1210. self.extension = extension
  1211. self.designation = designation
  1212. self.metadata = metadata or dict()
  1213. self.extended_metadata = extended_metadata
  1214. if dbkey: #dbkey is stored in metadata, only set if non-zero, or else we could clobber one supplied by input 'metadata'
  1215. self.dbkey = dbkey
  1216. self.deleted = deleted
  1217. self.visible = visible
  1218. # Relationships
  1219. if not dataset and create_dataset:
  1220. # Had to pass the sqlalchemy session in order to create a new dataset
  1221. dataset = Dataset( state=Dataset.states.NEW )
  1222. sa_session.add( dataset )
  1223. sa_session.flush()
  1224. self.dataset = dataset
  1225. self.parent_id = parent_id
  1226. self.validation_errors = validation_errors
  1227. @property
  1228. def ext( self ):
  1229. return self.extension
  1230. def get_dataset_state( self ):
  1231. #self._state is currently only used when setting metadata externally
  1232. #leave setting the state as-is, we'll currently handle this specially in the external metadata code
  1233. if self._state:
  1234. return self._state
  1235. return self.dataset.state
  1236. def set_dataset_state ( self, state ):
  1237. self.dataset.state = state
  1238. object_session( self ).add( self.dataset )
  1239. object_session( self ).flush() #flush here, because hda.flush() won't flush the Dataset object
  1240. state = property( get_dataset_state, set_dataset_state )
  1241. def get_file_name( self ):
  1242. return self.dataset.get_file_name()
  1243. def set_file_name (self, filename):
  1244. return self.dataset.set_file_name( filename )
  1245. file_name = property( get_file_name, set_file_name )
  1246. @property
  1247. def extra_files_path( self ):
  1248. return self.dataset.extra_files_path
  1249. @property
  1250. def datatype( self ):
  1251. return datatypes_registry.get_datatype_by_extension( self.extension )
  1252. def get_metadata( self ):
  1253. if not hasattr( self, '_metadata_collection' ) or self._metadata_collection.parent != self: #using weakref to store parent (to prevent circ ref), does a Session.clear() cause parent to be invalidated, while still copying over this non-database attribute?
  1254. self._metadata_collection = MetadataCollection( self )
  1255. return self._metadata_collection
  1256. def set_metadata( self, bunch ):
  1257. # Needs to accept a MetadataCollection, a bunch, or a dict
  1258. self._metadata = self.metadata.make_dict_copy( bunch )
  1259. metadata = property( get_metadata, set_metadata )
  1260. # This provide backwards compatibility with using the old dbkey
  1261. # field in the database. That field now maps to "old_dbkey" (see mapping.py).
  1262. def get_dbkey( self ):
  1263. dbkey = self.metadata.dbkey
  1264. if not isinstance(dbkey, list): dbkey = [dbkey]
  1265. if dbkey in [[None], []]: return "?"
  1266. return dbkey[0]
  1267. def set_dbkey( self, value ):
  1268. if "dbkey" in self.datatype.metadata_spec:
  1269. if not isinstance(value, list):
  1270. self.metadata.dbkey = [value]
  1271. else:
  1272. self.metadata.dbkey = value
  1273. dbkey = property( get_dbkey, set_dbkey )
  1274. def change_datatype( self, new_ext ):
  1275. self.clear_associated_files()
  1276. datatypes_registry.change_datatype( self, new_ext )
  1277. def get_size( self, nice_size=False ):
  1278. """Returns the size of the data on disk"""
  1279. if nice_size:
  1280. return galaxy.datatypes.data.nice_size( self.dataset.get_size() )
  1281. return self.dataset.get_size()
  1282. def set_size( self ):
  1283. """Returns the size of the data on disk"""
  1284. return self.dataset.set_size()
  1285. def get_total_size( self ):
  1286. return self.dataset.get_total_size()
  1287. def set_total_size( self ):
  1288. return self.dataset.set_total_size()
  1289. def has_data( self ):
  1290. """Detects whether there is any data"""
  1291. return self.dataset.has_data()
  1292. def get_raw_data( self ):
  1293. """Returns the full data. To stream it open the file_name and read/write as needed"""
  1294. return self.datatype.get_raw_data( self )
  1295. def write_from_stream( self, stream ):
  1296. """Writes data from a stream"""
  1297. self.datatype.write_from_stream(self, stream)
  1298. def set_raw_data( self, data ):
  1299. """Saves the data on the disc"""
  1300. self.datatype.set_raw_data(self, data)
  1301. def get_mime( self ):
  1302. """Returns the mime type of the data"""
  1303. try:
  1304. return datatypes_registry.get_mimetype_by_extension( self.extension.lower() )
  1305. except AttributeError:
  1306. # extension is None
  1307. return 'data'
  1308. def is_multi_byte( self ):
  1309. """Data consists of multi-byte characters"""
  1310. return self.dataset.is_multi_byte()
  1311. def set_peek( self, is_multi_byte=False ):
  1312. return self.datatype.set_peek( self, is_multi_byte=is_multi_byte )
  1313. def init_meta( self, copy_from=None ):
  1314. return self.datatype.init_meta( self, copy_from=copy_from )
  1315. def set_meta( self, **kwd ):
  1316. self.clear_associated_files( metadata_safe = True )
  1317. return self.datatype.set_meta( self, **kwd )
  1318. def missing_meta( self, **kwd ):
  1319. return self.datatype.missing_meta( self, **kwd )
  1320. def as_display_type( self, type, **kwd ):
  1321. return self.datatype.as_display_type( self, type, **kwd )
  1322. def display_peek( self ):
  1323. return self.datatype.display_peek( self )
  1324. def display_name( self ):
  1325. return self.datatype.display_name( self )
  1326. def display_info( self ):
  1327. return self.datatype.display_info( self )
  1328. def get_converted_files_by_type( self, file_type ):
  1329. for assoc in self.implicitly_converted_datasets:
  1330. if not assoc.deleted and assoc.type == file_type:
  1331. if assoc.dataset:
  1332. return assoc.dataset
  1333. return assoc.dataset_ldda
  1334. return None
  1335. def get_converted_dataset_deps(self, trans, target_ext):
  1336. """
  1337. Returns dict of { "dependency" => HDA }
  1338. """
  1339. # List of string of dependencies
  1340. try:
  1341. depends_list = trans.app.datatypes_registry.converter_deps[self.extension][target_ext]
  1342. except KeyError:
  1343. depends_list = []
  1344. return dict([ (dep, self.get_converted_dataset(trans, dep)) for dep in depends_list ])
  1345. def get_converted_dataset(self, trans, target_ext):
  1346. """
  1347. Return converted dataset(s) if they exist, along with a dict of dependencies.
  1348. If not converted yet, do so and return None (the first time). If unconvertible, raise exception.
  1349. """
  1350. # See if we can convert the dataset
  1351. if target_ext not in self.get_converter_types():
  1352. raise NoConverterException("Conversion from '%s' to '%s' not possible" % (self.extension, target_ext) )
  1353. deps = {}
  1354. # List of string of dependencies
  1355. try:
  1356. depends_list = trans.app.datatypes_registry.converter_deps[self.extension][target_ext]
  1357. except KeyError:
  1358. depends_list = []
  1359. # See if converted dataset already exists, either in metadata in conversions.
  1360. converted_dataset = self.get_metadata_dataset( trans, target_ext )
  1361. if converted_dataset:
  1362. return converted_dataset
  1363. converted_dataset = self.get_converted_files_by_type( target_ext )
  1364. if converted_dataset:
  1365. return converted_dataset
  1366. # Conversion is possible but hasn't been done yet, run converter.
  1367. # Check if we have dependencies
  1368. try:
  1369. for dependency in depends_list:
  1370. dep_dataset = self.get_converted_dataset(trans, dependency)
  1371. if dep_dataset is None:
  1372. # None means converter is running first time
  1373. return None
  1374. elif dep_dataset.state == Job.states.ERROR:
  1375. raise ConverterDependencyException("A dependency (%s) was in an error state." % dependency)
  1376. elif dep_dataset.state != Job.states.OK:
  1377. # Pending
  1378. return None
  1379. deps[dependency] = dep_dataset
  1380. except NoConverterException:
  1381. raise NoConverterException("A dependency (%s) is missing a converter." % dependency)
  1382. except KeyError:
  1383. pass # No deps
  1384. new_dataset = self.datatype.convert_dataset( trans, self, target_ext, return_output=True, visible=False, deps=deps, set_output_history=False ).values()[0]
  1385. new_dataset.name = self.name
  1386. assoc = ImplicitlyConvertedDatasetAssociation( parent=self, file_type=target_ext, dataset=new_dataset, metadata_safe=False )
  1387. session = trans.sa_session
  1388. session.add( new_dataset )
  1389. session.add( assoc )
  1390. session.flush()
  1391. return None
  1392. def get_metadata_dataset( self, trans, dataset_ext ):
  1393. """
  1394. Returns an HDA that points to a metadata file which contains a
  1395. converted data with the requested extension.
  1396. """
  1397. for name, value in self.metadata.items():
  1398. # HACK: MetadataFile objects do not have a type/ext, so need to use metadata name
  1399. # to determine type.
  1400. if dataset_ext == 'bai' and name == 'bam_index' and isinstance( value, MetadataFile ):
  1401. # HACK: MetadataFile objects cannot be used by tools, so return
  1402. # a fake HDA that points to metadata file.
  1403. fake_dataset = Dataset( state=Dataset.states.OK, external_filename=value.file_name )
  1404. fake_hda = HistoryDatasetAssociation( dataset=fake_dataset )
  1405. return fake_hda
  1406. def clear_associated_files( self, metadata_safe = False, purge = False ):
  1407. raise 'Unimplemented'
  1408. def get_child_by_designation(self, designation):
  1409. for child in self.children:
  1410. if child.designation == designation:
  1411. return child
  1412. return None
  1413. def get_converter_types(self):
  1414. return self.datatype.get_converter_types( self, datatypes_registry )
  1415. def can_convert_to(self, format):
  1416. return format in self.get_converter_types()
  1417. def find_conversion_destination( self, accepted_formats, **kwd ):
  1418. """Returns ( target_ext, existing converted dataset )"""
  1419. return self.datatype.find_conversion_destination( self, accepted_formats, datatypes_registry, **kwd )
  1420. def add_validation_error( self, validation_error ):
  1421. self.validation_errors.append( validation_error )
  1422. def extend_validation_errors( self, validation_errors ):
  1423. self.validation_errors.extend(validation_errors)
  1424. def mark_deleted( self, include_children=True ):
  1425. self.deleted = True
  1426. if include_children:
  1427. for child in self.children:
  1428. child.mark_deleted()
  1429. def mark_undeleted( self, include_children=True ):
  1430. self.deleted = False
  1431. if include_children:
  1432. for child in self.children:
  1433. child.mark_undeleted()
  1434. def mark_unhidden( self, include_children=True ):
  1435. self.visible = True
  1436. if include_children:
  1437. for child in self.children:
  1438. child.mark_unhidden()
  1439. def undeletable( self ):
  1440. if self.purged:
  1441. return False
  1442. return True
  1443. @property
  1444. def is_pending( self ):
  1445. """
  1446. Return true if the dataset is neither ready nor in error
  1447. """
  1448. return self.state in ( self.states.NEW, self.states.UPLOAD,
  1449. self.states.QUEUED, self.states.RUNNING,
  1450. self.states.SETTING_METADATA )
  1451. @property
  1452. def source_library_dataset( self ):
  1453. def get_source( dataset ):
  1454. if isinstance( dataset, LibraryDatasetDatasetAssociation ):
  1455. if dataset.library_dataset:
  1456. return ( dataset, dataset.library_dataset )
  1457. if dataset.copied_from_library_dataset_dataset_association:
  1458. source = get_source( dataset.copied_from_library_dataset_dataset_association )
  1459. if source:
  1460. return source
  1461. if dataset.copied_from_history_dataset_association:
  1462. source = get_source( dataset.copied_from_history_dataset_association )
  1463. if source:
  1464. return source
  1465. return ( None, None )
  1466. return get_source( self )
  1467. @property
  1468. def source_dataset_chain( self ):
  1469. def _source_dataset_chain( dataset, lst ):
  1470. try:
  1471. cp_from_ldda = dataset.copied_from_library_dataset_dataset_association
  1472. if cp_from_ldda:
  1473. lst.append( (cp_from_ldda, "(Data Library)") )
  1474. return _source_dataset_chain( cp_from_ldda, lst )
  1475. except Exception, e:
  1476. log.warning( e )
  1477. try:
  1478. cp_from_hda = dataset.copied_from_history_dataset_association
  1479. if cp_from_hda:
  1480. lst.append( (cp_from_hda, cp_from_hda.history.name) )
  1481. return _source_dataset_chain( cp_from_hda, lst )
  1482. except Exception, e:
  1483. log.warning( e )
  1484. return lst
  1485. return _source_dataset_chain( self, [] )
  1486. @property
  1487. def creating_job( self ):
  1488. creating_job_associations = None
  1489. if self.creating_job_associations:
  1490. creating_job_associations = self.creating_job_associations
  1491. else:
  1492. inherit_chain = self.source_dataset_chain
  1493. if inherit_chain:
  1494. creating_job_associations = inherit_chain[-1][0].creating_job_associations
  1495. if creating_job_associations:
  1496. return creating_job_associations[0].job
  1497. return None
  1498. def get_display_applications( self, trans ):
  1499. return self.datatype.get_display_applications_by_dataset( self, trans )
  1500. def get_visualizations( self ):
  1501. return self.datatype.get_visualizations( self )
  1502. def get_datasources( self, trans ):
  1503. """
  1504. Returns datasources for dataset; if datasources are not available
  1505. due to indexing, indexing is started. Return value is a dictionary
  1506. with entries of type
  1507. (<datasource_type> : {<datasource_name>, <indexing_message>}).
  1508. """
  1509. data_sources_dict = {}
  1510. msg = None
  1511. for source_type, source_list in self.datatype.data_sources.iteritems():
  1512. data_source = None
  1513. if source_type == "data_standalone":
  1514. # Nothing to do.
  1515. msg = None
  1516. data_source = source_list
  1517. else:
  1518. # Convert.
  1519. if isinstance( source_list, str ):
  1520. source_list = [ source_list ]
  1521. # Loop through sources until viable one is found.
  1522. for source in source_list:
  1523. msg = self.convert_dataset( trans, source )
  1524. # No message or PENDING means that source is viable. No
  1525. # message indicates conversion was done and is successful.
  1526. if not msg or msg == self.conversion_messages.PENDING:
  1527. data_source = source
  1528. break
  1529. # Store msg.
  1530. data_sources_dict[ source_type ] = { "name": data_source, "message": msg }
  1531. return data_sources_dict
  1532. def convert_dataset( self, trans, target_type ):
  1533. """
  1534. Converts a dataset to the target_type and returns a message indicating
  1535. status of the conversion. None is returned to indicate that dataset
  1536. was converted successfully.
  1537. """
  1538. # Get converted dataset; this will start the conversion if necessary.
  1539. try:
  1540. converted_dataset = self.get_converted_dataset( trans, target_type )
  1541. except NoConverterException:
  1542. return self.conversion_messages.NO_CONVERTER
  1543. except ConverterDependencyException, dep_error:
  1544. return { 'kind': self.conversion_messages.ERROR, 'message': dep_error.value }
  1545. # Check dataset state and return any messages.
  1546. msg = None
  1547. if converted_dataset and converted_dataset.state == Dataset.states.ERROR:
  1548. job_id = trans.sa_session.query( JobToOutputDatasetAssociation ) \
  1549. .filter_by( dataset_id=converted_dataset.id ).first().job_id
  1550. job = trans.sa_session.query( Job ).get( job_id )
  1551. msg = { 'kind': self.conversion_messages.ERROR, 'message': job.stderr }
  1552. elif not converted_dataset or converted_dataset.state != Dataset.states.OK:
  1553. msg = self.conversion_messages.PENDING
  1554. return msg
  1555. class HistoryDatasetAssociation( DatasetInstance, Dictifiable, UsesAnnotations, HasName ):
  1556. """
  1557. Resource class that creates a relation between a dataset and a user history.
  1558. """
  1559. def __init__( self,
  1560. hid = None,
  1561. history = None,
  1562. copied_from_history_dataset_association = None,
  1563. copied_from_library_dataset_dataset_association = None,
  1564. sa_session = None,
  1565. **kwd ):
  1566. """
  1567. Create a a new HDA and associate it with the given history.
  1568. """
  1569. # FIXME: sa_session is must be passed to DataSetInstance if the create_dataset
  1570. # parameter is True so that the new object can be flushed. Is there a better way?
  1571. DatasetInstance.__init__( self, sa_session=sa_session, **kwd )
  1572. self.hid = hid
  1573. # Relationships
  1574. self.history = history
  1575. self.copied_from_history_dataset_association = copied_from_history_dataset_association
  1576. self.copied_from_library_dataset_dataset_association = copied_from_library_dataset_dataset_association
  1577. def copy( self, copy_children = False, parent_id = None ):
  1578. """
  1579. Create a copy of this HDA.
  1580. """
  1581. hda = HistoryDatasetAssociation( hid=self.hid,
  1582. name=self.name,
  1583. info=self.info,
  1584. blurb=self.blurb,
  1585. peek=self.peek,
  1586. tool_version=self.tool_version,
  1587. extension=self.extension,
  1588. dbkey=self.dbkey,
  1589. dataset = self.dataset,
  1590. visible=self.visible,
  1591. deleted=self.deleted,
  1592. parent_id=parent_id,
  1593. copied_from_history_dataset_association=self )
  1594. # update init non-keywords as well
  1595. hda.purged = self.purged
  1596. object_session( self ).add( hda )
  1597. object_session( self ).flush()
  1598. hda.set_size()
  1599. # Need to set after flushed, as MetadataFiles require dataset.id
  1600. hda.metadata = self.metadata
  1601. if copy_children:
  1602. for child in self.children:
  1603. child.copy( copy_children = copy_children, parent_id = hda.id )
  1604. if not self.datatype.copy_safe_peek:
  1605. # In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
  1606. hda.set_peek()
  1607. object_session( self ).flush()
  1608. return hda
  1609. def to_library_dataset_dataset_association( self, trans, target_folder,
  1610. replace_dataset=None, parent_id=None, user=None, roles=None, ldda_message='' ):
  1611. """
  1612. Copy this HDA to a library optionally replacing an existing LDDA.
  1613. """
  1614. if replace_dataset:
  1615. # The replace_dataset param ( when not None ) refers to a LibraryDataset that
  1616. # is being replaced with a new version.
  1617. library_dataset = replace_dataset
  1618. else:
  1619. # If replace_dataset is None, the Library level permissions will be taken from the folder and
  1620. # applied to the new LibraryDataset, and the current user's DefaultUserPermissions will be applied
  1621. # to the associated Dataset.
  1622. library_dataset = LibraryDataset( folder=target_folder, name=self.name, info=self.info )
  1623. object_session( self ).add( library_dataset )
  1624. object_session( self ).flush()
  1625. if not user:
  1626. # This should never happen since users must be authenticated to upload to a data library
  1627. user = self.history.user
  1628. ldda = LibraryDatasetDatasetAssociation( name=self.name,
  1629. info=self.info,
  1630. blurb=self.blurb,
  1631. peek=self.peek,
  1632. tool_version=self.tool_version,
  1633. extension=self.extension,
  1634. dbkey=self.dbkey,
  1635. dataset=self.dataset,
  1636. library_dataset=library_dataset,
  1637. visible=self.visible,
  1638. deleted=self.deleted,
  1639. parent_id=parent_id,
  1640. copied_from_history_dataset_association=self,
  1641. user=user )
  1642. object_session( self ).add( ldda )
  1643. object_session( self ).flush()
  1644. # If roles were selected on the upload form, restrict access to the Dataset to those roles
  1645. roles = roles or []
  1646. for role in roles:
  1647. dp = trans.model.DatasetPermissions( trans.app.security_agent.permitted_actions.DATASET_ACCESS.action,
  1648. ldda.dataset, role )
  1649. trans.sa_session.add( dp )
  1650. trans.sa_session.flush()
  1651. # Must set metadata after ldda flushed, as MetadataFiles require ldda.id
  1652. ldda.metadata = self.metadata
  1653. if ldda_message:
  1654. ldda.message = ldda_message
  1655. if not replace_dataset:
  1656. target_folder.add_library_dataset( library_dataset, genome_build=ldda.dbkey )
  1657. object_session( self ).add( target_folder )
  1658. object_session( self ).flush()
  1659. library_dataset.library_dataset_dataset_association_id = ldda.id
  1660. object_session( self ).add( library_dataset )
  1661. object_session( self ).flush()
  1662. for child in self.children:
  1663. child.to_library_dataset_dataset_association( trans,
  1664. target_folder=target_folder,
  1665. replace_dataset=replace_dataset,
  1666. parent_id=ldda.id,
  1667. user=ldda.user )
  1668. if not self.datatype.copy_safe_peek:
  1669. # In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
  1670. ldda.set_peek()
  1671. object_session( self ).flush()
  1672. return ldda
  1673. def clear_associated_files( self, metadata_safe = False, purge = False ):
  1674. """
  1675. """
  1676. # metadata_safe = True means to only clear when assoc.metadata_safe == False
  1677. for assoc in self.implicitly_converted_datasets:
  1678. if not assoc.deleted and ( not metadata_safe or not assoc.metadata_safe ):
  1679. assoc.clear( purge = purge )
  1680. for assoc in self.implicitly_converted_parent_datasets:
  1681. assoc.clear( purge = purge, delete_dataset = False )
  1682. def get_access_roles( self, trans ):
  1683. """
  1684. Return The access roles associated with this HDA's dataset.
  1685. """
  1686. return self.dataset.get_access_roles( trans )
  1687. def quota_amount( self, user ):
  1688. """
  1689. Return the disk space used for this HDA relevant to user quotas.
  1690. If the user has multiple instances of this dataset, it will not affect their
  1691. disk usage statistic.
  1692. """
  1693. rval = 0
  1694. # Anon users are handled just by their single history size.
  1695. if not user:
  1696. return rval
  1697. # Gets an HDA and its children's disk usage, if the user does not already
  1698. # have an association of the same dataset
  1699. if not self.dataset.library_associations and not self.purged and not self.dataset.purged:
  1700. for hda in self.dataset.history_associations:
  1701. if hda.id == self.id:
  1702. continue
  1703. if not hda.purged and hda.history and hda.history.user and hda.history.user == user:
  1704. break
  1705. else:
  1706. rval += self.get_total_size()
  1707. for child in self.children:
  1708. rval += child.get_disk_usage( user )
  1709. return rval
  1710. def to_dict( self, view='collection', expose_dataset_path=False ):
  1711. """
  1712. Return attributes of this HDA that are exposed using the API.
  1713. """
  1714. # Since this class is a proxy to rather complex attributes we want to
  1715. # display in other objects, we can't use the simpler method used by
  1716. # other model classes.
  1717. hda = self
  1718. rval = dict( id = hda.id,
  1719. hda_ldda = 'hda',
  1720. uuid = ( lambda uuid: str( uuid ) if uuid else None )( hda.dataset.uuid ),
  1721. hid = hda.hid,
  1722. file_ext = hda.ext,
  1723. peek = ( lambda hda: hda.display_peek() if hda.peek and hda.peek != 'no peek' else None )( hda ),
  1724. model_class = self.__class__.__name__,
  1725. name = hda.name,
  1726. deleted = hda.deleted,
  1727. purged = hda.purged,
  1728. visible = hda.visible,
  1729. state = hda.state,
  1730. file_size = int( hda.get_size() ),
  1731. update_time = hda.update_time.isoformat(),
  1732. data_type = hda.ext,
  1733. genome_build = hda.dbkey,
  1734. misc_info = hda.info.strip() if isinstance( hda.info, basestring ) else hda.info,
  1735. misc_blurb = hda.blurb )
  1736. # add tags string list
  1737. tags_str_list = []
  1738. for tag in self.tags:
  1739. tag_str = tag.user_tname
  1740. if tag.value is not None:
  1741. tag_str += ":" + tag.user_value
  1742. tags_str_list.append( tag_str )
  1743. rval[ 'tags' ] = tags_str_list
  1744. if hda.copied_from_library_dataset_dataset_association is not None:
  1745. rval['copied_from_ldda_id'] = hda.copied_from_library_dataset_dataset_association.id
  1746. if hda.history is not None:
  1747. rval['history_id'] = hda.history.id
  1748. if hda.extended_metadata is not None:
  1749. rval['extended_metadata'] = hda.extended_metadata.data
  1750. rval[ 'peek' ] = to_unicode( hda.display_peek() )
  1751. for name, spec in hda.metadata.spec.items():
  1752. val = hda.metadata.get( name )
  1753. if isinstance( val, MetadataFile ):
  1754. # only when explicitly set: fetching filepaths can be expensive
  1755. if not expose_dataset_path:
  1756. continue
  1757. val = val.file_name
  1758. # If no value for metadata, look in datatype for metadata.
  1759. elif val == None and hasattr( hda.datatype, name ):
  1760. val = getattr( hda.datatype, name )
  1761. rval['metadata_' + name] = val
  1762. return rval
  1763. def set_from_dict( self, new_data ):
  1764. #AKA: set_api_value
  1765. """
  1766. Set object attributes to the values in dictionary new_data limiting
  1767. to only the following keys: name, deleted, visible, genome_build,
  1768. info, and blurb.
  1769. Returns a dictionary of the keys, values that have been changed.
  1770. """
  1771. # precondition: keys are proper, values are parsed and validated
  1772. #NOTE!: does not handle metadata
  1773. editable_keys = ( 'name', 'deleted', 'visible', 'dbkey', 'info', 'blurb' )
  1774. changed = {}
  1775. # unknown keys are ignored here
  1776. for key in [ k for k in new_data.keys() if k in editable_keys ]:
  1777. new_val = new_data[ key ]
  1778. old_val = self.__getattribute__( key )
  1779. if new_val == old_val:
  1780. continue
  1781. # special cases here
  1782. if key == 'deleted' and new_val is False and self.purged:
  1783. raise Exception( 'Cannot undelete a purged dataset' )
  1784. self.__setattr__( key, new_val )
  1785. changed[ key ] = new_val
  1786. return changed
  1787. class HistoryDatasetAssociationDisplayAtAuthorization( object ):
  1788. def __init__( self, hda=None, user=None, site=None ):
  1789. self.history_dataset_association = hda
  1790. self.user = user
  1791. self.site = site
  1792. class HistoryDatasetAssociationSubset( object ):
  1793. def __init__(self, hda, subset, location):
  1794. self.hda = hda
  1795. self.subset = subset
  1796. self.location = location
  1797. class Library( object, Dictifiable, HasName ):
  1798. permitted_actions = get_permitted_actions( filter='LIBRARY' )
  1799. dict_collection_visible_keys = ( 'id', 'name' )
  1800. dict_element_visible_keys = ( 'id', 'deleted', 'name', 'description', 'synopsis', 'root_folder_id' )
  1801. def __init__( self, name=None, description=None, synopsis=None, root_folder=None ):
  1802. self.name = name or "Unnamed library"
  1803. self.description = description
  1804. self.synopsis = synopsis
  1805. self.root_folder = root_folder
  1806. def to_dict( self, view='collection', value_mapper=None ):
  1807. """
  1808. We prepend an F to folders.
  1809. """
  1810. rval = super( Library, self ).to_dict( view=view, value_mapper=value_mapper )
  1811. if 'root_folder_id' in rval:
  1812. rval[ 'root_folder_id' ] = 'F' + rval[ 'root_folder_id' ]
  1813. return rval
  1814. def get_active_folders( self, folder, folders=None ):
  1815. # TODO: should we make sure the library is not deleted?
  1816. def sort_by_attr( seq, attr ):
  1817. """
  1818. Sort the sequence of objects by object's attribute
  1819. Arguments:
  1820. seq - the list or any sequence (including immutable one) of objects to sort.
  1821. attr - the name of attribute to sort by
  1822. """
  1823. # Use the "Schwartzian transform"
  1824. # Create the auxiliary list of tuples where every i-th tuple has form
  1825. # (seq[i].attr, i, seq[i]) and sort it. The second item of tuple is needed not
  1826. # only to provide stable sorting, but mainly to eliminate comparison of objects
  1827. # (which can be expensive or prohibited) in case of equal attribute values.
  1828. intermed = map( None, map( getattr, seq, ( attr, ) * len( seq ) ), xrange( len( seq ) ), seq )
  1829. intermed.sort()
  1830. return map( operator.getitem, intermed, ( -1, ) * len( intermed ) )
  1831. if folders is None:
  1832. active_folders = [ folder ]
  1833. for active_folder in folder.active_folders:
  1834. active_folders.extend( self.get_active_folders( active_folder, folders ) )
  1835. return sort_by_attr( active_folders, 'id' )
  1836. def get_info_association( self, restrict=False, inherited=False ):
  1837. if self.info_association:
  1838. if not inherited or self.info_association[0].inheritable:
  1839. return self.info_association[0], inherited
  1840. else:
  1841. return None, inherited
  1842. return None, inherited
  1843. def get_template_widgets( self, trans, get_contents=True ):
  1844. # See if we have any associated templates - the returned value for
  1845. # inherited is not applicable at the library level. The get_contents
  1846. # param is passed by callers that are inheriting a template - these
  1847. # are usually new library datsets for which we want to include template
  1848. # fields on the upload form, but not necessarily the contents of the
  1849. # inherited template saved for the parent.
  1850. info_association, inherited = self.get_info_association()
  1851. if info_association:
  1852. template = info_association.template
  1853. if get_contents:
  1854. # See if we have any field contents
  1855. info = info_association.info
  1856. if info:
  1857. return template.get_widgets( trans.user, contents=info.content )
  1858. return template.get_widgets( trans.user )
  1859. return []
  1860. def get_access_roles( self, trans ):
  1861. roles = []
  1862. for lp in self.actions:
  1863. if lp.action == trans.app.security_agent.permitted_actions.LIBRARY_ACCESS.action:
  1864. roles.append( lp.role )
  1865. return roles
  1866. class LibraryFolder( object, Dictifiable, HasName ):
  1867. dict_element_visible_keys = ( 'id', 'parent_id', 'name', 'description', 'item_count', 'genome_build', 'update_time' )
  1868. def __init__( self, name=None, description=None, item_count=0, order_id=None ):
  1869. self.name = name or "Unnamed folder"
  1870. self.description = description
  1871. self.item_count = item_count
  1872. self.order_id = order_id
  1873. self.genome_build = None
  1874. def add_library_dataset( self, library_dataset, genome_build=None ):
  1875. library_dataset.folder_id = self.id
  1876. library_dataset.order_id = self.item_count
  1877. self.item_count += 1
  1878. if genome_build not in [None, '?']:
  1879. self.genome_build = genome_build
  1880. def add_folder( self, folder ):
  1881. folder.parent_id = self.id
  1882. folder.order_id = self.item_count
  1883. self.item_count += 1
  1884. def get_info_association( self, restrict=False, inherited=False ):
  1885. # If restrict is True, we will return this folder's info_association, not inheriting.
  1886. # If restrict is False, we'll return the next available info_association in the
  1887. # inheritable hierarchy if it is "inheritable". True is also returned if the
  1888. # info_association was inherited and False if not. This enables us to eliminate
  1889. # displaying any contents of the inherited template.
  1890. if self.info_association:
  1891. if not inherited or self.info_association[0].inheritable:
  1892. return self.info_association[0], inherited
  1893. else:
  1894. return None, inherited
  1895. if restrict:
  1896. return None, inherited
  1897. if self.parent:
  1898. return self.parent.get_info_association( inherited=True )
  1899. if self.library_root:
  1900. return self.library_root[0].get_info_association( inherited=True )
  1901. return None, inherited
  1902. def get_template_widgets( self, trans, get_contents=True ):
  1903. # See if we have any associated templates. The get_contents
  1904. # param is passed by callers that are inheriting a template - these
  1905. # are usually new library datsets for which we want to include template
  1906. # fields on the upload form.
  1907. info_association, inherited = self.get_info_association()
  1908. if info_association:
  1909. if inherited:
  1910. template = info_association.template.current.latest_form
  1911. else:
  1912. template = info_association.template
  1913. # See if we have any field contents, but only if the info_association was
  1914. # not inherited ( we do not want to display the inherited contents ).
  1915. # (gvk: 8/30/10) Based on conversations with Dan, we agreed to ALWAYS inherit
  1916. # contents. We'll use this behavior until we hear from the community that
  1917. # contents should not be inherited. If we don't hear anything for a while,
  1918. # eliminate the old commented out behavior.
  1919. #if not inherited and get_contents:
  1920. if get_contents:
  1921. info = info_association.info
  1922. if info:
  1923. return template.get_widgets( trans.user, info.content )
  1924. else:
  1925. return template.get_widgets( trans.user )
  1926. return []
  1927. @property
  1928. def activatable_library_datasets( self ):
  1929. # This needs to be a list
  1930. return [ ld for ld in self.datasets if ld.library_dataset_dataset_association and not ld.library_dataset_dataset_association.dataset.deleted ]
  1931. def to_dict( self, view='collection', value_mapper=None ):
  1932. rval = super( LibraryFolder, self ).to_dict( view=view, value_mapper=value_mapper )
  1933. info_association, inherited = self.get_info_association()
  1934. if info_association:
  1935. if inherited:
  1936. template = info_association.template.current.latest_form
  1937. else:
  1938. template = info_association.template
  1939. rval['data_template'] = template.name
  1940. rval['library_path'] = self.library_path
  1941. rval['parent_library_id'] = self.parent_library.id
  1942. return rval
  1943. @property
  1944. def library_path(self):
  1945. l_path = []
  1946. f = self
  1947. while f.parent:
  1948. l_path.insert(0, f.name)
  1949. f = f.parent
  1950. return l_path
  1951. @property
  1952. def parent_library( self ):
  1953. f = self
  1954. while f.parent:
  1955. f = f.parent
  1956. return f.library_root[0]
  1957. class LibraryDataset( object ):
  1958. # This class acts as a proxy to the currently selected LDDA
  1959. upload_options = [ ( 'upload_file', 'Upload files' ),
  1960. ( 'upload_directory', 'Upload directory of files' ),
  1961. ( 'upload_paths', 'Upload files from filesystem paths' ),
  1962. ( 'import_from_history', 'Import datasets from your current history' ) ]
  1963. def __init__( self, folder=None, order_id=None, name=None, info=None, library_dataset_dataset_association=None, **kwd ):
  1964. self.folder = folder
  1965. self.order_id = order_id
  1966. self.name = name
  1967. self.info = info
  1968. self.library_dataset_dataset_association = library_dataset_dataset_association
  1969. def set_library_dataset_dataset_association( self, ldda ):
  1970. self.library_dataset_dataset_association = ldda
  1971. ldda.library_dataset = self
  1972. object_session( self ).add_all( ( ldda, self ) )
  1973. object_session( self ).flush()
  1974. def get_info( self ):
  1975. if self.library_dataset_dataset_association:
  1976. return self.library_dataset_dataset_association.info
  1977. elif self._info:
  1978. return self._info
  1979. else:
  1980. return 'no info'
  1981. def set_info( self, info ):
  1982. self._info = info
  1983. info = property( get_info, set_info )
  1984. def get_name( self ):
  1985. if self.library_dataset_dataset_association:
  1986. return self.library_dataset_dataset_association.name
  1987. elif self._name:
  1988. return self._name
  1989. else:
  1990. return 'Unnamed dataset'
  1991. def set_name( self, name ):
  1992. self._name = name
  1993. name = property( get_name, set_name )
  1994. def display_name( self ):
  1995. self.library_dataset_dataset_association.display_name()
  1996. def to_dict( self, view='collection' ):
  1997. # Since this class is a proxy to rather complex attributes we want to
  1998. # display in other objects, we can't use the simpler method used by
  1999. # other model classes.
  2000. ldda = self.library_dataset_dataset_association
  2001. template_data = {}
  2002. for temp_info in ldda.info_association:
  2003. template = temp_info.template
  2004. content = temp_info.info.content
  2005. tmp_dict = {}
  2006. for field in template.fields:
  2007. tmp_dict[field['label']] = content[field['name']]
  2008. template_data[template.name] = tmp_dict
  2009. rval = dict( id = self.id,
  2010. ldda_id = ldda.id,
  2011. parent_library_id = self.folder.parent_library.id,
  2012. folder_id = self.folder_id,
  2013. model_class = self.__class__.__name__,
  2014. state = ldda.state,
  2015. name = ldda.name,
  2016. file_name = ldda.file_name,
  2017. uploaded_by = ldda.user.email,
  2018. message = ldda.message,
  2019. date_uploaded = ldda.create_time.isoformat(),
  2020. file_size = int( ldda.get_size() ),
  2021. data_type = ldda.ext,
  2022. genome_build = ldda.dbkey,
  2023. misc_info = ldda.info,
  2024. misc_blurb = ldda.blurb,
  2025. peek = ( lambda ldda: ldda.display_peek() if ldda.peek and ldda.peek != 'no peek' else None )( ldda ),
  2026. template_data = template_data )
  2027. if ldda.dataset.uuid is None:
  2028. rval['uuid'] = None
  2029. else:
  2030. rval['uuid'] = str(ldda.dataset.uuid)
  2031. for name, spec in ldda.metadata.spec.items():
  2032. val = ldda.metadata.get( name )
  2033. if isinstance( val, MetadataFile ):
  2034. val = val.file_name
  2035. elif isinstance( val, list ):
  2036. val = ', '.join( [str(v) for v in val] )
  2037. rval['metadata_' + name] = val
  2038. return rval
  2039. class LibraryDatasetDatasetAssociation( DatasetInstance, HasName ):
  2040. def __init__( self,
  2041. copied_from_history_dataset_association=None,
  2042. copied_from_library_dataset_dataset_association=None,
  2043. library_dataset=None,
  2044. user=None,
  2045. sa_session=None,
  2046. **kwd ):
  2047. # FIXME: sa_session is must be passed to DataSetInstance if the create_dataset
  2048. # parameter in kwd is True so that the new object can be flushed. Is there a better way?
  2049. DatasetInstance.__init__( self, sa_session=sa_session, **kwd )
  2050. if copied_from_history_dataset_association:
  2051. self.copied_from_history_dataset_association_id = copied_from_history_dataset_association.id
  2052. if copied_from_library_dataset_dataset_association:
  2053. self.copied_from_library_dataset_dataset_association_id = copied_from_library_dataset_dataset_association.id
  2054. self.library_dataset = library_dataset
  2055. self.user = user
  2056. def to_history_dataset_association( self, target_history, parent_id = None, add_to_history = False ):
  2057. hda = HistoryDatasetAssociation( name=self.name,
  2058. info=self.info,
  2059. blurb=self.blurb,
  2060. peek=self.peek,
  2061. tool_version=self.tool_version,
  2062. extension=self.extension,
  2063. dbkey=self.dbkey,
  2064. dataset=self.dataset,
  2065. visible=self.visible,
  2066. deleted=self.deleted,
  2067. parent_id=parent_id,
  2068. copied_from_library_dataset_dataset_association=self,
  2069. history=target_history )
  2070. object_session( self ).add( hda )
  2071. object_session( self ).flush()
  2072. hda.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id
  2073. if add_to_history and target_history:
  2074. target_history.add_dataset( hda )
  2075. for child in self.children:
  2076. child.to_history_dataset_association( target_history = target_history, parent_id = hda.id, add_to_history = False )
  2077. if not self.datatype.copy_safe_peek:
  2078. hda.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
  2079. object_session( self ).flush()
  2080. return hda
  2081. def copy( self, copy_children = False, parent_id = None, target_folder = None ):
  2082. ldda = LibraryDatasetDatasetAssociation( name=self.name,
  2083. info=self.info,
  2084. blurb=self.blurb,
  2085. peek=self.peek,
  2086. tool_version=self.tool_version,
  2087. extension=self.extension,
  2088. dbkey=self.dbkey,
  2089. dataset=self.dataset,
  2090. visible=self.visible,
  2091. deleted=self.deleted,
  2092. parent_id=parent_id,
  2093. copied_from_library_dataset_dataset_association=self,
  2094. folder=target_folder )
  2095. object_session( self ).add( ldda )
  2096. object_session( self ).flush()
  2097. # Need to set after flushed, as MetadataFiles require dataset.id
  2098. ldda.metadata = self.metadata
  2099. if copy_children:
  2100. for child in self.children:
  2101. child.copy( copy_children = copy_children, parent_id = ldda.id )
  2102. if not self.datatype.copy_safe_peek:
  2103. # In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
  2104. ldda.set_peek()
  2105. object_session( self ).flush()
  2106. return ldda
  2107. def clear_associated_files( self, metadata_safe = False, purge = False ):
  2108. return
  2109. def get_access_roles( self, trans ):
  2110. return self.dataset.get_access_roles( trans )
  2111. def get_manage_permissions_roles( self, trans ):
  2112. return self.dataset.get_manage_permissions_roles( trans )
  2113. def has_manage_permissions_roles( self, trans ):
  2114. return self.dataset.has_manage_permissions_roles( trans )
  2115. def get_info_association( self, restrict=False, inherited=False ):
  2116. # If restrict is True, we will return this ldda's info_association whether it
  2117. # exists or not ( in which case None will be returned ). If restrict is False,
  2118. # we'll return the next available info_association in the inheritable hierarchy.
  2119. # True is also returned if the info_association was inherited, and False if not.
  2120. # This enables us to eliminate displaying any contents of the inherited template.
  2121. # SM: Accessing self.info_association can cause a query to be emitted
  2122. if self.info_association:
  2123. return self.info_association[0], inherited
  2124. if restrict:
  2125. return None, inherited
  2126. return self.library_dataset.folder.get_info_association( inherited=True )
  2127. def to_dict( self, view='collection' ):
  2128. # Since this class is a proxy to rather complex attributes we want to
  2129. # display in other objects, we can't use the simpler method used by
  2130. # other model classes.
  2131. ldda = self
  2132. try:
  2133. file_size = int( ldda.get_size() )
  2134. except OSError:
  2135. file_size = 0
  2136. rval = dict( id = ldda.id,
  2137. hda_ldda = 'ldda',
  2138. model_class = self.__class__.__name__,
  2139. name = ldda.name,
  2140. deleted = ldda.deleted,
  2141. visible = ldda.visible,
  2142. state = ldda.state,
  2143. library_dataset_id = ldda.library_dataset_id,
  2144. file_size = file_size,
  2145. file_name = ldda.file_name,
  2146. update_time = ldda.update_time.isoformat(),
  2147. data_type = ldda.ext,
  2148. genome_build = ldda.dbkey,
  2149. misc_info = ldda.info,
  2150. misc_blurb = ldda.blurb )
  2151. if ldda.dataset.uuid is None:
  2152. rval['uuid'] = None
  2153. else:
  2154. rval['uuid'] = str(ldda.dataset.uuid)
  2155. rval['parent_library_id'] = ldda.library_dataset.folder.parent_library.id
  2156. if ldda.extended_metadata is not None:
  2157. rval['extended_metadata'] = ldda.extended_metadata.data
  2158. for name, spec in ldda.metadata.spec.items():
  2159. val = ldda.metadata.get( name )
  2160. if isinstance( val, MetadataFile ):
  2161. val = val.file_name
  2162. # If no value for metadata, look in datatype for metadata.
  2163. elif val == None and hasattr( ldda.datatype, name ):
  2164. val = getattr( ldda.datatype, name )
  2165. rval['metadata_' + name] = val
  2166. return rval
  2167. def get_template_widgets( self, trans, get_contents=True ):
  2168. # See if we have any associated templatesThe get_contents
  2169. # param is passed by callers that are inheriting a template - these
  2170. # are usually new library datsets for which we want to include template
  2171. # fields on the upload form, but not necessarily the contents of the
  2172. # inherited template saved for the parent.
  2173. info_association, inherited = self.get_info_association()
  2174. if info_association:
  2175. if inherited:
  2176. template = info_association.template.current.latest_form
  2177. else:
  2178. template = info_association.template
  2179. # See if we have any field contents, but only if the info_association was
  2180. # not inherited ( we do not want to display the inherited contents ).
  2181. # (gvk: 8/30/10) Based on conversations with Dan, we agreed to ALWAYS inherit
  2182. # contents. We'll use this behavior until we hear from the community that
  2183. # contents should not be inherited. If we don't hear anything for a while,
  2184. # eliminate the old commented out behavior.
  2185. #if not inherited and get_contents:
  2186. if get_contents:
  2187. info = info_association.info
  2188. if info:
  2189. return template.get_widgets( trans.user, info.content )
  2190. else:
  2191. return template.get_widgets( trans.user )
  2192. return []
  2193. def templates_dict( self, use_name=False ):
  2194. """
  2195. Returns a dict of template info
  2196. """
  2197. #TODO: Should have a method that allows names and labels to be returned together in a structured way
  2198. template_data = {}
  2199. for temp_info in self.info_association:
  2200. template = temp_info.template
  2201. content = temp_info.info.content
  2202. tmp_dict = {}
  2203. for field in template.fields:
  2204. if use_name:
  2205. name = field[ 'name' ]
  2206. else:
  2207. name = field[ 'label' ]
  2208. tmp_dict[ name ] = content.get( field[ 'name' ] )
  2209. template_data[template.name] = tmp_dict
  2210. return template_data
  2211. def templates_json( self, use_name=False ):
  2212. return json.dumps( self.templates_dict( use_name=use_name ) )
  2213. class ExtendedMetadata( object ):
  2214. def __init__(self, data):
  2215. self.data = data
  2216. class ExtendedMetadataIndex( object ):
  2217. def __init__( self, extended_metadata, path, value):
  2218. self.extended_metadata = extended_metadata
  2219. self.path = path
  2220. self.value = value
  2221. class LibraryInfoAssociation( object ):
  2222. def __init__( self, library, form_definition, info, inheritable=False ):
  2223. self.library = library
  2224. self.template = form_definition
  2225. self.info = info
  2226. self.inheritable = inheritable
  2227. class LibraryFolderInfoAssociation( object ):
  2228. def __init__( self, folder, form_definition, info, inheritable=False ):
  2229. self.folder = folder
  2230. self.template = form_definition
  2231. self.info = info
  2232. self.inheritable = inheritable
  2233. class LibraryDatasetDatasetInfoAssociation( object ):
  2234. def __init__( self, library_dataset_dataset_association, form_definition, info ):
  2235. # TODO: need to figure out if this should be inheritable to the associated LibraryDataset
  2236. self.library_dataset_dataset_association = library_dataset_dataset_association
  2237. self.template = form_definition
  2238. self.info = info
  2239. @property
  2240. def inheritable( self ):
  2241. return True #always allow inheriting, used for replacement
  2242. class ValidationError( object ):
  2243. def __init__( self, message=None, err_type=None, attributes=None ):
  2244. self.message = message
  2245. self.err_type = err_type
  2246. self.attributes = attributes
  2247. class DatasetToValidationErrorAssociation( object ):
  2248. def __init__( self, dataset, validation_error ):
  2249. self.dataset = dataset
  2250. self.validation_error = validation_error
  2251. class ImplicitlyConvertedDatasetAssociation( object ):
  2252. def __init__( self, id = None, parent = None, dataset = None, file_type = None, deleted = False, purged = False, metadata_safe = True ):
  2253. self.id = id
  2254. if isinstance(dataset, HistoryDatasetAssociation):
  2255. self.dataset = dataset
  2256. elif isinstance(dataset, LibraryDatasetDatasetAssociation):
  2257. self.dataset_ldda = dataset
  2258. else:
  2259. raise AttributeError, 'Unknown dataset type provided for dataset: %s' % type( dataset )
  2260. if isinstance(parent, HistoryDatasetAssociation):
  2261. self.parent_hda = parent
  2262. elif isinstance(parent, LibraryDatasetDatasetAssociation):
  2263. self.parent_ldda = parent
  2264. else:
  2265. raise AttributeError, 'Unknown dataset type provided for parent: %s' % type( parent )
  2266. self.type = file_type
  2267. self.deleted = deleted
  2268. self.purged = purged
  2269. self.metadata_safe = metadata_safe
  2270. def clear( self, purge = False, delete_dataset = True ):
  2271. self.deleted = True
  2272. if self.dataset:
  2273. if delete_dataset:
  2274. self.dataset.deleted = True
  2275. if purge:
  2276. self.dataset.purged = True
  2277. if purge and self.dataset.deleted: #do something with purging
  2278. self.purged = True
  2279. try: os.unlink( self.file_name )
  2280. except Exception, e: print "Failed to purge associated file (%s) from disk: %s" % ( self.file_name, e )
  2281. class Event( object ):
  2282. def __init__( self, message=None, history=None, user=None, galaxy_session=None ):
  2283. self.history = history
  2284. self.galaxy_session = galaxy_session
  2285. self.user = user
  2286. self.tool_id = None
  2287. self.message = message
  2288. class GalaxySession( object ):
  2289. def __init__( self,
  2290. id=None,
  2291. user=None,
  2292. remote_host=None,
  2293. remote_addr=None,
  2294. referer=None,
  2295. current_history=None,
  2296. session_key=None,
  2297. is_valid=False,
  2298. prev_session_id=None ):
  2299. self.id = id
  2300. self.user = user
  2301. self.remote_host = remote_host
  2302. self.remote_addr = remote_addr
  2303. self.referer = referer
  2304. self.current_history = current_history
  2305. self.session_key = session_key
  2306. self.is_valid = is_valid
  2307. self.prev_session_id = prev_session_id
  2308. self.histories = []
  2309. def add_history( self, history, association=None ):
  2310. if association is None:
  2311. self.histories.append( GalaxySessionToHistoryAssociation( self, history ) )
  2312. else:
  2313. self.histories.append( association )
  2314. def get_disk_usage( self ):
  2315. if self.disk_usage is None:
  2316. return 0
  2317. return self.disk_usage
  2318. def set_disk_usage( self, bytes ):
  2319. self.disk_usage = bytes
  2320. total_disk_usage = property( get_disk_usage, set_disk_usage )
  2321. class GalaxySessionToHistoryAssociation( object ):
  2322. def __init__( self, galaxy_session, history ):
  2323. self.galaxy_session = galaxy_session
  2324. self.history = history
  2325. class UCI( object ):
  2326. def __init__( self ):
  2327. self.id = None
  2328. self.user = None
  2329. class StoredWorkflow( object, Dictifiable):
  2330. dict_collection_visible_keys = ( 'id', 'name', 'published', 'deleted' )
  2331. dict_element_visible_keys = ( 'id', 'name', 'published', 'deleted' )
  2332. def __init__( self ):
  2333. self.id = None
  2334. self.user = None
  2335. self.name = None
  2336. self.slug = None
  2337. self.published = False
  2338. self.latest_workflow_id = None
  2339. self.workflows = []
  2340. def copy_tags_from(self,target_user,source_workflow):
  2341. for src_swta in source_workflow.owner_tags:
  2342. new_swta = src_swta.copy()
  2343. new_swta.user = target_user
  2344. self.tags.append(new_swta)
  2345. def to_dict( self, view='collection', value_mapper = None ):
  2346. rval = super( StoredWorkflow, self ).to_dict( view=view, value_mapper = value_mapper )
  2347. tags_str_list = []
  2348. for tag in self.tags:
  2349. tag_str = tag.user_tname
  2350. if tag.value is not None:
  2351. tag_str += ":" + tag.user_value
  2352. tags_str_list.append( tag_str )
  2353. rval['tags'] = tags_str_list
  2354. return rval
  2355. class Workflow( object, Dictifiable ):
  2356. dict_collection_visible_keys = ( 'name', 'has_cycles', 'has_errors' )
  2357. dict_element_visible_keys = ( 'name', 'has_cycles', 'has_errors' )
  2358. def __init__( self ):
  2359. self.user = None
  2360. self.name = None
  2361. self.has_cycles = None
  2362. self.has_errors = None
  2363. self.steps = []
  2364. class WorkflowStep( object ):
  2365. def __init__( self ):
  2366. self.id = None
  2367. self.type = None
  2368. self.tool_id = None
  2369. self.tool_inputs = None
  2370. self.tool_errors = None
  2371. self.position = None
  2372. self.input_connections = []
  2373. self.config = None
  2374. class WorkflowStepConnection( object ):
  2375. def __init__( self ):
  2376. self.output_step_id = None
  2377. self.output_name = None
  2378. self.input_step_id = None
  2379. self.input_name = None
  2380. class WorkflowOutput(object):
  2381. def __init__( self, workflow_step, output_name):
  2382. self.workflow_step = workflow_step
  2383. self.output_name = output_name
  2384. class StoredWorkflowUserShareAssociation( object ):
  2385. def __init__( self ):
  2386. self.stored_workflow = None
  2387. self.user = None
  2388. class StoredWorkflowMenuEntry( object ):
  2389. def __init__( self ):
  2390. self.stored_workflow = None
  2391. self.user = None
  2392. self.order_index = None
  2393. class WorkflowInvocation( object, Dictifiable ):
  2394. dict_collection_visible_keys = ( 'id', 'update_time', 'workflow_id' )
  2395. dict_element_visible_keys = ( 'id', 'update_time', 'workflow_id' )
  2396. def to_dict( self, view='collection', value_mapper = None ):
  2397. rval = super( WorkflowInvocation, self ).to_dict( view=view, value_mapper=value_mapper )
  2398. if view == 'element':
  2399. steps = {}
  2400. for step in self.steps:
  2401. v = step.to_dict()
  2402. steps[str(v['order_index'])] = v
  2403. rval['steps'] = steps
  2404. inputs = {}
  2405. for step in self.steps:
  2406. if step.workflow_step.type =='tool':
  2407. for step_input in step.workflow_step.input_connections:
  2408. if step_input.output_step.type == 'data_input':
  2409. for job_input in step.job.input_datasets:
  2410. if job_input.name == step_input.input_name:
  2411. inputs[str(step_input.output_step.order_index)] = { "id" : job_input.dataset_id, "src" : "hda"}
  2412. rval['inputs'] = inputs
  2413. return rval
  2414. class WorkflowInvocationStep( object, Dictifiable ):
  2415. dict_collection_visible_keys = ( 'id', 'update_time', 'job_id', 'workflow_step_id' )
  2416. dict_element_visible_keys = ( 'id', 'update_time', 'job_id', 'workflow_step_id' )
  2417. def to_dict( self, view='collection', value_mapper = None ):
  2418. rval = super( WorkflowInvocationStep, self ).to_dict( view=view, value_mapper=value_mapper )
  2419. rval['order_index'] = self.workflow_step.order_index
  2420. return rval
  2421. class MetadataFile( object ):
  2422. def __init__( self, dataset = None, name = None ):
  2423. if isinstance( dataset, HistoryDatasetAssociation ):
  2424. self.history_dataset = dataset
  2425. elif isinstance( dataset, LibraryDatasetDatasetAssociation ):
  2426. self.library_dataset = dataset
  2427. self.name = name
  2428. @property
  2429. def file_name( self ):
  2430. assert self.id is not None, "ID must be set before filename used (commit the object)"
  2431. # Ensure the directory structure and the metadata file object exist
  2432. try:
  2433. da = self.history_dataset or self.library_dataset
  2434. if self.object_store_id is None and da is not None:
  2435. self.object_store_id = da.dataset.object_store_id
  2436. if not da.dataset.object_store.exists( self, extra_dir='_metadata_files', extra_dir_at_root=True, alt_name="metadata_%d.dat" % self.id ):
  2437. da.dataset.object_store.create( self, extra_dir='_metadata_files', extra_dir_at_root=True, alt_name="metadata_%d.dat" % self.id )
  2438. path = da.dataset.object_store.get_filename( self, extra_dir='_metadata_files', extra_dir_at_root=True, alt_name="metadata_%d.dat" % self.id )
  2439. return path
  2440. except AttributeError:
  2441. # In case we're not working with the history_dataset
  2442. # print "Caught AttributeError"
  2443. path = os.path.join( Dataset.file_path, '_metadata_files', *directory_hash_id( self.id ) )
  2444. # Create directory if it does not exist
  2445. try:
  2446. os.makedirs( path )
  2447. except OSError, e:
  2448. # File Exists is okay, otherwise reraise
  2449. if e.errno != errno.EEXIST:
  2450. raise
  2451. # Return filename inside hashed directory
  2452. return os.path.abspath( os.path.join( path, "metadata_%d.dat" % self.id ) )
  2453. class FormDefinition( object, Dictifiable ):
  2454. # The following form_builder classes are supported by the FormDefinition class.
  2455. supported_field_types = [ AddressField, CheckboxField, PasswordField, SelectField, TextArea, TextField, WorkflowField, WorkflowMappingField, HistoryField ]
  2456. types = Bunch( REQUEST = 'Sequencing Request Form',
  2457. SAMPLE = 'Sequencing Sample Form',
  2458. EXTERNAL_SERVICE = 'External Service Information Form',
  2459. RUN_DETAILS_TEMPLATE = 'Sample run details template',
  2460. LIBRARY_INFO_TEMPLATE = 'Library information template',
  2461. USER_INFO = 'User Information' )
  2462. dict_collection_visible_keys = ( 'id', 'name' )
  2463. dict_element_visible_keys = ( 'id', 'name', 'desc', 'form_definition_current_id', 'fields', 'layout' )
  2464. def __init__( self, name=None, desc=None, fields=[], form_definition_current=None, form_type=None, layout=None ):
  2465. self.name = name
  2466. self.desc = desc
  2467. self.fields = fields
  2468. self.form_definition_current = form_definition_current
  2469. self.type = form_type
  2470. self.layout = layout
  2471. def grid_fields( self, grid_index ):
  2472. # Returns a dictionary whose keys are integers corresponding to field positions
  2473. # on the grid and whose values are the field.
  2474. gridfields = {}
  2475. for i, f in enumerate( self.fields ):
  2476. if str( f[ 'layout' ] ) == str( grid_index ):
  2477. gridfields[i] = f
  2478. return gridfields
  2479. def get_widgets( self, user, contents={}, **kwd ):
  2480. '''
  2481. Return the list of widgets that comprise a form definition,
  2482. including field contents if any.
  2483. '''
  2484. params = Params( kwd )
  2485. widgets = []
  2486. for index, field in enumerate( self.fields ):
  2487. field_type = field[ 'type' ]
  2488. if 'name' in field:
  2489. field_name = field[ 'name' ]
  2490. else:
  2491. # Default to names like field_0, field_1, etc for backward compatibility
  2492. # (not sure this is necessary)...
  2493. field_name = 'field_%i' % index
  2494. # Determine the value of the field
  2495. if field_name in kwd:
  2496. # The form was submitted via refresh_on_change
  2497. if field_type == 'CheckboxField':
  2498. value = CheckboxField.is_checked( params.get( field_name, False ) )
  2499. else:
  2500. value = restore_text( params.get( field_name, '' ) )
  2501. elif contents:
  2502. try:
  2503. # This field has a saved value.
  2504. value = str( contents[ field[ 'name' ] ] )
  2505. except:
  2506. # If there was an error getting the saved value, we'll still
  2507. # display the widget, but it will be empty.
  2508. if field_type == 'AddressField':
  2509. value = 'none'
  2510. elif field_type == 'CheckboxField':
  2511. # Since we do not have contents, set checkbox value to False
  2512. value = False
  2513. else:
  2514. # Set other field types to empty string
  2515. value = ''
  2516. else:
  2517. # If none of the above, then leave the field empty
  2518. if field_type == 'AddressField':
  2519. value = 'none'
  2520. elif field_type == 'CheckboxField':
  2521. # Since we do not have contents, set checkbox value to False
  2522. value = False
  2523. else:
  2524. # Set other field types to the default value of the field
  2525. value = field.get( 'default', '' )
  2526. # Create the field widget
  2527. field_widget = eval( field_type )( field_name )
  2528. if field_type in [ 'TextField', 'PasswordField' ]:
  2529. field_widget.set_size( 40 )
  2530. field_widget.value = value
  2531. elif field_type == 'TextArea':
  2532. field_widget.set_size( 3, 40 )
  2533. field_widget.value = value
  2534. elif field_type in ['AddressField', 'WorkflowField', 'WorkflowMappingField', 'HistoryField']:
  2535. field_widget.user = user
  2536. field_widget.value = value
  2537. field_widget.params = params
  2538. elif field_type == 'SelectField':
  2539. for option in field[ 'selectlist' ]:
  2540. if option == value:
  2541. field_widget.add_option( option, option, selected=True )
  2542. else:
  2543. field_widget.add_option( option, option )
  2544. elif field_type == 'CheckboxField':
  2545. field_widget.set_checked( value )
  2546. if field[ 'required' ] == 'required':
  2547. req = 'Required'
  2548. else:
  2549. req = 'Optional'
  2550. if field[ 'helptext' ]:
  2551. helptext='%s (%s)' % ( field[ 'helptext' ], req )
  2552. else:
  2553. helptext = '(%s)' % req
  2554. widgets.append( dict( label=field[ 'label' ],
  2555. widget=field_widget,
  2556. helptext=helptext ) )
  2557. return widgets
  2558. def field_as_html( self, field ):
  2559. """Generates disabled html for a field"""
  2560. type = field[ 'type' ]
  2561. form_field = None
  2562. for field_type in self.supported_field_types:
  2563. if type == field_type.__name__:
  2564. # Name it AddressField, CheckboxField, etc.
  2565. form_field = field_type( type )
  2566. break
  2567. if form_field:
  2568. return form_field.get_html( disabled=True )
  2569. # Return None if unsupported field type
  2570. return None
  2571. class FormDefinitionCurrent( object ):
  2572. def __init__(self, form_definition=None):
  2573. self.latest_form = form_definition
  2574. class FormValues( object ):
  2575. def __init__(self, form_def=None, content=None):
  2576. self.form_definition = form_def
  2577. self.content = content
  2578. class Request( object, Dictifiable ):
  2579. states = Bunch( NEW = 'New',
  2580. SUBMITTED = 'In Progress',
  2581. REJECTED = 'Rejected',
  2582. COMPLETE = 'Complete' )
  2583. dict_collection_visible_keys = ( 'id', 'name', 'state' )
  2584. def __init__( self, name=None, desc=None, request_type=None, user=None, form_values=None, notification=None ):
  2585. self.name = name
  2586. self.desc = desc
  2587. self.type = request_type
  2588. self.values = form_values
  2589. self.user = user
  2590. self.notification = notification
  2591. self.samples_list = []
  2592. @property
  2593. def state( self ):
  2594. latest_event = self.latest_event
  2595. if latest_event:
  2596. return latest_event.state
  2597. return None
  2598. @property
  2599. def latest_event( self ):
  2600. if self.events:
  2601. return self.events[0]
  2602. return None
  2603. @property
  2604. def samples_have_common_state( self ):
  2605. """
  2606. Returns the state of this request's samples when they are all
  2607. in one common state. Otherwise returns False.
  2608. """
  2609. state_for_comparison = self.samples[0].state
  2610. if state_for_comparison is None:
  2611. for s in self.samples:
  2612. if s.state is not None:
  2613. return False
  2614. for s in self.samples:
  2615. if s.state.id != state_for_comparison.id:
  2616. return False
  2617. return state_for_comparison
  2618. @property
  2619. def last_comment( self ):
  2620. latest_event = self.latest_event
  2621. if latest_event:
  2622. if latest_event.comment:
  2623. return latest_event.comment
  2624. return ''
  2625. return 'No comment'
  2626. def get_sample( self, sample_name ):
  2627. for sample in self.samples:
  2628. if sample.name == sample_name:
  2629. return sample
  2630. return None
  2631. @property
  2632. def is_unsubmitted( self ):
  2633. return self.state in [ self.states.REJECTED, self.states.NEW ]
  2634. @property
  2635. def is_rejected( self ):
  2636. return self.state == self.states.REJECTED
  2637. @property
  2638. def is_submitted( self ):
  2639. return self.state == self.states.SUBMITTED
  2640. @property
  2641. def is_new( self ):
  2642. return self.state == self.states.NEW
  2643. @property
  2644. def is_complete( self ):
  2645. return self.state == self.states.COMPLETE
  2646. @property
  2647. def samples_without_library_destinations( self ):
  2648. # Return all samples that are not associated with a library
  2649. samples = []
  2650. for sample in self.samples:
  2651. if not sample.library:
  2652. samples.append( sample )
  2653. return samples
  2654. @property
  2655. def samples_with_bar_code( self ):
  2656. # Return all samples that have associated bar code
  2657. samples = []
  2658. for sample in self.samples:
  2659. if sample.bar_code:
  2660. samples.append( sample )
  2661. return samples
  2662. def send_email_notification( self, trans, common_state, final_state=False ):
  2663. # Check if an email notification is configured to be sent when the samples
  2664. # are in this state
  2665. if self.notification and common_state.id not in self.notification[ 'sample_states' ]:
  2666. return
  2667. comments = ''
  2668. # Send email
  2669. if trans.app.config.smtp_server is not None and self.notification and self.notification[ 'email' ]:
  2670. host = trans.request.host.split( ':' )[0]
  2671. if host in [ 'localhost', '127.0.0.1', '0.0.0.0' ]:
  2672. host = socket.getfqdn()
  2673. body = """
  2674. Galaxy Sample Tracking Notification
  2675. ===================================
  2676. User: %(user)s
  2677. Sequencing request: %(request_name)s
  2678. Sequencer configuration: %(request_type)s
  2679. Sequencing request state: %(request_state)s
  2680. Number of samples: %(num_samples)s
  2681. All samples in state: %(sample_state)s
  2682. """
  2683. values = dict( user=self.user.email,
  2684. request_name=self.name,
  2685. request_type=self.type.name,
  2686. request_state=self.state,
  2687. num_samples=str( len( self.samples ) ),
  2688. sample_state=common_state.name,
  2689. create_time=self.create_time,
  2690. submit_time=self.create_time )
  2691. body = body % values
  2692. # check if this is the final state of the samples
  2693. if final_state:
  2694. txt = "Sample Name -> Data Library/Folder\r\n"
  2695. for s in self.samples:
  2696. if s.library:
  2697. library_name = s.library.name
  2698. folder_name = s.folder.name
  2699. else:
  2700. library_name = 'No target data library'
  2701. folder_name = 'No target data library folder'
  2702. txt = txt + "%s -> %s/%s\r\n" % ( s.name, library_name, folder_name )
  2703. body = body + txt
  2704. to = self.notification['email']
  2705. frm = 'galaxy-no-reply@' + host
  2706. subject = "Galaxy Sample Tracking notification: '%s' sequencing request" % self.name
  2707. try:
  2708. send_mail( frm, to, subject, body, trans.app.config )
  2709. comments = "Email notification sent to %s." % ", ".join( to ).strip().strip( ',' )
  2710. except Exception,e:
  2711. comments = "Email notification failed. (%s)" % str(e)
  2712. # update the request history with the email notification event
  2713. elif not trans.app.config.smtp_server:
  2714. comments = "Email notification failed as SMTP server not set in config file"
  2715. if comments:
  2716. event = RequestEvent( self, self.state, comments )
  2717. trans.sa_session.add( event )
  2718. trans.sa_session.flush()
  2719. return comments
  2720. class RequestEvent( object ):
  2721. def __init__(self, request=None, request_state=None, comment=''):
  2722. self.request = request
  2723. self.state = request_state
  2724. self.comment = comment
  2725. class ExternalService( object ):
  2726. data_transfer_protocol = Bunch( HTTP = 'http',
  2727. HTTPS = 'https',
  2728. SCP = 'scp' )
  2729. def __init__( self, name=None, description=None, external_service_type_id=None, version=None, form_definition_id=None, form_values_id=None, deleted=None ):
  2730. self.name = name
  2731. self.description = description
  2732. self.external_service_type_id = external_service_type_id
  2733. self.version = version
  2734. self.form_definition_id = form_definition_id
  2735. self.form_values_id = form_values_id
  2736. self.deleted = deleted
  2737. self.label = None # Used in the request_type controller's __build_external_service_select_field() method
  2738. def get_external_service_type( self, trans ):
  2739. return trans.app.external_service_types.all_external_service_types[ self.external_service_type_id ]
  2740. def load_data_transfer_settings( self, trans ):
  2741. trans.app.external_service_types.reload( self.external_service_type_id )
  2742. self.data_transfer = {}
  2743. external_service_type = self.get_external_service_type( trans )
  2744. for data_transfer_protocol, data_transfer_obj in external_service_type.data_transfer.items():
  2745. if data_transfer_protocol == self.data_transfer_protocol.SCP:
  2746. scp_configs = {}
  2747. automatic_transfer = data_transfer_obj.config.get( 'automatic_transfer', 'false' )
  2748. scp_configs[ 'automatic_transfer' ] = galaxy.util.string_as_bool( automatic_transfer )
  2749. scp_configs[ 'host' ] = self.form_values.content.get( data_transfer_obj.config.get( 'host', '' ), '' )
  2750. scp_configs[ 'user_name' ] = self.form_values.content.get( data_transfer_obj.config.get( 'user_name', '' ), '' )
  2751. scp_configs[ 'password' ] = self.form_values.content.get( data_transfer_obj.config.get( 'password', '' ), '' )
  2752. scp_configs[ 'data_location' ] = self.form_values.content.get( data_transfer_obj.config.get( 'data_location', '' ), '' )
  2753. scp_configs[ 'rename_dataset' ] = self.form_values.content.get( data_transfer_obj.config.get( 'rename_dataset', '' ), '' )
  2754. self.data_transfer[ self.data_transfer_protocol.SCP ] = scp_configs
  2755. if data_transfer_protocol == self.data_transfer_protocol.HTTP:
  2756. http_configs = {}
  2757. automatic_transfer = data_transfer_obj.config.get( 'automatic_transfer', 'false' )
  2758. http_configs[ 'automatic_transfer' ] = galaxy.util.string_as_bool( automatic_transfer )
  2759. self.data_transfer[ self.data_transfer_protocol.HTTP ] = http_configs
  2760. def populate_actions( self, trans, item, param_dict=None ):
  2761. return self.get_external_service_type( trans ).actions.populate( self, item, param_dict=param_dict )
  2762. class RequestType( object, Dictifiable ):
  2763. dict_collection_visible_keys = ( 'id', 'name', 'desc' )
  2764. dict_element_visible_keys = ( 'id', 'name', 'desc', 'request_form_id', 'sample_form_id' )
  2765. rename_dataset_options = Bunch( NO = 'Do not rename',
  2766. SAMPLE_NAME = 'Preprend sample name',
  2767. EXPERIMENT_NAME = 'Prepend experiment name',
  2768. EXPERIMENT_AND_SAMPLE_NAME = 'Prepend experiment and sample name')
  2769. permitted_actions = get_permitted_actions( filter='REQUEST_TYPE' )
  2770. def __init__( self, name=None, desc=None, request_form=None, sample_form=None ):
  2771. self.name = name
  2772. self.desc = desc
  2773. self.request_form = request_form
  2774. self.sample_form = sample_form
  2775. @property
  2776. def external_services( self ):
  2777. external_services = []
  2778. for rtesa in self.external_service_associations:
  2779. external_services.append( rtesa.external_service )
  2780. return external_services
  2781. def get_external_service( self, external_service_type_id ):
  2782. for rtesa in self.external_service_associations:
  2783. if rtesa.external_service.external_service_type_id == external_service_type_id:
  2784. return rtesa.external_service
  2785. return None
  2786. def get_external_services_for_manual_data_transfer( self, trans ):
  2787. '''Returns all external services that use manual data transfer'''
  2788. external_services = []
  2789. for rtesa in self.external_service_associations:
  2790. external_service = rtesa.external_service
  2791. # load data transfer settings
  2792. external_service.load_data_transfer_settings( trans )
  2793. if external_service.data_transfer:
  2794. for transfer_type, transfer_type_settings in external_service.data_transfer.items():
  2795. if not transfer_type_settings[ 'automatic_transfer' ]:
  2796. external_services.append( external_service )
  2797. return external_services
  2798. def delete_external_service_associations( self, trans ):
  2799. '''Deletes all external service associations.'''
  2800. flush_needed = False
  2801. for rtesa in self.external_service_associations:
  2802. trans.sa_session.delete( rtesa )
  2803. flush_needed = True
  2804. if flush_needed:
  2805. trans.sa_session.flush()
  2806. def add_external_service_association( self, trans, external_service ):
  2807. rtesa = trans.model.RequestTypeExternalServiceAssociation( self, external_service )
  2808. trans.sa_session.add( rtesa )
  2809. trans.sa_session.flush()
  2810. @property
  2811. def final_sample_state( self ):
  2812. # The states mapper for this object orders ascending
  2813. return self.states[-1]
  2814. @property
  2815. def run_details( self ):
  2816. if self.run:
  2817. # self.run[0] is [RequestTypeRunAssociation]
  2818. return self.run[0]
  2819. return None
  2820. def get_template_widgets( self, trans, get_contents=True ):
  2821. # See if we have any associated templates. The get_contents param
  2822. # is passed by callers that are inheriting a template - these are
  2823. # usually new samples for which we want to include template fields,
  2824. # but not necessarily the contents of the inherited template.
  2825. rtra = self.run_details
  2826. if rtra:
  2827. run = rtra.run
  2828. template = run.template
  2829. if get_contents:
  2830. # See if we have any field contents
  2831. info = run.info
  2832. if info:
  2833. return template.get_widgets( trans.user, contents=info.content )
  2834. return template.get_widgets( trans.user )
  2835. return []
  2836. class RequestTypeExternalServiceAssociation( object ):
  2837. def __init__( self, request_type, external_service ):
  2838. self.request_type = request_type
  2839. self.external_service = external_service
  2840. class RequestTypePermissions( object ):
  2841. def __init__( self, action, request_type, role ):
  2842. self.action = action
  2843. self.request_type = request_type
  2844. self.role = role
  2845. class Sample( object, Dictifiable ):
  2846. # The following form_builder classes are supported by the Sample class.
  2847. supported_field_types = [ CheckboxField, SelectField, TextField, WorkflowField, WorkflowMappingField, HistoryField ]
  2848. bulk_operations = Bunch( CHANGE_STATE = 'Change state',
  2849. SELECT_LIBRARY = 'Select data library and folder' )
  2850. dict_collection_visible_keys = ( 'id', 'name' )
  2851. def __init__(self, name=None, desc=None, request=None, form_values=None, bar_code=None, library=None, folder=None, workflow=None, history=None):
  2852. self.name = name
  2853. self.desc = desc
  2854. self.request = request
  2855. self.values = form_values
  2856. self.bar_code = bar_code
  2857. self.library = library
  2858. self.folder = folder
  2859. self.history = history
  2860. self.workflow = workflow
  2861. @property
  2862. def state( self ):
  2863. latest_event = self.latest_event
  2864. if latest_event:
  2865. return latest_event.state
  2866. return None
  2867. @property
  2868. def latest_event( self ):
  2869. if self.events:
  2870. return self.events[0]
  2871. return None
  2872. @property
  2873. def adding_to_library_dataset_files( self ):
  2874. adding_to_library_datasets = []
  2875. for dataset in self.datasets:
  2876. if dataset.status == SampleDataset.transfer_status.ADD_TO_LIBRARY:
  2877. adding_to_library_datasets.append( dataset )
  2878. return adding_to_library_datasets
  2879. @property
  2880. def inprogress_dataset_files( self ):
  2881. inprogress_datasets = []
  2882. for dataset in self.datasets:
  2883. if dataset.status not in [ SampleDataset.transfer_status.NOT_STARTED, SampleDataset.transfer_status.COMPLETE ]:
  2884. inprogress_datasets.append( dataset )
  2885. return inprogress_datasets
  2886. @property
  2887. def queued_dataset_files( self ):
  2888. queued_datasets = []
  2889. for dataset in self.datasets:
  2890. if dataset.status == SampleDataset.transfer_status.IN_QUEUE:
  2891. queued_datasets.append( dataset )
  2892. return queued_datasets
  2893. @property
  2894. def transfer_error_dataset_files( self ):
  2895. transfer_error_datasets = []
  2896. for dataset in self.datasets:
  2897. if dataset.status == SampleDataset.transfer_status.ERROR:
  2898. transfer_error_datasets.append( dataset )
  2899. return transfer_error_datasets
  2900. @property
  2901. def transferred_dataset_files( self ):
  2902. transferred_datasets = []
  2903. for dataset in self.datasets:
  2904. if dataset.status == SampleDataset.transfer_status.COMPLETE:
  2905. transferred_datasets.append( dataset )
  2906. return transferred_datasets
  2907. @property
  2908. def transferring_dataset_files( self ):
  2909. transferring_datasets = []
  2910. for dataset in self.datasets:
  2911. if dataset.status == SampleDataset.transfer_status.TRANSFERRING:
  2912. transferring_datasets.append( dataset )
  2913. return transferring_datasets
  2914. @property
  2915. def untransferred_dataset_files( self ):
  2916. untransferred_datasets = []
  2917. for dataset in self.datasets:
  2918. if dataset.status != SampleDataset.transfer_status.COMPLETE:
  2919. untransferred_datasets.append( dataset )
  2920. return untransferred_datasets
  2921. def get_untransferred_dataset_size( self, filepath, scp_configs ):
  2922. def print_ticks( d ):
  2923. pass
  2924. error_msg = 'Error encountered in determining the file size of %s on the external_service.' % filepath
  2925. if not scp_configs['host'] or not scp_configs['user_name'] or not scp_configs['password']:
  2926. return error_msg
  2927. login_str = '%s@%s' % ( scp_configs['user_name'], scp_configs['host'] )
  2928. cmd = 'ssh %s "du -sh \'%s\'"' % ( login_str, filepath )
  2929. try:
  2930. output = pexpect.run( cmd,
  2931. events={ '.ssword:*': scp_configs['password']+'\r\n',
  2932. pexpect.TIMEOUT:print_ticks},
  2933. timeout=10 )
  2934. except Exception:
  2935. return error_msg
  2936. # cleanup the output to get just the file size
  2937. return output.replace( filepath, '' )\
  2938. .replace( 'Password:', '' )\
  2939. .replace( "'s password:", '' )\
  2940. .replace( login_str, '' )\
  2941. .strip()
  2942. @property
  2943. def run_details( self ):
  2944. # self.runs is a list of SampleRunAssociations ordered descending on update_time.
  2945. if self.runs:
  2946. # Always use the latest run details template, self.runs[0] is a SampleRunAssociation
  2947. return self.runs[0]
  2948. # Inherit this sample's RequestType run details, if one exists.
  2949. return self.request.type.run_details
  2950. def get_template_widgets( self, trans, get_contents=True ):
  2951. # Samples have a one-to-many relationship with run details, so we return the
  2952. # widgets for last associated template. The get_contents param will populate
  2953. # the widget fields with values from the template inherited from the sample's
  2954. # RequestType.
  2955. template = None
  2956. if self.runs:
  2957. # The self.runs mapper orders descending on update_time.
  2958. run = self.runs[0].run
  2959. template = run.template
  2960. if template is None:
  2961. # There are no run details associated with this sample, so inherit the
  2962. # run details template from the sample's RequestType.
  2963. rtra = self.request.type.run_details
  2964. if rtra:
  2965. run = rtra.run
  2966. template = run.template
  2967. if template:
  2968. if get_contents:
  2969. # See if we have any field contents
  2970. info = run.info
  2971. if info:
  2972. return template.get_widgets( trans.user, contents=info.content )
  2973. return template.get_widgets( trans.user )
  2974. return []
  2975. def populate_external_services( self, param_dict = None, trans = None ):
  2976. if self.request and self.request.type:
  2977. return [ service.populate_actions( item = self, param_dict = param_dict, trans = trans ) for service in self.request.type.external_services ]
  2978. class SampleState( object ):
  2979. def __init__(self, name=None, desc=None, request_type=None):
  2980. self.name = name
  2981. self.desc = desc
  2982. self.request_type = request_type
  2983. class SampleEvent( object ):
  2984. def __init__(self, sample=None, sample_state=None, comment=''):
  2985. self.sample = sample
  2986. self.state = sample_state
  2987. self.comment = comment
  2988. class SampleDataset( object ):
  2989. transfer_status = Bunch( NOT_STARTED = 'Not started',
  2990. IN_QUEUE = 'In queue',
  2991. TRANSFERRING = 'Transferring dataset',
  2992. ADD_TO_LIBRARY = 'Adding to data library',
  2993. COMPLETE = 'Complete',
  2994. ERROR = 'Error' )
  2995. def __init__( self, sample=None, name=None, file_path=None, status=None, error_msg=None, size=None, external_service=None ):
  2996. self.sample = sample
  2997. self.name = name
  2998. self.file_path = file_path
  2999. self.status = status
  3000. self.error_msg = error_msg
  3001. self.size = size
  3002. self.external_service = external_service
  3003. class Run( object ):
  3004. def __init__( self, form_definition, form_values, subindex=None ):
  3005. self.template = form_definition
  3006. self.info = form_values
  3007. self.subindex = subindex
  3008. class RequestTypeRunAssociation( object ):
  3009. def __init__( self, request_type, run ):
  3010. self.request_type = request_type
  3011. self.run = run
  3012. class SampleRunAssociation( object ):
  3013. def __init__( self, sample, run ):
  3014. self.sample = sample
  3015. self.run = run
  3016. class UserAddress( object ):
  3017. def __init__( self, user=None, desc=None, name=None, institution=None,
  3018. address=None, city=None, state=None, postal_code=None,
  3019. country=None, phone=None ):
  3020. self.user = user
  3021. self.desc = desc
  3022. self.name = name
  3023. self.institution = institution
  3024. self.address = address
  3025. self.city = city
  3026. self.state = state
  3027. self.postal_code = postal_code
  3028. self.country = country
  3029. self.phone = phone
  3030. def get_html(self):
  3031. html = ''
  3032. if self.name:
  3033. html = html + self.name
  3034. if self.institution:
  3035. html = html + '<br/>' + self.institution
  3036. if self.address:
  3037. html = html + '<br/>' + self.address
  3038. if self.city:
  3039. html = html + '<br/>' + self.city
  3040. if self.state:
  3041. html = html + ' ' + self.state
  3042. if self.postal_code:
  3043. html = html + ' ' + self.postal_code
  3044. if self.country:
  3045. html = html + '<br/>' + self.country
  3046. if self.phone:
  3047. html = html + '<br/>' + 'Phone: ' + self.phone
  3048. return html
  3049. class UserOpenID( object ):
  3050. def __init__( self, user=None, session=None, openid=None ):
  3051. self.user = user
  3052. self.session = session
  3053. self.openid = openid
  3054. class Page( object, Dictifiable ):
  3055. dict_element_visible_keys = [ 'id', 'title', 'latest_revision_id', 'slug', 'published', 'importable', 'deleted' ]
  3056. def __init__( self ):
  3057. self.id = None
  3058. self.user = None
  3059. self.title = None
  3060. self.slug = None
  3061. self.latest_revision_id = None
  3062. self.revisions = []
  3063. self.importable = None
  3064. self.published = None
  3065. def to_dict( self, view='element' ):
  3066. rval = super( Page, self ).to_dict( view=view )
  3067. rev = []
  3068. for a in self.revisions:
  3069. rev.append(a.id)
  3070. rval['revision_ids'] = rev
  3071. return rval
  3072. class PageRevision( object, Dictifiable ):
  3073. dict_element_visible_keys = [ 'id', 'page_id', 'title', 'content' ]
  3074. def __init__( self ):
  3075. self.user = None
  3076. self.title = None
  3077. self.content = None
  3078. def to_dict( self, view='element' ):
  3079. rval = super( PageRevision, self ).to_dict( view=view )
  3080. rval['create_time'] = str(self.create_time)
  3081. rval['update_time'] = str(self.update_time)
  3082. return rval
  3083. class PageUserShareAssociation( object ):
  3084. def __init__( self ):
  3085. self.page = None
  3086. self.user = None
  3087. class Visualization( object ):
  3088. def __init__( self, id=None, user=None, type=None, title=None, dbkey=None, slug=None, latest_revision=None ):
  3089. self.id = id
  3090. self.user = user
  3091. self.type = type
  3092. self.title = title
  3093. self.dbkey = dbkey
  3094. self.slug = slug
  3095. self.latest_revision = latest_revision
  3096. self.revisions = []
  3097. if self.latest_revision:
  3098. self.revisions.append( latest_revision )
  3099. def copy( self, user=None, title=None ):
  3100. """
  3101. Provide copy of visualization with only its latest revision.
  3102. """
  3103. # NOTE: a shallow copy is done: the config is copied as is but datasets
  3104. # are not copied nor are the dataset ids changed. This means that the
  3105. # user does not have a copy of the data in his/her history and the
  3106. # user who owns the datasets may delete them, making them inaccessible
  3107. # for the current user.
  3108. # TODO: a deep copy option is needed.
  3109. if not user:
  3110. user = self.user
  3111. if not title:
  3112. title = self.title
  3113. copy_viz = Visualization( user=user, type=self.type, title=title, dbkey=self.dbkey )
  3114. copy_revision = self.latest_revision.copy( visualization=copy_viz )
  3115. copy_viz.latest_revision = copy_revision
  3116. return copy_viz
  3117. class VisualizationRevision( object ):
  3118. def __init__( self, visualization=None, title=None, dbkey=None, config=None ):
  3119. self.id = None
  3120. self.visualization = visualization
  3121. self.title = title
  3122. self.dbkey = dbkey
  3123. self.config = config
  3124. def copy( self, visualization=None ):
  3125. """
  3126. Returns a copy of this object.
  3127. """
  3128. if not visualization:
  3129. visualization = self.visualization
  3130. return VisualizationRevision(
  3131. visualization=visualization,
  3132. title=self.title,
  3133. dbkey=self.dbkey,
  3134. config=self.config
  3135. )
  3136. class VisualizationUserShareAssociation( object ):
  3137. def __init__( self ):
  3138. self.visualization = None
  3139. self.user = None
  3140. class TransferJob( object ):
  3141. # These states are used both by the transfer manager's IPC and the object
  3142. # state in the database. Not all states are used by both.
  3143. states = Bunch( NEW = 'new',
  3144. UNKNOWN = 'unknown',
  3145. PROGRESS = 'progress',
  3146. RUNNING = 'running',
  3147. ERROR = 'error',
  3148. DONE = 'done' )
  3149. terminal_states = [ states.ERROR,
  3150. states.DONE ]
  3151. def __init__( self, state=None, path=None, info=None, pid=None, socket=None, params=None ):
  3152. self.state = state
  3153. self.path = path
  3154. self.info = info
  3155. self.pid = pid
  3156. self.socket = socket
  3157. self.params = params
  3158. class Tag ( object ):
  3159. def __init__( self, id=None, type=None, parent_id=None, name=None ):
  3160. self.id = id
  3161. self.type = type
  3162. self.parent_id = parent_id
  3163. self.name = name
  3164. def __str__ ( self ):
  3165. return "Tag(id=%s, type=%i, parent_id=%s, name=%s)" % ( self.id, self.type, self.parent_id, self.name )
  3166. class ItemTagAssociation ( object, Dictifiable ):
  3167. dict_collection_visible_keys = ( 'id', 'user_tname', 'user_value' )
  3168. dict_element_visible_keys = dict_collection_visible_keys
  3169. def __init__( self, id=None, user=None, item_id=None, tag_id=None, user_tname=None, value=None ):
  3170. self.id = id
  3171. self.user = user
  3172. self.item_id = item_id
  3173. self.tag_id = tag_id
  3174. self.user_tname = user_tname
  3175. self.value = None
  3176. self.user_value = None
  3177. def copy(self):
  3178. new_ta = type(self)()
  3179. new_ta.tag_id = self.tag_id
  3180. new_ta.user_tname = self.user_tname
  3181. new_ta.value = self.value
  3182. new_ta.user_value = self.user_value
  3183. return new_ta
  3184. class HistoryTagAssociation ( ItemTagAssociation ):
  3185. pass
  3186. class DatasetTagAssociation ( ItemTagAssociation ):
  3187. pass
  3188. class HistoryDatasetAssociationTagAssociation ( ItemTagAssociation ):
  3189. pass
  3190. class PageTagAssociation ( ItemTagAssociation ):
  3191. pass
  3192. class WorkflowStepTagAssociation ( ItemTagAssociation ):
  3193. pass
  3194. class StoredWorkflowTagAssociation ( ItemTagAssociation ):
  3195. pass
  3196. class VisualizationTagAssociation ( ItemTagAssociation ):
  3197. pass
  3198. class ToolTagAssociation( ItemTagAssociation ):
  3199. def __init__( self, id=None, user=None, tool_id=None, tag_id=None, user_tname=None, value=None ):
  3200. self.id = id
  3201. self.user = user
  3202. self.tool_id = tool_id
  3203. self.tag_id = tag_id
  3204. self.user_tname = user_tname
  3205. self.value = None
  3206. self.user_value = None
  3207. # Item annotation classes.
  3208. class HistoryAnnotationAssociation( object ):
  3209. pass
  3210. class HistoryDatasetAssociationAnnotationAssociation( object ):
  3211. pass
  3212. class StoredWorkflowAnnotationAssociation( object ):
  3213. pass
  3214. class WorkflowStepAnnotationAssociation( object ):
  3215. pass
  3216. class PageAnnotationAssociation( object ):
  3217. pass
  3218. class VisualizationAnnotationAssociation( object ):
  3219. pass
  3220. # Item rating classes.
  3221. class ItemRatingAssociation( object ):
  3222. def __init__( self, id=None, user=None, item=None, rating=0 ):
  3223. self.id = id
  3224. self.user = user
  3225. self.item = item
  3226. self.rating = rating
  3227. def set_item( self, item ):
  3228. """ Set association's item. """
  3229. pass
  3230. class HistoryRatingAssociation( ItemRatingAssociation ):
  3231. def set_item( self, history ):
  3232. self.history = history
  3233. class HistoryDatasetAssociationRatingAssociation( ItemRatingAssociation ):
  3234. def set_item( self, history_dataset_association ):
  3235. self.history_dataset_association = history_dataset_association
  3236. class StoredWorkflowRatingAssociation( ItemRatingAssociation ):
  3237. def set_item( self, stored_workflow ):
  3238. self.stored_workflow = stored_workflow
  3239. class PageRatingAssociation( ItemRatingAssociation ):
  3240. def set_item( self, page ):
  3241. self.page = page
  3242. class VisualizationRatingAssociation( ItemRatingAssociation ):
  3243. def set_item( self, visualization ):
  3244. self.visualization = visualization
  3245. #Data Manager Classes
  3246. class DataManagerHistoryAssociation( object ):
  3247. def __init__( self, id=None, history=None, user=None ):
  3248. self.id = id
  3249. self.history = history
  3250. self.user = user
  3251. class DataManagerJobAssociation( object ):
  3252. def __init__( self, id=None, job=None, data_manager_id=None ):
  3253. self.id = id
  3254. self.job = job
  3255. self.data_manager_id = data_manager_id
  3256. #end of Data Manager Classes
  3257. class UserPreference ( object ):
  3258. def __init__( self, name=None, value=None):
  3259. self.name = name
  3260. self.value = value
  3261. class UserAction( object ):
  3262. def __init__( self, id=None, create_time=None, user_id=None, session_id=None, action=None, params=None, context=None):
  3263. self.id = id
  3264. self.create_time = create_time
  3265. self.user_id = user_id
  3266. self.session_id = session_id
  3267. self.action = action
  3268. self.params = params
  3269. self.context = context
  3270. class APIKeys( object ):
  3271. def __init__( self, id=None, user_id=None, key=None):
  3272. self.id = id
  3273. self.user_id = user_id
  3274. self.key = key