[2] | 1 | """ |
---|
| 2 | Galaxy data model classes |
---|
| 3 | |
---|
| 4 | Naming: try to use class names that have a distinct plural form so that |
---|
| 5 | the relationship cardinalities are obvious (e.g. prefer Dataset to Data) |
---|
| 6 | """ |
---|
| 7 | |
---|
| 8 | import galaxy.datatypes |
---|
| 9 | from galaxy.util.bunch import Bunch |
---|
| 10 | from galaxy import util |
---|
| 11 | import galaxy.datatypes.registry |
---|
| 12 | from galaxy.datatypes.metadata import MetadataCollection |
---|
| 13 | from galaxy.security import RBACAgent, get_permitted_actions |
---|
| 14 | from galaxy.util.hash_util import * |
---|
| 15 | from galaxy.web.form_builder import * |
---|
| 16 | from galaxy.model.item_attrs import UsesAnnotations |
---|
| 17 | from sqlalchemy.orm import object_session |
---|
| 18 | import os.path, os, errno, codecs, operator, smtplib, socket, pexpect, logging |
---|
| 19 | |
---|
| 20 | log = logging.getLogger( __name__ ) |
---|
| 21 | |
---|
| 22 | datatypes_registry = galaxy.datatypes.registry.Registry() #Default Value Required for unit tests |
---|
| 23 | |
---|
| 24 | def set_datatypes_registry( d_registry ): |
---|
| 25 | """ |
---|
| 26 | Set up datatypes_registry |
---|
| 27 | """ |
---|
| 28 | global datatypes_registry |
---|
| 29 | datatypes_registry = d_registry |
---|
| 30 | |
---|
| 31 | class User( object ): |
---|
| 32 | def __init__( self, email=None, password=None ): |
---|
| 33 | self.email = email |
---|
| 34 | self.password = password |
---|
| 35 | self.external = False |
---|
| 36 | self.deleted = False |
---|
| 37 | self.purged = False |
---|
| 38 | self.username = None |
---|
| 39 | # Relationships |
---|
| 40 | self.histories = [] |
---|
| 41 | self.credentials = [] |
---|
| 42 | def set_password_cleartext( self, cleartext ): |
---|
| 43 | """Set 'self.password' to the digest of 'cleartext'.""" |
---|
| 44 | self.password = new_secure_hash( text_type=cleartext ) |
---|
| 45 | def check_password( self, cleartext ): |
---|
| 46 | """Check if 'cleartext' matches 'self.password' when hashed.""" |
---|
| 47 | return self.password == new_secure_hash( text_type=cleartext ) |
---|
| 48 | def all_roles( self ): |
---|
| 49 | roles = [ ura.role for ura in self.roles ] |
---|
| 50 | for group in [ uga.group for uga in self.groups ]: |
---|
| 51 | for role in [ gra.role for gra in group.roles ]: |
---|
| 52 | if role not in roles: |
---|
| 53 | roles.append( role ) |
---|
| 54 | return roles |
---|
| 55 | def accessible_libraries( self, trans, actions ): |
---|
| 56 | # Get all permitted libraries for this user |
---|
| 57 | all_libraries = trans.sa_session.query( trans.app.model.Library ) \ |
---|
| 58 | .filter( trans.app.model.Library.table.c.deleted == False ) \ |
---|
| 59 | .order_by( trans.app.model.Library.name ) |
---|
| 60 | roles = self.all_roles() |
---|
| 61 | actions_to_check = actions |
---|
| 62 | # The libraries dictionary looks like: { library : '1,2' }, library : '3' } |
---|
| 63 | # Its keys are the libraries that should be displayed for the current user and whose values are a |
---|
| 64 | # string of comma-separated folder ids, of the associated folders the should NOT be displayed. |
---|
| 65 | # The folders that should not be displayed may not be a complete list, but it is ultimately passed |
---|
| 66 | # to the calling method to keep from re-checking the same folders when the library / folder |
---|
| 67 | # select lists are rendered. |
---|
| 68 | libraries = {} |
---|
| 69 | for library in all_libraries: |
---|
| 70 | can_show, hidden_folder_ids = trans.app.security_agent.show_library_item( self, roles, library, actions_to_check ) |
---|
| 71 | if can_show: |
---|
| 72 | libraries[ library ] = hidden_folder_ids |
---|
| 73 | return libraries |
---|
| 74 | def accessible_request_types( self, trans ): |
---|
| 75 | active_request_types = trans.sa_session.query( trans.app.model.RequestType ) \ |
---|
| 76 | .filter( trans.app.model.RequestType.table.c.deleted == False ) \ |
---|
| 77 | .order_by( trans.app.model.RequestType.name ) |
---|
| 78 | # Filter active_request_types to those that can be accessed by this user |
---|
| 79 | role_ids = [ r.id for r in self.all_roles() ] |
---|
| 80 | accessible_request_types = set() |
---|
| 81 | for request_type in active_request_types: |
---|
| 82 | for permission in request_type.actions: |
---|
| 83 | if permission.role.id in role_ids: |
---|
| 84 | accessible_request_types.add( request_type ) |
---|
| 85 | accessible_request_types = [ request_type for request_type in accessible_request_types ] |
---|
| 86 | return accessible_request_types |
---|
| 87 | |
---|
| 88 | class Job( object ): |
---|
| 89 | """ |
---|
| 90 | A job represents a request to run a tool given input datasets, tool |
---|
| 91 | parameters, and output datasets. |
---|
| 92 | """ |
---|
| 93 | states = Bunch( NEW = 'new', |
---|
| 94 | UPLOAD = 'upload', |
---|
| 95 | WAITING = 'waiting', |
---|
| 96 | QUEUED = 'queued', |
---|
| 97 | RUNNING = 'running', |
---|
| 98 | OK = 'ok', |
---|
| 99 | ERROR = 'error', |
---|
| 100 | DELETED = 'deleted' ) |
---|
| 101 | def __init__( self ): |
---|
| 102 | self.session_id = None |
---|
| 103 | self.user_id = None |
---|
| 104 | self.tool_id = None |
---|
| 105 | self.tool_version = None |
---|
| 106 | self.command_line = None |
---|
| 107 | self.param_filename = None |
---|
| 108 | self.parameters = [] |
---|
| 109 | self.input_datasets = [] |
---|
| 110 | self.output_datasets = [] |
---|
| 111 | self.output_library_datasets = [] |
---|
| 112 | self.state = Job.states.NEW |
---|
| 113 | self.info = None |
---|
| 114 | self.job_runner_name = None |
---|
| 115 | self.job_runner_external_id = None |
---|
| 116 | self.post_job_actions = [] |
---|
| 117 | self.imported = False |
---|
| 118 | |
---|
| 119 | def add_parameter( self, name, value ): |
---|
| 120 | self.parameters.append( JobParameter( name, value ) ) |
---|
| 121 | def add_input_dataset( self, name, dataset ): |
---|
| 122 | self.input_datasets.append( JobToInputDatasetAssociation( name, dataset ) ) |
---|
| 123 | def add_output_dataset( self, name, dataset ): |
---|
| 124 | self.output_datasets.append( JobToOutputDatasetAssociation( name, dataset ) ) |
---|
| 125 | def add_output_library_dataset( self, name, dataset ): |
---|
| 126 | self.output_library_datasets.append( JobToOutputLibraryDatasetAssociation( name, dataset ) ) |
---|
| 127 | def add_post_job_action(self, pja): |
---|
| 128 | self.post_job_actions.append( PostJobActionAssociation( pja, self ) ) |
---|
| 129 | def set_state( self, state ): |
---|
| 130 | self.state = state |
---|
| 131 | # For historical reasons state propogates down to datasets |
---|
| 132 | for da in self.output_datasets: |
---|
| 133 | da.dataset.state = state |
---|
| 134 | def get_param_values( self, app ): |
---|
| 135 | """ |
---|
| 136 | Read encoded parameter values from the database and turn back into a |
---|
| 137 | dict of tool parameter values. |
---|
| 138 | """ |
---|
| 139 | param_dict = dict( [ ( p.name, p.value ) for p in self.parameters ] ) |
---|
| 140 | tool = app.toolbox.tools_by_id[self.tool_id] |
---|
| 141 | param_dict = tool.params_from_strings( param_dict, app ) |
---|
| 142 | return param_dict |
---|
| 143 | def check_if_output_datasets_deleted( self ): |
---|
| 144 | """ |
---|
| 145 | Return true if all of the output datasets associated with this job are |
---|
| 146 | in the deleted state |
---|
| 147 | """ |
---|
| 148 | for dataset_assoc in self.output_datasets: |
---|
| 149 | dataset = dataset_assoc.dataset |
---|
| 150 | # only the originator of the job can delete a dataset to cause |
---|
| 151 | # cancellation of the job, no need to loop through history_associations |
---|
| 152 | if not dataset.deleted: |
---|
| 153 | return False |
---|
| 154 | return True |
---|
| 155 | def mark_deleted( self ): |
---|
| 156 | """ |
---|
| 157 | Mark this job as deleted, and mark any output datasets as discarded. |
---|
| 158 | """ |
---|
| 159 | self.state = Job.states.DELETED |
---|
| 160 | self.info = "Job output deleted by user before job completed." |
---|
| 161 | for dataset_assoc in self.output_datasets: |
---|
| 162 | dataset = dataset_assoc.dataset |
---|
| 163 | dataset.deleted = True |
---|
| 164 | dataset.state = dataset.states.DISCARDED |
---|
| 165 | for dataset in dataset.dataset.history_associations: |
---|
| 166 | # propagate info across shared datasets |
---|
| 167 | dataset.deleted = True |
---|
| 168 | dataset.blurb = 'deleted' |
---|
| 169 | dataset.peek = 'Job deleted' |
---|
| 170 | dataset.info = 'Job output deleted by user before job completed' |
---|
| 171 | |
---|
| 172 | class JobParameter( object ): |
---|
| 173 | def __init__( self, name, value ): |
---|
| 174 | self.name = name |
---|
| 175 | self.value = value |
---|
| 176 | |
---|
| 177 | class JobToInputDatasetAssociation( object ): |
---|
| 178 | def __init__( self, name, dataset ): |
---|
| 179 | self.name = name |
---|
| 180 | self.dataset = dataset |
---|
| 181 | |
---|
| 182 | class JobToOutputDatasetAssociation( object ): |
---|
| 183 | def __init__( self, name, dataset ): |
---|
| 184 | self.name = name |
---|
| 185 | self.dataset = dataset |
---|
| 186 | |
---|
| 187 | class JobToOutputLibraryDatasetAssociation( object ): |
---|
| 188 | def __init__( self, name, dataset ): |
---|
| 189 | self.name = name |
---|
| 190 | self.dataset = dataset |
---|
| 191 | |
---|
| 192 | class PostJobAction( object ): |
---|
| 193 | def __init__( self, action_type, workflow_step, output_name = None, action_arguments = None): |
---|
| 194 | self.action_type = action_type |
---|
| 195 | self.output_name = output_name |
---|
| 196 | self.action_arguments = action_arguments |
---|
| 197 | self.workflow_step = workflow_step |
---|
| 198 | |
---|
| 199 | class PostJobActionAssociation( object ): |
---|
| 200 | def __init__(self, pja, job): |
---|
| 201 | self.job = job |
---|
| 202 | self.post_job_action = pja |
---|
| 203 | |
---|
| 204 | class JobExternalOutputMetadata( object ): |
---|
| 205 | def __init__( self, job = None, dataset = None ): |
---|
| 206 | self.job = job |
---|
| 207 | if isinstance( dataset, galaxy.model.HistoryDatasetAssociation ): |
---|
| 208 | self.history_dataset_association = dataset |
---|
| 209 | elif isinstance( dataset, galaxy.model.LibraryDatasetDatasetAssociation ): |
---|
| 210 | self.library_dataset_dataset_association = dataset |
---|
| 211 | @property |
---|
| 212 | def dataset( self ): |
---|
| 213 | if self.history_dataset_association: |
---|
| 214 | return self.history_dataset_association |
---|
| 215 | elif self.library_dataset_dataset_association: |
---|
| 216 | return self.library_dataset_dataset_association |
---|
| 217 | return None |
---|
| 218 | |
---|
| 219 | class JobExportHistoryArchive( object ): |
---|
| 220 | def __init__( self, job=None, history=None, dataset=None, compressed=False, \ |
---|
| 221 | history_attrs_filename=None, datasets_attrs_filename=None, |
---|
| 222 | jobs_attrs_filename=None ): |
---|
| 223 | self.job = job |
---|
| 224 | self.history = history |
---|
| 225 | self.dataset = dataset |
---|
| 226 | self.compressed = compressed |
---|
| 227 | self.history_attrs_filename = history_attrs_filename |
---|
| 228 | self.datasets_attrs_filename = datasets_attrs_filename |
---|
| 229 | self.jobs_attrs_filename = jobs_attrs_filename |
---|
| 230 | |
---|
| 231 | class Group( object ): |
---|
| 232 | def __init__( self, name = None ): |
---|
| 233 | self.name = name |
---|
| 234 | self.deleted = False |
---|
| 235 | |
---|
| 236 | class UserGroupAssociation( object ): |
---|
| 237 | def __init__( self, user, group ): |
---|
| 238 | self.user = user |
---|
| 239 | self.group = group |
---|
| 240 | |
---|
| 241 | class History( object, UsesAnnotations ): |
---|
| 242 | def __init__( self, id=None, name=None, user=None ): |
---|
| 243 | self.id = id |
---|
| 244 | self.name = name or "Unnamed history" |
---|
| 245 | self.deleted = False |
---|
| 246 | self.purged = False |
---|
| 247 | self.genome_build = None |
---|
| 248 | self.published = False |
---|
| 249 | # Relationships |
---|
| 250 | self.user = user |
---|
| 251 | self.datasets = [] |
---|
| 252 | self.galaxy_sessions = [] |
---|
| 253 | def _next_hid( self ): |
---|
| 254 | # TODO: override this with something in the database that ensures |
---|
| 255 | # better integrity |
---|
| 256 | if len( self.datasets ) == 0: |
---|
| 257 | return 1 |
---|
| 258 | else: |
---|
| 259 | last_hid = 0 |
---|
| 260 | for dataset in self.datasets: |
---|
| 261 | if dataset.hid > last_hid: |
---|
| 262 | last_hid = dataset.hid |
---|
| 263 | return last_hid + 1 |
---|
| 264 | def add_galaxy_session( self, galaxy_session, association=None ): |
---|
| 265 | if association is None: |
---|
| 266 | self.galaxy_sessions.append( GalaxySessionToHistoryAssociation( galaxy_session, self ) ) |
---|
| 267 | else: |
---|
| 268 | self.galaxy_sessions.append( association ) |
---|
| 269 | def add_dataset( self, dataset, parent_id=None, genome_build=None, set_hid = True ): |
---|
| 270 | if isinstance( dataset, Dataset ): |
---|
| 271 | dataset = HistoryDatasetAssociation( dataset = dataset, copied_from = dataset ) |
---|
| 272 | object_session( self ).add( dataset ) |
---|
| 273 | object_session( self ).flush() |
---|
| 274 | elif not isinstance( dataset, HistoryDatasetAssociation ): |
---|
| 275 | raise TypeError, "You can only add Dataset and HistoryDatasetAssociation instances to a history ( you tried to add %s )." % str( dataset ) |
---|
| 276 | if parent_id: |
---|
| 277 | for data in self.datasets: |
---|
| 278 | if data.id == parent_id: |
---|
| 279 | dataset.hid = data.hid |
---|
| 280 | break |
---|
| 281 | else: |
---|
| 282 | if set_hid: |
---|
| 283 | dataset.hid = self._next_hid() |
---|
| 284 | else: |
---|
| 285 | if set_hid: |
---|
| 286 | dataset.hid = self._next_hid() |
---|
| 287 | dataset.history = self |
---|
| 288 | if genome_build not in [None, '?']: |
---|
| 289 | self.genome_build = genome_build |
---|
| 290 | self.datasets.append( dataset ) |
---|
| 291 | def copy( self, name=None, target_user=None, activatable=False ): |
---|
| 292 | # Create new history. |
---|
| 293 | if not name: |
---|
| 294 | name = self.name |
---|
| 295 | if not target_user: |
---|
| 296 | target_user = self.user |
---|
| 297 | new_history = History( name=name, user=target_user ) |
---|
| 298 | db_session = object_session( self ) |
---|
| 299 | db_session.add( new_history ) |
---|
| 300 | db_session.flush() |
---|
| 301 | |
---|
| 302 | # Copy annotation. |
---|
| 303 | self.copy_item_annotation( db_session, self.user, self, target_user, new_history ) |
---|
| 304 | |
---|
| 305 | # Copy HDAs. |
---|
| 306 | if activatable: |
---|
| 307 | hdas = self.activatable_datasets |
---|
| 308 | else: |
---|
| 309 | hdas = self.active_datasets |
---|
| 310 | for hda in hdas: |
---|
| 311 | # Copy HDA. |
---|
| 312 | new_hda = hda.copy( copy_children=True, target_history=new_history ) |
---|
| 313 | new_history.add_dataset( new_hda, set_hid = False ) |
---|
| 314 | db_session.add( new_hda ) |
---|
| 315 | db_session.flush() |
---|
| 316 | # Copy annotation. |
---|
| 317 | self.copy_item_annotation( db_session, self.user, hda, target_user, new_hda ) |
---|
| 318 | new_history.hid_counter = self.hid_counter |
---|
| 319 | db_session.add( new_history ) |
---|
| 320 | db_session.flush() |
---|
| 321 | return new_history |
---|
| 322 | @property |
---|
| 323 | def activatable_datasets( self ): |
---|
| 324 | # This needs to be a list |
---|
| 325 | return [ hda for hda in self.datasets if not hda.dataset.deleted ] |
---|
| 326 | def get_display_name( self ): |
---|
| 327 | """ History name can be either a string or a unicode object. If string, convert to unicode object assuming 'utf-8' format. """ |
---|
| 328 | history_name = self.name |
---|
| 329 | if isinstance(history_name, str): |
---|
| 330 | history_name = unicode(history_name, 'utf-8') |
---|
| 331 | return history_name |
---|
| 332 | |
---|
| 333 | class HistoryUserShareAssociation( object ): |
---|
| 334 | def __init__( self ): |
---|
| 335 | self.history = None |
---|
| 336 | self.user = None |
---|
| 337 | |
---|
| 338 | class UserRoleAssociation( object ): |
---|
| 339 | def __init__( self, user, role ): |
---|
| 340 | self.user = user |
---|
| 341 | self.role = role |
---|
| 342 | |
---|
| 343 | class GroupRoleAssociation( object ): |
---|
| 344 | def __init__( self, group, role ): |
---|
| 345 | self.group = group |
---|
| 346 | self.role = role |
---|
| 347 | |
---|
| 348 | class Role( object ): |
---|
| 349 | private_id = None |
---|
| 350 | types = Bunch( |
---|
| 351 | PRIVATE = 'private', |
---|
| 352 | SYSTEM = 'system', |
---|
| 353 | USER = 'user', |
---|
| 354 | ADMIN = 'admin', |
---|
| 355 | SHARING = 'sharing' |
---|
| 356 | ) |
---|
| 357 | def __init__( self, name="", description="", type="system", deleted=False ): |
---|
| 358 | self.name = name |
---|
| 359 | self.description = description |
---|
| 360 | self.type = type |
---|
| 361 | self.deleted = deleted |
---|
| 362 | |
---|
| 363 | class DatasetPermissions( object ): |
---|
| 364 | def __init__( self, action, dataset, role ): |
---|
| 365 | self.action = action |
---|
| 366 | self.dataset = dataset |
---|
| 367 | self.role = role |
---|
| 368 | |
---|
| 369 | class LibraryPermissions( object ): |
---|
| 370 | def __init__( self, action, library_item, role ): |
---|
| 371 | self.action = action |
---|
| 372 | if isinstance( library_item, Library ): |
---|
| 373 | self.library = library_item |
---|
| 374 | else: |
---|
| 375 | raise "Invalid Library specified: %s" % library_item.__class__.__name__ |
---|
| 376 | self.role = role |
---|
| 377 | |
---|
| 378 | class LibraryFolderPermissions( object ): |
---|
| 379 | def __init__( self, action, library_item, role ): |
---|
| 380 | self.action = action |
---|
| 381 | if isinstance( library_item, LibraryFolder ): |
---|
| 382 | self.folder = library_item |
---|
| 383 | else: |
---|
| 384 | raise "Invalid LibraryFolder specified: %s" % library_item.__class__.__name__ |
---|
| 385 | self.role = role |
---|
| 386 | |
---|
| 387 | class LibraryDatasetPermissions( object ): |
---|
| 388 | def __init__( self, action, library_item, role ): |
---|
| 389 | self.action = action |
---|
| 390 | if isinstance( library_item, LibraryDataset ): |
---|
| 391 | self.library_dataset = library_item |
---|
| 392 | else: |
---|
| 393 | raise "Invalid LibraryDataset specified: %s" % library_item.__class__.__name__ |
---|
| 394 | self.role = role |
---|
| 395 | |
---|
| 396 | class LibraryDatasetDatasetAssociationPermissions( object ): |
---|
| 397 | def __init__( self, action, library_item, role ): |
---|
| 398 | self.action = action |
---|
| 399 | if isinstance( library_item, LibraryDatasetDatasetAssociation ): |
---|
| 400 | self.library_dataset_dataset_association = library_item |
---|
| 401 | else: |
---|
| 402 | raise "Invalid LibraryDatasetDatasetAssociation specified: %s" % library_item.__class__.__name__ |
---|
| 403 | self.role = role |
---|
| 404 | |
---|
| 405 | class DefaultUserPermissions( object ): |
---|
| 406 | def __init__( self, user, action, role ): |
---|
| 407 | self.user = user |
---|
| 408 | self.action = action |
---|
| 409 | self.role = role |
---|
| 410 | |
---|
| 411 | class DefaultHistoryPermissions( object ): |
---|
| 412 | def __init__( self, history, action, role ): |
---|
| 413 | self.history = history |
---|
| 414 | self.action = action |
---|
| 415 | self.role = role |
---|
| 416 | |
---|
| 417 | class Dataset( object ): |
---|
| 418 | states = Bunch( NEW = 'new', |
---|
| 419 | UPLOAD = 'upload', |
---|
| 420 | QUEUED = 'queued', |
---|
| 421 | RUNNING = 'running', |
---|
| 422 | OK = 'ok', |
---|
| 423 | EMPTY = 'empty', |
---|
| 424 | ERROR = 'error', |
---|
| 425 | DISCARDED = 'discarded', |
---|
| 426 | SETTING_METADATA = 'setting_metadata', |
---|
| 427 | FAILED_METADATA = 'failed_metadata' ) |
---|
| 428 | permitted_actions = get_permitted_actions( filter='DATASET' ) |
---|
| 429 | file_path = "/tmp/" |
---|
| 430 | engine = None |
---|
| 431 | def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True ): |
---|
| 432 | self.id = id |
---|
| 433 | self.state = state |
---|
| 434 | self.deleted = False |
---|
| 435 | self.purged = False |
---|
| 436 | self.purgable = purgable |
---|
| 437 | self.external_filename = external_filename |
---|
| 438 | self._extra_files_path = extra_files_path |
---|
| 439 | self.file_size = file_size |
---|
| 440 | def get_file_name( self ): |
---|
| 441 | if not self.external_filename: |
---|
| 442 | assert self.id is not None, "ID must be set before filename used (commit the object)" |
---|
| 443 | # First try filename directly under file_path |
---|
| 444 | filename = os.path.join( self.file_path, "dataset_%d.dat" % self.id ) |
---|
| 445 | # Only use that filename if it already exists (backward compatibility), |
---|
| 446 | # otherwise construct hashed path |
---|
| 447 | if not os.path.exists( filename ): |
---|
| 448 | dir = os.path.join( self.file_path, *directory_hash_id( self.id ) ) |
---|
| 449 | # Create directory if it does not exist |
---|
| 450 | if not os.path.exists( dir ): |
---|
| 451 | os.makedirs( dir ) |
---|
| 452 | # Return filename inside hashed directory |
---|
| 453 | return os.path.abspath( os.path.join( dir, "dataset_%d.dat" % self.id ) ) |
---|
| 454 | else: |
---|
| 455 | filename = self.external_filename |
---|
| 456 | # Make filename absolute |
---|
| 457 | return os.path.abspath( filename ) |
---|
| 458 | def set_file_name ( self, filename ): |
---|
| 459 | if not filename: |
---|
| 460 | self.external_filename = None |
---|
| 461 | else: |
---|
| 462 | self.external_filename = filename |
---|
| 463 | file_name = property( get_file_name, set_file_name ) |
---|
| 464 | @property |
---|
| 465 | def extra_files_path( self ): |
---|
| 466 | if self._extra_files_path: |
---|
| 467 | path = self._extra_files_path |
---|
| 468 | else: |
---|
| 469 | path = os.path.join( self.file_path, "dataset_%d_files" % self.id ) |
---|
| 470 | #only use path directly under self.file_path if it exists |
---|
| 471 | if not os.path.exists( path ): |
---|
| 472 | path = os.path.join( os.path.join( self.file_path, *directory_hash_id( self.id ) ), "dataset_%d_files" % self.id ) |
---|
| 473 | # Make path absolute |
---|
| 474 | return os.path.abspath( path ) |
---|
| 475 | def get_size( self, nice_size=False ): |
---|
| 476 | """Returns the size of the data on disk""" |
---|
| 477 | if self.file_size: |
---|
| 478 | if nice_size: |
---|
| 479 | return galaxy.datatypes.data.nice_size( self.file_size ) |
---|
| 480 | else: |
---|
| 481 | return self.file_size |
---|
| 482 | else: |
---|
| 483 | try: |
---|
| 484 | if nice_size: |
---|
| 485 | return galaxy.datatypes.data.nice_size( os.path.getsize( self.file_name ) ) |
---|
| 486 | else: |
---|
| 487 | return os.path.getsize( self.file_name ) |
---|
| 488 | except OSError: |
---|
| 489 | return 0 |
---|
| 490 | def set_size( self ): |
---|
| 491 | """Returns the size of the data on disk""" |
---|
| 492 | try: |
---|
| 493 | if not self.file_size: |
---|
| 494 | self.file_size = os.path.getsize( self.file_name ) |
---|
| 495 | except OSError: |
---|
| 496 | self.file_size = 0 |
---|
| 497 | def has_data( self ): |
---|
| 498 | """Detects whether there is any data""" |
---|
| 499 | return self.get_size() > 0 |
---|
| 500 | def mark_deleted( self, include_children=True ): |
---|
| 501 | self.deleted = True |
---|
| 502 | def is_multi_byte( self ): |
---|
| 503 | if not self.has_data(): |
---|
| 504 | return False |
---|
| 505 | try: |
---|
| 506 | return util.is_multi_byte( codecs.open( self.file_name, 'r', 'utf-8' ).read( 100 ) ) |
---|
| 507 | except UnicodeDecodeError, e: |
---|
| 508 | return False |
---|
| 509 | # FIXME: sqlalchemy will replace this |
---|
| 510 | def _delete(self): |
---|
| 511 | """Remove the file that corresponds to this data""" |
---|
| 512 | try: |
---|
| 513 | os.remove(self.data.file_name) |
---|
| 514 | except OSError, e: |
---|
| 515 | log.critical('%s delete error %s' % (self.__class__.__name__, e)) |
---|
| 516 | def get_access_roles( self, trans ): |
---|
| 517 | roles = [] |
---|
| 518 | for dp in self.actions: |
---|
| 519 | if dp.action == trans.app.security_agent.permitted_actions.DATASET_ACCESS.action: |
---|
| 520 | roles.append( dp.role ) |
---|
| 521 | return roles |
---|
| 522 | |
---|
| 523 | class DatasetInstance( object ): |
---|
| 524 | """A base class for all 'dataset instances', HDAs, LDAs, etc""" |
---|
| 525 | states = Dataset.states |
---|
| 526 | permitted_actions = Dataset.permitted_actions |
---|
| 527 | def __init__( self, id=None, hid=None, name=None, info=None, blurb=None, peek=None, extension=None, |
---|
| 528 | dbkey=None, metadata=None, history=None, dataset=None, deleted=False, designation=None, |
---|
| 529 | parent_id=None, validation_errors=None, visible=True, create_dataset=False, sa_session=None ): |
---|
| 530 | self.name = name or "Unnamed dataset" |
---|
| 531 | self.id = id |
---|
| 532 | self.info = info |
---|
| 533 | self.blurb = blurb |
---|
| 534 | self.peek = peek |
---|
| 535 | self.extension = extension |
---|
| 536 | self.designation = designation |
---|
| 537 | self.metadata = metadata or dict() |
---|
| 538 | if dbkey: #dbkey is stored in metadata, only set if non-zero, or else we could clobber one supplied by input 'metadata' |
---|
| 539 | self.dbkey = dbkey |
---|
| 540 | self.deleted = deleted |
---|
| 541 | self.visible = visible |
---|
| 542 | # Relationships |
---|
| 543 | if not dataset and create_dataset: |
---|
| 544 | # Had to pass the sqlalchemy session in order to create a new dataset |
---|
| 545 | dataset = Dataset( state=Dataset.states.NEW ) |
---|
| 546 | sa_session.add( dataset ) |
---|
| 547 | sa_session.flush() |
---|
| 548 | self.dataset = dataset |
---|
| 549 | self.parent_id = parent_id |
---|
| 550 | self.validation_errors = validation_errors |
---|
| 551 | @property |
---|
| 552 | def ext( self ): |
---|
| 553 | return self.extension |
---|
| 554 | def get_dataset_state( self ): |
---|
| 555 | #self._state is currently only used when setting metadata externally |
---|
| 556 | #leave setting the state as-is, we'll currently handle this specially in the external metadata code |
---|
| 557 | if self._state: |
---|
| 558 | return self._state |
---|
| 559 | return self.dataset.state |
---|
| 560 | def set_dataset_state ( self, state ): |
---|
| 561 | self.dataset.state = state |
---|
| 562 | object_session( self ).add( self.dataset ) |
---|
| 563 | object_session( self ).flush() #flush here, because hda.flush() won't flush the Dataset object |
---|
| 564 | state = property( get_dataset_state, set_dataset_state ) |
---|
| 565 | def get_file_name( self ): |
---|
| 566 | return self.dataset.get_file_name() |
---|
| 567 | def set_file_name (self, filename): |
---|
| 568 | return self.dataset.set_file_name( filename ) |
---|
| 569 | file_name = property( get_file_name, set_file_name ) |
---|
| 570 | @property |
---|
| 571 | def extra_files_path( self ): |
---|
| 572 | return self.dataset.extra_files_path |
---|
| 573 | @property |
---|
| 574 | def datatype( self ): |
---|
| 575 | return datatypes_registry.get_datatype_by_extension( self.extension ) |
---|
| 576 | def get_metadata( self ): |
---|
| 577 | 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? |
---|
| 578 | self._metadata_collection = MetadataCollection( self ) |
---|
| 579 | return self._metadata_collection |
---|
| 580 | def set_metadata( self, bunch ): |
---|
| 581 | # Needs to accept a MetadataCollection, a bunch, or a dict |
---|
| 582 | self._metadata = self.metadata.make_dict_copy( bunch ) |
---|
| 583 | metadata = property( get_metadata, set_metadata ) |
---|
| 584 | # This provide backwards compatibility with using the old dbkey |
---|
| 585 | # field in the database. That field now maps to "old_dbkey" (see mapping.py). |
---|
| 586 | def get_dbkey( self ): |
---|
| 587 | dbkey = self.metadata.dbkey |
---|
| 588 | if not isinstance(dbkey, list): dbkey = [dbkey] |
---|
| 589 | if dbkey in [[None], []]: return "?" |
---|
| 590 | return dbkey[0] |
---|
| 591 | def set_dbkey( self, value ): |
---|
| 592 | if "dbkey" in self.datatype.metadata_spec: |
---|
| 593 | if not isinstance(value, list): |
---|
| 594 | self.metadata.dbkey = [value] |
---|
| 595 | else: |
---|
| 596 | self.metadata.dbkey = value |
---|
| 597 | dbkey = property( get_dbkey, set_dbkey ) |
---|
| 598 | def change_datatype( self, new_ext ): |
---|
| 599 | self.clear_associated_files() |
---|
| 600 | datatypes_registry.change_datatype( self, new_ext ) |
---|
| 601 | def get_size( self, nice_size=False ): |
---|
| 602 | """Returns the size of the data on disk""" |
---|
| 603 | if nice_size: |
---|
| 604 | return galaxy.datatypes.data.nice_size( self.dataset.get_size() ) |
---|
| 605 | return self.dataset.get_size() |
---|
| 606 | def set_size( self ): |
---|
| 607 | """Returns the size of the data on disk""" |
---|
| 608 | return self.dataset.set_size() |
---|
| 609 | def has_data( self ): |
---|
| 610 | """Detects whether there is any data""" |
---|
| 611 | return self.dataset.has_data() |
---|
| 612 | def get_raw_data( self ): |
---|
| 613 | """Returns the full data. To stream it open the file_name and read/write as needed""" |
---|
| 614 | return self.datatype.get_raw_data( self ) |
---|
| 615 | def write_from_stream( self, stream ): |
---|
| 616 | """Writes data from a stream""" |
---|
| 617 | self.datatype.write_from_stream(self, stream) |
---|
| 618 | def set_raw_data( self, data ): |
---|
| 619 | """Saves the data on the disc""" |
---|
| 620 | self.datatype.set_raw_data(self, data) |
---|
| 621 | def get_mime( self ): |
---|
| 622 | """Returns the mime type of the data""" |
---|
| 623 | return datatypes_registry.get_mimetype_by_extension( self.extension.lower() ) |
---|
| 624 | def is_multi_byte( self ): |
---|
| 625 | """Data consists of multi-byte characters""" |
---|
| 626 | return self.dataset.is_multi_byte() |
---|
| 627 | def set_peek( self, is_multi_byte=False ): |
---|
| 628 | return self.datatype.set_peek( self, is_multi_byte=is_multi_byte ) |
---|
| 629 | def init_meta( self, copy_from=None ): |
---|
| 630 | return self.datatype.init_meta( self, copy_from=copy_from ) |
---|
| 631 | def set_meta( self, **kwd ): |
---|
| 632 | self.clear_associated_files( metadata_safe = True ) |
---|
| 633 | return self.datatype.set_meta( self, **kwd ) |
---|
| 634 | def missing_meta( self, **kwd ): |
---|
| 635 | return self.datatype.missing_meta( self, **kwd ) |
---|
| 636 | def as_display_type( self, type, **kwd ): |
---|
| 637 | return self.datatype.as_display_type( self, type, **kwd ) |
---|
| 638 | def display_peek( self ): |
---|
| 639 | return self.datatype.display_peek( self ) |
---|
| 640 | def display_name( self ): |
---|
| 641 | return self.datatype.display_name( self ) |
---|
| 642 | def display_info( self ): |
---|
| 643 | return self.datatype.display_info( self ) |
---|
| 644 | def get_converted_files_by_type( self, file_type ): |
---|
| 645 | for assoc in self.implicitly_converted_datasets: |
---|
| 646 | if not assoc.deleted and assoc.type == file_type: |
---|
| 647 | return assoc.dataset |
---|
| 648 | return None |
---|
| 649 | def get_converted_dataset(self, trans, target_ext): |
---|
| 650 | """ |
---|
| 651 | Return converted dataset(s) if they exist. If not converted yet, do so and return None (the first time). |
---|
| 652 | If unconvertible, raise exception. |
---|
| 653 | """ |
---|
| 654 | # See if we can convert the dataset |
---|
| 655 | if target_ext not in self.get_converter_types(): |
---|
| 656 | raise ValueError("Conversion from '%s' to '%s' not possible", self.extension, target_ext) |
---|
| 657 | |
---|
| 658 | # See if converted dataset already exists |
---|
| 659 | converted_dataset = self.get_converted_files_by_type( target_ext ) |
---|
| 660 | if converted_dataset: |
---|
| 661 | return converted_dataset |
---|
| 662 | |
---|
| 663 | # Conversion is possible but hasn't been done yet, run converter. |
---|
| 664 | # Check if we have dependencies |
---|
| 665 | deps = {} |
---|
| 666 | try: |
---|
| 667 | fail_dependencies = False |
---|
| 668 | depends_on = trans.app.datatypes_registry.converter_deps[self.extension][target_ext] |
---|
| 669 | for dependency in depends_on: |
---|
| 670 | dep_dataset = self.get_converted_dataset(trans, dependency) |
---|
| 671 | if dep_dataset is None or dep_dataset.state != trans.app.model.Job.states.OK: |
---|
| 672 | fail_dependencies = True |
---|
| 673 | else: |
---|
| 674 | deps[dependency] = dep_dataset |
---|
| 675 | if fail_dependencies: |
---|
| 676 | return None |
---|
| 677 | except ValueError: |
---|
| 678 | raise ValueError("A dependency could not be converted.") |
---|
| 679 | except KeyError: |
---|
| 680 | pass # No deps |
---|
| 681 | |
---|
| 682 | assoc = ImplicitlyConvertedDatasetAssociation( parent=self, file_type=target_ext, metadata_safe=False ) |
---|
| 683 | new_dataset = self.datatype.convert_dataset( trans, self, target_ext, return_output=True, visible=False, deps=deps ).values()[0] |
---|
| 684 | new_dataset.hid = self.hid |
---|
| 685 | new_dataset.name = self.name |
---|
| 686 | session = trans.sa_session |
---|
| 687 | session.add( new_dataset ) |
---|
| 688 | assoc.dataset = new_dataset |
---|
| 689 | session.add( assoc ) |
---|
| 690 | session.flush() |
---|
| 691 | return None |
---|
| 692 | def clear_associated_files( self, metadata_safe = False, purge = False ): |
---|
| 693 | raise 'Unimplemented' |
---|
| 694 | def get_child_by_designation(self, designation): |
---|
| 695 | for child in self.children: |
---|
| 696 | if child.designation == designation: |
---|
| 697 | return child |
---|
| 698 | return None |
---|
| 699 | def get_converter_types(self): |
---|
| 700 | return self.datatype.get_converter_types( self, datatypes_registry ) |
---|
| 701 | def find_conversion_destination( self, accepted_formats, **kwd ): |
---|
| 702 | """Returns ( target_ext, existing converted dataset )""" |
---|
| 703 | return self.datatype.find_conversion_destination( self, accepted_formats, datatypes_registry, **kwd ) |
---|
| 704 | def add_validation_error( self, validation_error ): |
---|
| 705 | self.validation_errors.append( validation_error ) |
---|
| 706 | def extend_validation_errors( self, validation_errors ): |
---|
| 707 | self.validation_errors.extend(validation_errors) |
---|
| 708 | def mark_deleted( self, include_children=True ): |
---|
| 709 | self.deleted = True |
---|
| 710 | if include_children: |
---|
| 711 | for child in self.children: |
---|
| 712 | child.mark_deleted() |
---|
| 713 | def mark_undeleted( self, include_children=True ): |
---|
| 714 | self.deleted = False |
---|
| 715 | if include_children: |
---|
| 716 | for child in self.children: |
---|
| 717 | child.mark_undeleted() |
---|
| 718 | def mark_unhidden( self, include_children=True ): |
---|
| 719 | self.visible = True |
---|
| 720 | if include_children: |
---|
| 721 | for child in self.children: |
---|
| 722 | child.mark_unhidden() |
---|
| 723 | def undeletable( self ): |
---|
| 724 | if self.purged: |
---|
| 725 | return False |
---|
| 726 | return True |
---|
| 727 | @property |
---|
| 728 | def is_pending( self ): |
---|
| 729 | """ |
---|
| 730 | Return true if the dataset is neither ready nor in error |
---|
| 731 | """ |
---|
| 732 | return self.state in ( self.states.NEW, self.states.UPLOAD, |
---|
| 733 | self.states.QUEUED, self.states.RUNNING, |
---|
| 734 | self.states.SETTING_METADATA ) |
---|
| 735 | @property |
---|
| 736 | def source_library_dataset( self ): |
---|
| 737 | def get_source( dataset ): |
---|
| 738 | if isinstance( dataset, LibraryDatasetDatasetAssociation ): |
---|
| 739 | if dataset.library_dataset: |
---|
| 740 | return ( dataset, dataset.library_dataset ) |
---|
| 741 | if dataset.copied_from_library_dataset_dataset_association: |
---|
| 742 | source = get_source( dataset.copied_from_library_dataset_dataset_association ) |
---|
| 743 | if source: |
---|
| 744 | return source |
---|
| 745 | if dataset.copied_from_history_dataset_association: |
---|
| 746 | source = get_source( dataset.copied_from_history_dataset_association ) |
---|
| 747 | if source: |
---|
| 748 | return source |
---|
| 749 | return ( None, None ) |
---|
| 750 | return get_source( self ) |
---|
| 751 | |
---|
| 752 | def get_display_applications( self, trans ): |
---|
| 753 | return self.datatype.get_display_applications_by_dataset( self, trans ) |
---|
| 754 | |
---|
| 755 | class HistoryDatasetAssociation( DatasetInstance ): |
---|
| 756 | def __init__( self, |
---|
| 757 | hid = None, |
---|
| 758 | history = None, |
---|
| 759 | copied_from_history_dataset_association = None, |
---|
| 760 | copied_from_library_dataset_dataset_association = None, |
---|
| 761 | sa_session = None, |
---|
| 762 | **kwd ): |
---|
| 763 | # FIXME: sa_session is must be passed to DataSetInstance if the create_dataset |
---|
| 764 | # parameter is True so that the new object can be flushed. Is there a better way? |
---|
| 765 | DatasetInstance.__init__( self, sa_session=sa_session, **kwd ) |
---|
| 766 | self.hid = hid |
---|
| 767 | # Relationships |
---|
| 768 | self.history = history |
---|
| 769 | self.copied_from_history_dataset_association = copied_from_history_dataset_association |
---|
| 770 | self.copied_from_library_dataset_dataset_association = copied_from_library_dataset_dataset_association |
---|
| 771 | def copy( self, copy_children = False, parent_id = None, target_history = None ): |
---|
| 772 | hda = HistoryDatasetAssociation( hid=self.hid, |
---|
| 773 | name=self.name, |
---|
| 774 | info=self.info, |
---|
| 775 | blurb=self.blurb, |
---|
| 776 | peek=self.peek, |
---|
| 777 | extension=self.extension, |
---|
| 778 | dbkey=self.dbkey, |
---|
| 779 | dataset = self.dataset, |
---|
| 780 | visible=self.visible, |
---|
| 781 | deleted=self.deleted, |
---|
| 782 | parent_id=parent_id, |
---|
| 783 | copied_from_history_dataset_association=self, |
---|
| 784 | history = target_history ) |
---|
| 785 | object_session( self ).add( hda ) |
---|
| 786 | object_session( self ).flush() |
---|
| 787 | hda.set_size() |
---|
| 788 | # Need to set after flushed, as MetadataFiles require dataset.id |
---|
| 789 | hda.metadata = self.metadata |
---|
| 790 | if copy_children: |
---|
| 791 | for child in self.children: |
---|
| 792 | child_copy = child.copy( copy_children = copy_children, parent_id = hda.id ) |
---|
| 793 | if not self.datatype.copy_safe_peek: |
---|
| 794 | # In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs |
---|
| 795 | hda.set_peek() |
---|
| 796 | object_session( self ).flush() |
---|
| 797 | return hda |
---|
| 798 | def to_library_dataset_dataset_association( self, trans, target_folder, replace_dataset=None, parent_id=None, user=None, roles=[], ldda_message='' ): |
---|
| 799 | if replace_dataset: |
---|
| 800 | # The replace_dataset param ( when not None ) refers to a LibraryDataset that is being replaced with a new version. |
---|
| 801 | library_dataset = replace_dataset |
---|
| 802 | else: |
---|
| 803 | # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new |
---|
| 804 | # LibraryDataset, and the current user's DefaultUserPermissions will be applied to the associated Dataset. |
---|
| 805 | library_dataset = LibraryDataset( folder=target_folder, name=self.name, info=self.info ) |
---|
| 806 | object_session( self ).add( library_dataset ) |
---|
| 807 | object_session( self ).flush() |
---|
| 808 | if not user: |
---|
| 809 | # This should never happen since users must be authenticated to upload to a data library |
---|
| 810 | user = self.history.user |
---|
| 811 | ldda = LibraryDatasetDatasetAssociation( name=self.name, |
---|
| 812 | info=self.info, |
---|
| 813 | blurb=self.blurb, |
---|
| 814 | peek=self.peek, |
---|
| 815 | extension=self.extension, |
---|
| 816 | dbkey=self.dbkey, |
---|
| 817 | dataset=self.dataset, |
---|
| 818 | library_dataset=library_dataset, |
---|
| 819 | visible=self.visible, |
---|
| 820 | deleted=self.deleted, |
---|
| 821 | parent_id=parent_id, |
---|
| 822 | copied_from_history_dataset_association=self, |
---|
| 823 | user=user ) |
---|
| 824 | object_session( self ).add( ldda ) |
---|
| 825 | object_session( self ).flush() |
---|
| 826 | # If roles were selected on the upload form, restrict access to the Dataset to those roles |
---|
| 827 | for role in roles: |
---|
| 828 | dp = trans.model.DatasetPermissions( trans.app.security_agent.permitted_actions.DATASET_ACCESS.action, ldda.dataset, role ) |
---|
| 829 | trans.sa_session.add( dp ) |
---|
| 830 | trans.sa_session.flush() |
---|
| 831 | # Must set metadata after ldda flushed, as MetadataFiles require ldda.id |
---|
| 832 | ldda.metadata = self.metadata |
---|
| 833 | if ldda_message: |
---|
| 834 | ldda.message = ldda_message |
---|
| 835 | if not replace_dataset: |
---|
| 836 | target_folder.add_library_dataset( library_dataset, genome_build=ldda.dbkey ) |
---|
| 837 | object_session( self ).add( target_folder ) |
---|
| 838 | object_session( self ).flush() |
---|
| 839 | library_dataset.library_dataset_dataset_association_id = ldda.id |
---|
| 840 | object_session( self ).add( library_dataset ) |
---|
| 841 | object_session( self ).flush() |
---|
| 842 | for child in self.children: |
---|
| 843 | child_copy = child.to_library_dataset_dataset_association( trans, |
---|
| 844 | target_folder=target_folder, |
---|
| 845 | replace_dataset=replace_dataset, |
---|
| 846 | parent_id=ldda.id, |
---|
| 847 | user=ldda.user ) |
---|
| 848 | if not self.datatype.copy_safe_peek: |
---|
| 849 | # In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs |
---|
| 850 | ldda.set_peek() |
---|
| 851 | object_session( self ).flush() |
---|
| 852 | return ldda |
---|
| 853 | def clear_associated_files( self, metadata_safe = False, purge = False ): |
---|
| 854 | # metadata_safe = True means to only clear when assoc.metadata_safe == False |
---|
| 855 | for assoc in self.implicitly_converted_datasets: |
---|
| 856 | if not metadata_safe or not assoc.metadata_safe: |
---|
| 857 | assoc.clear( purge = purge ) |
---|
| 858 | def get_display_name( self ): |
---|
| 859 | ## Name can be either a string or a unicode object. If string, convert to unicode object assuming 'utf-8' format. |
---|
| 860 | hda_name = self.name |
---|
| 861 | if isinstance(hda_name, str): |
---|
| 862 | hda_name = unicode(hda_name, 'utf-8') |
---|
| 863 | return hda_name |
---|
| 864 | def get_access_roles( self, trans ): |
---|
| 865 | return self.dataset.get_access_roles( trans ) |
---|
| 866 | |
---|
| 867 | class HistoryDatasetAssociationDisplayAtAuthorization( object ): |
---|
| 868 | def __init__( self, hda=None, user=None, site=None ): |
---|
| 869 | self.history_dataset_association = hda |
---|
| 870 | self.user = user |
---|
| 871 | self.site = site |
---|
| 872 | |
---|
| 873 | class Library( object ): |
---|
| 874 | permitted_actions = get_permitted_actions( filter='LIBRARY' ) |
---|
| 875 | api_collection_visible_keys = ( 'id', 'name' ) |
---|
| 876 | api_element_visible_keys = ( 'name', 'description', 'synopsis' ) |
---|
| 877 | def __init__( self, name=None, description=None, synopsis=None, root_folder=None ): |
---|
| 878 | self.name = name or "Unnamed library" |
---|
| 879 | self.description = description |
---|
| 880 | self.synopsis = synopsis |
---|
| 881 | self.root_folder = root_folder |
---|
| 882 | def get_info_association( self, restrict=False, inherited=False ): |
---|
| 883 | if self.info_association: |
---|
| 884 | if not inherited or self.info_association[0].inheritable: |
---|
| 885 | return self.info_association[0], inherited |
---|
| 886 | else: |
---|
| 887 | return None, inherited |
---|
| 888 | return None, inherited |
---|
| 889 | def get_template_widgets( self, trans, get_contents=True ): |
---|
| 890 | # See if we have any associated templates - the returned value for |
---|
| 891 | # inherited is not applicable at the library level. The get_contents |
---|
| 892 | # param is passed by callers that are inheriting a template - these |
---|
| 893 | # are usually new library datsets for which we want to include template |
---|
| 894 | # fields on the upload form, but not necessarily the contents of the |
---|
| 895 | # inherited template saved for the parent. |
---|
| 896 | info_association, inherited = self.get_info_association() |
---|
| 897 | if info_association: |
---|
| 898 | template = info_association.template |
---|
| 899 | if get_contents: |
---|
| 900 | # See if we have any field contents |
---|
| 901 | info = info_association.info |
---|
| 902 | if info: |
---|
| 903 | return template.get_widgets( trans.user, contents=info.content ) |
---|
| 904 | return template.get_widgets( trans.user ) |
---|
| 905 | return [] |
---|
| 906 | def get_access_roles( self, trans ): |
---|
| 907 | roles = [] |
---|
| 908 | for lp in self.actions: |
---|
| 909 | if lp.action == trans.app.security_agent.permitted_actions.LIBRARY_ACCESS.action: |
---|
| 910 | roles.append( lp.role ) |
---|
| 911 | return roles |
---|
| 912 | def get_display_name( self ): |
---|
| 913 | # Library name can be either a string or a unicode object. If string, |
---|
| 914 | # convert to unicode object assuming 'utf-8' format. |
---|
| 915 | name = self.name |
---|
| 916 | if isinstance( name, str ): |
---|
| 917 | name = unicode( name, 'utf-8' ) |
---|
| 918 | return name |
---|
| 919 | def get_api_value( self, view='collection' ): |
---|
| 920 | rval = {} |
---|
| 921 | try: |
---|
| 922 | visible_keys = self.__getattribute__( 'api_' + view + '_visible_keys' ) |
---|
| 923 | except AttributeError: |
---|
| 924 | raise Exception( 'Unknown API view: %s' % view ) |
---|
| 925 | for key in visible_keys: |
---|
| 926 | try: |
---|
| 927 | rval[key] = self.__getattribute__( key ) |
---|
| 928 | except AttributeError: |
---|
| 929 | rval[key] = None |
---|
| 930 | return rval |
---|
| 931 | |
---|
| 932 | class LibraryFolder( object ): |
---|
| 933 | api_element_visible_keys = ( 'name', 'description', 'item_count', 'genome_build' ) |
---|
| 934 | def __init__( self, name=None, description=None, item_count=0, order_id=None ): |
---|
| 935 | self.name = name or "Unnamed folder" |
---|
| 936 | self.description = description |
---|
| 937 | self.item_count = item_count |
---|
| 938 | self.order_id = order_id |
---|
| 939 | self.genome_build = None |
---|
| 940 | def add_library_dataset( self, library_dataset, genome_build=None ): |
---|
| 941 | library_dataset.folder_id = self.id |
---|
| 942 | library_dataset.order_id = self.item_count |
---|
| 943 | self.item_count += 1 |
---|
| 944 | if genome_build not in [None, '?']: |
---|
| 945 | self.genome_build = genome_build |
---|
| 946 | def add_folder( self, folder ): |
---|
| 947 | folder.parent_id = self.id |
---|
| 948 | folder.order_id = self.item_count |
---|
| 949 | self.item_count += 1 |
---|
| 950 | def get_info_association( self, restrict=False, inherited=False ): |
---|
| 951 | # If restrict is True, we will return this folder's info_association, not inheriting. |
---|
| 952 | # If restrict is False, we'll return the next available info_association in the |
---|
| 953 | # inheritable hierarchy if it is "inheritable". True is also returned if the |
---|
| 954 | # info_association was inherited and False if not. This enables us to eliminate |
---|
| 955 | # displaying any contents of the inherited template. |
---|
| 956 | if self.info_association: |
---|
| 957 | if not inherited or self.info_association[0].inheritable: |
---|
| 958 | return self.info_association[0], inherited |
---|
| 959 | else: |
---|
| 960 | return None, inherited |
---|
| 961 | if restrict: |
---|
| 962 | return None, inherited |
---|
| 963 | if self.parent: |
---|
| 964 | return self.parent.get_info_association( inherited=True ) |
---|
| 965 | if self.library_root: |
---|
| 966 | return self.library_root[0].get_info_association( inherited=True ) |
---|
| 967 | return None, inherited |
---|
| 968 | def get_template_widgets( self, trans, get_contents=True ): |
---|
| 969 | # See if we have any associated templates. The get_contents |
---|
| 970 | # param is passed by callers that are inheriting a template - these |
---|
| 971 | # are usually new library datsets for which we want to include template |
---|
| 972 | # fields on the upload form. |
---|
| 973 | info_association, inherited = self.get_info_association() |
---|
| 974 | if info_association: |
---|
| 975 | if inherited: |
---|
| 976 | template = info_association.template.current.latest_form |
---|
| 977 | else: |
---|
| 978 | template = info_association.template |
---|
| 979 | # See if we have any field contents, but only if the info_association was |
---|
| 980 | # not inherited ( we do not want to display the inherited contents ). |
---|
| 981 | # (gvk: 8/30/10) Based on conversations with Dan, we agreed to ALWAYS inherit |
---|
| 982 | # contents. We'll use this behavior until we hear from the community that |
---|
| 983 | # contents should not be inherited. If we don't hear anything for a while, |
---|
| 984 | # eliminate the old commented out behavior. |
---|
| 985 | #if not inherited and get_contents: |
---|
| 986 | if get_contents: |
---|
| 987 | info = info_association.info |
---|
| 988 | if info: |
---|
| 989 | return template.get_widgets( trans.user, info.content ) |
---|
| 990 | else: |
---|
| 991 | return template.get_widgets( trans.user ) |
---|
| 992 | return [] |
---|
| 993 | @property |
---|
| 994 | def active_library_datasets( self ): |
---|
| 995 | def sort_by_attr( seq, attr ): |
---|
| 996 | """ |
---|
| 997 | Sort the sequence of objects by object's attribute |
---|
| 998 | Arguments: |
---|
| 999 | seq - the list or any sequence (including immutable one) of objects to sort. |
---|
| 1000 | attr - the name of attribute to sort by |
---|
| 1001 | """ |
---|
| 1002 | # Use the "Schwartzian transform" |
---|
| 1003 | # Create the auxiliary list of tuples where every i-th tuple has form |
---|
| 1004 | # (seq[i].attr, i, seq[i]) and sort it. The second item of tuple is needed not |
---|
| 1005 | # only to provide stable sorting, but mainly to eliminate comparison of objects |
---|
| 1006 | # (which can be expensive or prohibited) in case of equal attribute values. |
---|
| 1007 | intermed = map( None, map( getattr, seq, ( attr, ) * len( seq ) ), xrange( len( seq ) ), seq ) |
---|
| 1008 | intermed.sort() |
---|
| 1009 | return map( operator.getitem, intermed, ( -1, ) * len( intermed ) ) |
---|
| 1010 | # This needs to be a list |
---|
| 1011 | active_library_datasets = [ ld for ld in self.datasets if ld.library_dataset_dataset_association and not ld.library_dataset_dataset_association.deleted ] |
---|
| 1012 | return sort_by_attr( [ ld for ld in active_library_datasets ], 'name' ) |
---|
| 1013 | @property |
---|
| 1014 | def activatable_library_datasets( self ): |
---|
| 1015 | # This needs to be a list |
---|
| 1016 | return [ ld for ld in self.datasets if ld.library_dataset_dataset_association and not ld.library_dataset_dataset_association.dataset.deleted ] |
---|
| 1017 | @property |
---|
| 1018 | def active_datasets( self ): |
---|
| 1019 | # This needs to be a list |
---|
| 1020 | return [ ld.library_dataset_dataset_association.dataset for ld in self.datasets if ld.library_dataset_dataset_association and not ld.library_dataset_dataset_association.deleted ] |
---|
| 1021 | def get_display_name( self ): |
---|
| 1022 | # Library folder name can be either a string or a unicode object. If string, |
---|
| 1023 | # convert to unicode object assuming 'utf-8' format. |
---|
| 1024 | name = self.name |
---|
| 1025 | if isinstance( name, str ): |
---|
| 1026 | name = unicode( name, 'utf-8' ) |
---|
| 1027 | return name |
---|
| 1028 | def get_api_value( self, view='collection' ): |
---|
| 1029 | rval = {} |
---|
| 1030 | info_association, inherited = self.get_info_association() |
---|
| 1031 | if info_association: |
---|
| 1032 | if inherited: |
---|
| 1033 | template = info_association.template.current.latest_form |
---|
| 1034 | else: |
---|
| 1035 | template = info_association.template |
---|
| 1036 | rval['data_template'] = template.name |
---|
| 1037 | |
---|
| 1038 | try: |
---|
| 1039 | visible_keys = self.__getattribute__( 'api_' + view + '_visible_keys' ) |
---|
| 1040 | except AttributeError: |
---|
| 1041 | raise Exception( 'Unknown API view: %s' % view ) |
---|
| 1042 | for key in visible_keys: |
---|
| 1043 | try: |
---|
| 1044 | rval[key] = self.__getattribute__( key ) |
---|
| 1045 | except AttributeError: |
---|
| 1046 | rval[key] = None |
---|
| 1047 | return rval |
---|
| 1048 | @property |
---|
| 1049 | def parent_library( self ): |
---|
| 1050 | f = self |
---|
| 1051 | while f.parent: |
---|
| 1052 | f = f.parent |
---|
| 1053 | return f.library_root[0] |
---|
| 1054 | |
---|
| 1055 | class LibraryDataset( object ): |
---|
| 1056 | # This class acts as a proxy to the currently selected LDDA |
---|
| 1057 | upload_options = [ ( 'upload_file', 'Upload files' ), |
---|
| 1058 | ( 'upload_directory', 'Upload directory of files' ), |
---|
| 1059 | ( 'upload_paths', 'Upload files from filesystem paths' ), |
---|
| 1060 | ( 'import_from_history', 'Import datasets from your current history' ) ] |
---|
| 1061 | def __init__( self, folder=None, order_id=None, name=None, info=None, library_dataset_dataset_association=None, **kwd ): |
---|
| 1062 | self.folder = folder |
---|
| 1063 | self.order_id = order_id |
---|
| 1064 | self.name = name |
---|
| 1065 | self.info = info |
---|
| 1066 | self.library_dataset_dataset_association = library_dataset_dataset_association |
---|
| 1067 | def set_library_dataset_dataset_association( self, ldda ): |
---|
| 1068 | self.library_dataset_dataset_association = ldda |
---|
| 1069 | ldda.library_dataset = self |
---|
| 1070 | object_session( self ).add_all( ( ldda, self ) ) |
---|
| 1071 | object_session( self ).flush() |
---|
| 1072 | def get_info( self ): |
---|
| 1073 | if self.library_dataset_dataset_association: |
---|
| 1074 | return self.library_dataset_dataset_association.info |
---|
| 1075 | elif self._info: |
---|
| 1076 | return self._info |
---|
| 1077 | else: |
---|
| 1078 | return 'no info' |
---|
| 1079 | def set_info( self, info ): |
---|
| 1080 | self._info = info |
---|
| 1081 | info = property( get_info, set_info ) |
---|
| 1082 | def get_name( self ): |
---|
| 1083 | if self.library_dataset_dataset_association: |
---|
| 1084 | return self.library_dataset_dataset_association.name |
---|
| 1085 | elif self._name: |
---|
| 1086 | return self._name |
---|
| 1087 | else: |
---|
| 1088 | return 'Unnamed dataset' |
---|
| 1089 | def set_name( self, name ): |
---|
| 1090 | self._name = name |
---|
| 1091 | name = property( get_name, set_name ) |
---|
| 1092 | def display_name( self ): |
---|
| 1093 | self.library_dataset_dataset_association.display_name() |
---|
| 1094 | def get_purged( self ): |
---|
| 1095 | return self.library_dataset_dataset_association.dataset.purged |
---|
| 1096 | def set_purged( self, purged ): |
---|
| 1097 | if purged: |
---|
| 1098 | raise Exception( "Not implemented" ) |
---|
| 1099 | if not purged and self.purged: |
---|
| 1100 | raise Exception( "Cannot unpurge once purged" ) |
---|
| 1101 | purged = property( get_purged, set_purged ) |
---|
| 1102 | def get_api_value( self, view='collection' ): |
---|
| 1103 | # Since this class is a proxy to rather complex attributes we want to |
---|
| 1104 | # display in other objects, we can't use the simpler method used by |
---|
| 1105 | # other model classes. |
---|
| 1106 | ldda = self.library_dataset_dataset_association |
---|
| 1107 | template_data = {} |
---|
| 1108 | for temp_info in ldda.info_association: |
---|
| 1109 | template = temp_info.template |
---|
| 1110 | content = temp_info.info.content |
---|
| 1111 | tmp_dict = {} |
---|
| 1112 | for i, field in enumerate(template.fields): |
---|
| 1113 | tmp_dict[field['label']] = content[i] |
---|
| 1114 | template_data[template.name] = tmp_dict |
---|
| 1115 | |
---|
| 1116 | rval = dict( name = ldda.name, |
---|
| 1117 | file_name = ldda.file_name, |
---|
| 1118 | uploaded_by = ldda.user.email, |
---|
| 1119 | message = ldda.message, |
---|
| 1120 | date_uploaded = ldda.create_time.isoformat(), |
---|
| 1121 | file_size = int( ldda.get_size() ), |
---|
| 1122 | data_type = ldda.ext, |
---|
| 1123 | genome_build = ldda.dbkey, |
---|
| 1124 | misc_info = ldda.info, |
---|
| 1125 | misc_blurb = ldda.blurb, |
---|
| 1126 | template_data = template_data ) |
---|
| 1127 | for name, spec in ldda.metadata.spec.items(): |
---|
| 1128 | val = ldda.metadata.get( name ) |
---|
| 1129 | if isinstance( val, MetadataFile ): |
---|
| 1130 | val = val.file_name |
---|
| 1131 | elif isinstance( val, list ): |
---|
| 1132 | val = ', '.join( val ) |
---|
| 1133 | rval['metadata_' + name] = val |
---|
| 1134 | return rval |
---|
| 1135 | |
---|
| 1136 | class LibraryDatasetDatasetAssociation( DatasetInstance ): |
---|
| 1137 | def __init__( self, |
---|
| 1138 | copied_from_history_dataset_association=None, |
---|
| 1139 | copied_from_library_dataset_dataset_association=None, |
---|
| 1140 | library_dataset=None, |
---|
| 1141 | user=None, |
---|
| 1142 | sa_session=None, |
---|
| 1143 | **kwd ): |
---|
| 1144 | # FIXME: sa_session is must be passed to DataSetInstance if the create_dataset |
---|
| 1145 | # parameter in kwd is True so that the new object can be flushed. Is there a better way? |
---|
| 1146 | DatasetInstance.__init__( self, sa_session=sa_session, **kwd ) |
---|
| 1147 | if copied_from_history_dataset_association: |
---|
| 1148 | self.copied_from_history_dataset_association_id = copied_from_history_dataset_association.id |
---|
| 1149 | if copied_from_library_dataset_dataset_association: |
---|
| 1150 | self.copied_from_library_dataset_dataset_association_id = copied_from_library_dataset_dataset_association.id |
---|
| 1151 | self.library_dataset = library_dataset |
---|
| 1152 | self.user = user |
---|
| 1153 | def to_history_dataset_association( self, target_history, parent_id = None, add_to_history = False ): |
---|
| 1154 | hda = HistoryDatasetAssociation( name=self.name, |
---|
| 1155 | info=self.info, |
---|
| 1156 | blurb=self.blurb, |
---|
| 1157 | peek=self.peek, |
---|
| 1158 | extension=self.extension, |
---|
| 1159 | dbkey=self.dbkey, |
---|
| 1160 | dataset=self.dataset, |
---|
| 1161 | visible=self.visible, |
---|
| 1162 | deleted=self.deleted, |
---|
| 1163 | parent_id=parent_id, |
---|
| 1164 | copied_from_library_dataset_dataset_association=self, |
---|
| 1165 | history=target_history ) |
---|
| 1166 | object_session( self ).add( hda ) |
---|
| 1167 | object_session( self ).flush() |
---|
| 1168 | hda.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id |
---|
| 1169 | if add_to_history and target_history: |
---|
| 1170 | target_history.add_dataset( hda ) |
---|
| 1171 | for child in self.children: |
---|
| 1172 | child_copy = child.to_history_dataset_association( target_history = target_history, parent_id = hda.id, add_to_history = False ) |
---|
| 1173 | if not self.datatype.copy_safe_peek: |
---|
| 1174 | hda.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs |
---|
| 1175 | object_session( self ).flush() |
---|
| 1176 | return hda |
---|
| 1177 | def copy( self, copy_children = False, parent_id = None, target_folder = None ): |
---|
| 1178 | ldda = LibraryDatasetDatasetAssociation( name=self.name, |
---|
| 1179 | info=self.info, |
---|
| 1180 | blurb=self.blurb, |
---|
| 1181 | peek=self.peek, |
---|
| 1182 | extension=self.extension, |
---|
| 1183 | dbkey=self.dbkey, |
---|
| 1184 | dataset=self.dataset, |
---|
| 1185 | visible=self.visible, |
---|
| 1186 | deleted=self.deleted, |
---|
| 1187 | parent_id=parent_id, |
---|
| 1188 | copied_from_library_dataset_dataset_association=self, |
---|
| 1189 | folder=target_folder ) |
---|
| 1190 | object_session( self ).add( ldda ) |
---|
| 1191 | object_session( self ).flush() |
---|
| 1192 | # Need to set after flushed, as MetadataFiles require dataset.id |
---|
| 1193 | ldda.metadata = self.metadata |
---|
| 1194 | if copy_children: |
---|
| 1195 | for child in self.children: |
---|
| 1196 | child_copy = child.copy( copy_children = copy_children, parent_id = ldda.id ) |
---|
| 1197 | if not self.datatype.copy_safe_peek: |
---|
| 1198 | # In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs |
---|
| 1199 | ldda.set_peek() |
---|
| 1200 | object_session( self ).flush() |
---|
| 1201 | return ldda |
---|
| 1202 | def clear_associated_files( self, metadata_safe = False, purge = False ): |
---|
| 1203 | return |
---|
| 1204 | def get_access_roles( self, trans ): |
---|
| 1205 | return self.dataset.get_access_roles( trans ) |
---|
| 1206 | def get_info_association( self, restrict=False, inherited=False ): |
---|
| 1207 | # If restrict is True, we will return this ldda's info_association whether it |
---|
| 1208 | # exists or not ( in which case None will be returned ). If restrict is False, |
---|
| 1209 | # we'll return the next available info_association in the inheritable hierarchy. |
---|
| 1210 | # True is also returned if the info_association was inherited, and False if not. |
---|
| 1211 | # This enables us to eliminate displaying any contents of the inherited template. |
---|
| 1212 | if self.info_association: |
---|
| 1213 | return self.info_association[0], inherited |
---|
| 1214 | if restrict: |
---|
| 1215 | return None, inherited |
---|
| 1216 | return self.library_dataset.folder.get_info_association( inherited=True ) |
---|
| 1217 | def get_template_widgets( self, trans, get_contents=True ): |
---|
| 1218 | # See if we have any associated templatesThe get_contents |
---|
| 1219 | # param is passed by callers that are inheriting a template - these |
---|
| 1220 | # are usually new library datsets for which we want to include template |
---|
| 1221 | # fields on the upload form, but not necessarily the contents of the |
---|
| 1222 | # inherited template saved for the parent. |
---|
| 1223 | info_association, inherited = self.get_info_association() |
---|
| 1224 | if info_association: |
---|
| 1225 | if inherited: |
---|
| 1226 | template = info_association.template.current.latest_form |
---|
| 1227 | else: |
---|
| 1228 | template = info_association.template |
---|
| 1229 | # See if we have any field contents, but only if the info_association was |
---|
| 1230 | # not inherited ( we do not want to display the inherited contents ). |
---|
| 1231 | # (gvk: 8/30/10) Based on conversations with Dan, we agreed to ALWAYS inherit |
---|
| 1232 | # contents. We'll use this behavior until we hear from the community that |
---|
| 1233 | # contents should not be inherited. If we don't hear anything for a while, |
---|
| 1234 | # eliminate the old commented out behavior. |
---|
| 1235 | #if not inherited and get_contents: |
---|
| 1236 | if get_contents: |
---|
| 1237 | info = info_association.info |
---|
| 1238 | if info: |
---|
| 1239 | return template.get_widgets( trans.user, info.content ) |
---|
| 1240 | else: |
---|
| 1241 | return template.get_widgets( trans.user ) |
---|
| 1242 | return [] |
---|
| 1243 | def get_display_name( self ): |
---|
| 1244 | """ |
---|
| 1245 | LibraryDatasetDatasetAssociation name can be either a string or a unicode object. |
---|
| 1246 | If string, convert to unicode object assuming 'utf-8' format. |
---|
| 1247 | """ |
---|
| 1248 | ldda_name = self.name |
---|
| 1249 | if isinstance( ldda_name, str ): |
---|
| 1250 | ldda_name = unicode( ldda_name, 'utf-8' ) |
---|
| 1251 | return ldda_name |
---|
| 1252 | |
---|
| 1253 | class LibraryInfoAssociation( object ): |
---|
| 1254 | def __init__( self, library, form_definition, info, inheritable=False ): |
---|
| 1255 | self.library = library |
---|
| 1256 | self.template = form_definition |
---|
| 1257 | self.info = info |
---|
| 1258 | self.inheritable = inheritable |
---|
| 1259 | |
---|
| 1260 | class LibraryFolderInfoAssociation( object ): |
---|
| 1261 | def __init__( self, folder, form_definition, info, inheritable=False ): |
---|
| 1262 | self.folder = folder |
---|
| 1263 | self.template = form_definition |
---|
| 1264 | self.info = info |
---|
| 1265 | self.inheritable = inheritable |
---|
| 1266 | |
---|
| 1267 | class LibraryDatasetDatasetInfoAssociation( object ): |
---|
| 1268 | def __init__( self, library_dataset_dataset_association, form_definition, info ): |
---|
| 1269 | # TODO: need to figure out if this should be inheritable to the associated LibraryDataset |
---|
| 1270 | self.library_dataset_dataset_association = library_dataset_dataset_association |
---|
| 1271 | self.template = form_definition |
---|
| 1272 | self.info = info |
---|
| 1273 | |
---|
| 1274 | class ValidationError( object ): |
---|
| 1275 | def __init__( self, message=None, err_type=None, attributes=None ): |
---|
| 1276 | self.message = message |
---|
| 1277 | self.err_type = err_type |
---|
| 1278 | self.attributes = attributes |
---|
| 1279 | |
---|
| 1280 | class DatasetToValidationErrorAssociation( object ): |
---|
| 1281 | def __init__( self, dataset, validation_error ): |
---|
| 1282 | self.dataset = dataset |
---|
| 1283 | self.validation_error = validation_error |
---|
| 1284 | |
---|
| 1285 | class ImplicitlyConvertedDatasetAssociation( object ): |
---|
| 1286 | def __init__( self, id = None, parent = None, dataset = None, file_type = None, deleted = False, purged = False, metadata_safe = True ): |
---|
| 1287 | self.id = id |
---|
| 1288 | self.dataset = dataset |
---|
| 1289 | self.parent = parent |
---|
| 1290 | self.type = file_type |
---|
| 1291 | self.deleted = deleted |
---|
| 1292 | self.purged = purged |
---|
| 1293 | self.metadata_safe = metadata_safe |
---|
| 1294 | |
---|
| 1295 | def clear( self, purge = False ): |
---|
| 1296 | self.deleted = True |
---|
| 1297 | if self.dataset: |
---|
| 1298 | self.dataset.deleted = True |
---|
| 1299 | self.dataset.purged = purge |
---|
| 1300 | if purge: #do something with purging |
---|
| 1301 | self.purged = True |
---|
| 1302 | try: os.unlink( self.file_name ) |
---|
| 1303 | except Exception, e: print "Failed to purge associated file (%s) from disk: %s" % ( self.file_name, e ) |
---|
| 1304 | |
---|
| 1305 | class Event( object ): |
---|
| 1306 | def __init__( self, message=None, history=None, user=None, galaxy_session=None ): |
---|
| 1307 | self.history = history |
---|
| 1308 | self.galaxy_session = galaxy_session |
---|
| 1309 | self.user = user |
---|
| 1310 | self.tool_id = None |
---|
| 1311 | self.message = message |
---|
| 1312 | |
---|
| 1313 | class GalaxySession( object ): |
---|
| 1314 | def __init__( self, |
---|
| 1315 | id=None, |
---|
| 1316 | user=None, |
---|
| 1317 | remote_host=None, |
---|
| 1318 | remote_addr=None, |
---|
| 1319 | referer=None, |
---|
| 1320 | current_history=None, |
---|
| 1321 | session_key=None, |
---|
| 1322 | is_valid=False, |
---|
| 1323 | prev_session_id=None ): |
---|
| 1324 | self.id = id |
---|
| 1325 | self.user = user |
---|
| 1326 | self.remote_host = remote_host |
---|
| 1327 | self.remote_addr = remote_addr |
---|
| 1328 | self.referer = referer |
---|
| 1329 | self.current_history = current_history |
---|
| 1330 | self.session_key = session_key |
---|
| 1331 | self.is_valid = is_valid |
---|
| 1332 | self.prev_session_id = prev_session_id |
---|
| 1333 | self.histories = [] |
---|
| 1334 | def add_history( self, history, association=None ): |
---|
| 1335 | if association is None: |
---|
| 1336 | self.histories.append( GalaxySessionToHistoryAssociation( self, history ) ) |
---|
| 1337 | else: |
---|
| 1338 | self.histories.append( association ) |
---|
| 1339 | |
---|
| 1340 | class GalaxySessionToHistoryAssociation( object ): |
---|
| 1341 | def __init__( self, galaxy_session, history ): |
---|
| 1342 | self.galaxy_session = galaxy_session |
---|
| 1343 | self.history = history |
---|
| 1344 | |
---|
| 1345 | class CloudImage( object ): |
---|
| 1346 | def __init__( self ): |
---|
| 1347 | self.id = None |
---|
| 1348 | self.instance_id = None |
---|
| 1349 | self.state = None |
---|
| 1350 | |
---|
| 1351 | class UCI( object ): |
---|
| 1352 | def __init__( self ): |
---|
| 1353 | self.id = None |
---|
| 1354 | self.user = None |
---|
| 1355 | |
---|
| 1356 | class CloudInstance( object ): |
---|
| 1357 | def __init__( self ): |
---|
| 1358 | self.id = None |
---|
| 1359 | self.user = None |
---|
| 1360 | self.name = None |
---|
| 1361 | self.instance_id = None |
---|
| 1362 | self.mi = None |
---|
| 1363 | self.state = None |
---|
| 1364 | self.public_dns = None |
---|
| 1365 | self.availability_zone = None |
---|
| 1366 | |
---|
| 1367 | class CloudStore( object ): |
---|
| 1368 | def __init__( self ): |
---|
| 1369 | self.id = None |
---|
| 1370 | self.volume_id = None |
---|
| 1371 | self.user = None |
---|
| 1372 | self.size = None |
---|
| 1373 | self.availability_zone = None |
---|
| 1374 | |
---|
| 1375 | class CloudSnapshot( object ): |
---|
| 1376 | def __init__( self ): |
---|
| 1377 | self.id = None |
---|
| 1378 | self.user = None |
---|
| 1379 | self.store_id = None |
---|
| 1380 | self.snapshot_id = None |
---|
| 1381 | |
---|
| 1382 | class CloudProvider( object ): |
---|
| 1383 | def __init__( self ): |
---|
| 1384 | self.id = None |
---|
| 1385 | self.user = None |
---|
| 1386 | self.type = None |
---|
| 1387 | |
---|
| 1388 | class CloudUserCredentials( object ): |
---|
| 1389 | def __init__( self ): |
---|
| 1390 | self.id = None |
---|
| 1391 | self.user = None |
---|
| 1392 | self.name = None |
---|
| 1393 | self.accessKey = None |
---|
| 1394 | self.secretKey = None |
---|
| 1395 | self.credentials = [] |
---|
| 1396 | |
---|
| 1397 | class StoredWorkflow( object ): |
---|
| 1398 | def __init__( self ): |
---|
| 1399 | self.id = None |
---|
| 1400 | self.user = None |
---|
| 1401 | self.name = None |
---|
| 1402 | self.slug = None |
---|
| 1403 | self.published = False |
---|
| 1404 | self.latest_workflow_id = None |
---|
| 1405 | self.workflows = [] |
---|
| 1406 | |
---|
| 1407 | class Workflow( object ): |
---|
| 1408 | def __init__( self ): |
---|
| 1409 | self.user = None |
---|
| 1410 | self.name = None |
---|
| 1411 | self.has_cycles = None |
---|
| 1412 | self.has_errors = None |
---|
| 1413 | self.steps = [] |
---|
| 1414 | |
---|
| 1415 | class WorkflowStep( object ): |
---|
| 1416 | def __init__( self ): |
---|
| 1417 | self.id = None |
---|
| 1418 | self.type = None |
---|
| 1419 | self.tool_id = None |
---|
| 1420 | self.tool_inputs = None |
---|
| 1421 | self.tool_errors = None |
---|
| 1422 | self.position = None |
---|
| 1423 | self.input_connections = [] |
---|
| 1424 | self.config = None |
---|
| 1425 | |
---|
| 1426 | class WorkflowStepConnection( object ): |
---|
| 1427 | def __init__( self ): |
---|
| 1428 | self.output_step_id = None |
---|
| 1429 | self.output_name = None |
---|
| 1430 | self.input_step_id = None |
---|
| 1431 | self.input_name = None |
---|
| 1432 | |
---|
| 1433 | class WorkflowOutput(object): |
---|
| 1434 | def __init__( self, workflow_step, output_name): |
---|
| 1435 | self.workflow_step = workflow_step |
---|
| 1436 | self.output_name = output_name |
---|
| 1437 | |
---|
| 1438 | class StoredWorkflowUserShareAssociation( object ): |
---|
| 1439 | def __init__( self ): |
---|
| 1440 | self.stored_workflow = None |
---|
| 1441 | self.user = None |
---|
| 1442 | |
---|
| 1443 | class StoredWorkflowMenuEntry( object ): |
---|
| 1444 | def __init__( self ): |
---|
| 1445 | self.stored_workflow = None |
---|
| 1446 | self.user = None |
---|
| 1447 | self.order_index = None |
---|
| 1448 | |
---|
| 1449 | class WorkflowInvocation( object ): |
---|
| 1450 | pass |
---|
| 1451 | |
---|
| 1452 | class WorkflowInvocationStep( object ): |
---|
| 1453 | pass |
---|
| 1454 | |
---|
| 1455 | class MetadataFile( object ): |
---|
| 1456 | def __init__( self, dataset = None, name = None ): |
---|
| 1457 | if isinstance( dataset, HistoryDatasetAssociation ): |
---|
| 1458 | self.history_dataset = dataset |
---|
| 1459 | elif isinstance( dataset, LibraryDatasetDatasetAssociation ): |
---|
| 1460 | self.library_dataset = dataset |
---|
| 1461 | self.name = name |
---|
| 1462 | @property |
---|
| 1463 | def file_name( self ): |
---|
| 1464 | assert self.id is not None, "ID must be set before filename used (commit the object)" |
---|
| 1465 | path = os.path.join( Dataset.file_path, '_metadata_files', *directory_hash_id( self.id ) ) |
---|
| 1466 | # Create directory if it does not exist |
---|
| 1467 | try: |
---|
| 1468 | os.makedirs( path ) |
---|
| 1469 | except OSError, e: |
---|
| 1470 | # File Exists is okay, otherwise reraise |
---|
| 1471 | if e.errno != errno.EEXIST: |
---|
| 1472 | raise |
---|
| 1473 | # Return filename inside hashed directory |
---|
| 1474 | return os.path.abspath( os.path.join( path, "metadata_%d.dat" % self.id ) ) |
---|
| 1475 | |
---|
| 1476 | class FormDefinition( object ): |
---|
| 1477 | types = Bunch( REQUEST = 'Sequencing Request Form', |
---|
| 1478 | SAMPLE = 'Sequencing Sample Form', |
---|
| 1479 | LIBRARY_INFO_TEMPLATE = 'Library information template', |
---|
| 1480 | USER_INFO = 'User Information' ) |
---|
| 1481 | def __init__(self, name=None, desc=None, fields=[], |
---|
| 1482 | form_definition_current=None, form_type=None, layout=None): |
---|
| 1483 | self.name = name |
---|
| 1484 | self.desc = desc |
---|
| 1485 | self.fields = fields |
---|
| 1486 | self.form_definition_current = form_definition_current |
---|
| 1487 | self.type = form_type |
---|
| 1488 | self.layout = layout |
---|
| 1489 | def fields_of_grid(self, grid_index): |
---|
| 1490 | ''' |
---|
| 1491 | This method returns the list of fields belonging to the given grid. |
---|
| 1492 | ''' |
---|
| 1493 | gridfields = {} |
---|
| 1494 | for i, f in enumerate(self.fields): |
---|
| 1495 | if str(f['layout']) == str(grid_index): |
---|
| 1496 | gridfields[i] = f |
---|
| 1497 | return gridfields |
---|
| 1498 | def get_widgets( self, user, contents=[], **kwd ): |
---|
| 1499 | ''' |
---|
| 1500 | Return the list of widgets that comprise a form definition, |
---|
| 1501 | including field contents if any. |
---|
| 1502 | ''' |
---|
| 1503 | params = util.Params( kwd ) |
---|
| 1504 | widgets = [] |
---|
| 1505 | for index, field in enumerate( self.fields ): |
---|
| 1506 | field_type = field[ 'type' ] |
---|
| 1507 | field_name = 'field_%i' % index |
---|
| 1508 | # determine the value of the field |
---|
| 1509 | if field_name in kwd: |
---|
| 1510 | # The form was submitted via refresh_on_change |
---|
| 1511 | if field_type == 'CheckboxField': |
---|
| 1512 | value = CheckboxField.is_checked( params.get( field_name, False ) ) |
---|
| 1513 | else: |
---|
| 1514 | value = util.restore_text( params.get( field_name, '' ) ) |
---|
| 1515 | elif contents: |
---|
| 1516 | try: |
---|
| 1517 | # This field has a saved value. |
---|
| 1518 | value = str( contents[ index ] ) |
---|
| 1519 | except: |
---|
| 1520 | # If there was an error getting the saved value, we'll still |
---|
| 1521 | # display the widget, but it will be empty. |
---|
| 1522 | if field_type == 'CheckboxField': |
---|
| 1523 | # Since we do not have contents, set checkbox value to False |
---|
| 1524 | value = False |
---|
| 1525 | else: |
---|
| 1526 | # Set other field types to empty string |
---|
| 1527 | value = '' |
---|
| 1528 | else: |
---|
| 1529 | # if none of the above, then leave the field empty |
---|
| 1530 | if field_type == 'CheckboxField': |
---|
| 1531 | # Since we do not have contents, set checkbox value to False |
---|
| 1532 | value = False |
---|
| 1533 | else: |
---|
| 1534 | # Set other field types to the default value of the field |
---|
| 1535 | value = field.get('default', '') |
---|
| 1536 | # Create the field widget |
---|
| 1537 | field_widget = eval( field_type )( field_name ) |
---|
| 1538 | if field_type == 'TextField': |
---|
| 1539 | field_widget.set_size( 40 ) |
---|
| 1540 | field_widget.value = value |
---|
| 1541 | elif field_type == 'TextArea': |
---|
| 1542 | field_widget.set_size( 3, 40 ) |
---|
| 1543 | field_widget.value = value |
---|
| 1544 | elif field_type == 'AddressField': |
---|
| 1545 | field_widget.user = user |
---|
| 1546 | field_widget.value = value |
---|
| 1547 | field_widget.params = params |
---|
| 1548 | elif field['type'] == 'WorkflowField': |
---|
| 1549 | field_widget.user = user |
---|
| 1550 | field_widget.value = value |
---|
| 1551 | field_widget.params = params |
---|
| 1552 | elif field_type == 'SelectField': |
---|
| 1553 | for option in field[ 'selectlist' ]: |
---|
| 1554 | if option == value: |
---|
| 1555 | field_widget.add_option( option, option, selected=True ) |
---|
| 1556 | else: |
---|
| 1557 | field_widget.add_option( option, option ) |
---|
| 1558 | elif field_type == 'CheckboxField': |
---|
| 1559 | field_widget.set_checked( value ) |
---|
| 1560 | if field[ 'required' ] == 'required': |
---|
| 1561 | req = 'Required' |
---|
| 1562 | else: |
---|
| 1563 | req = 'Optional' |
---|
| 1564 | if field[ 'helptext' ]: |
---|
| 1565 | helptext='%s (%s)' % ( field[ 'helptext' ], req ) |
---|
| 1566 | else: |
---|
| 1567 | helptext = '' |
---|
| 1568 | widgets.append( dict( label=field[ 'label' ], |
---|
| 1569 | widget=field_widget, |
---|
| 1570 | helptext=helptext ) ) |
---|
| 1571 | return widgets |
---|
| 1572 | |
---|
| 1573 | class FormDefinitionCurrent( object ): |
---|
| 1574 | def __init__(self, form_definition=None): |
---|
| 1575 | self.latest_form = form_definition |
---|
| 1576 | |
---|
| 1577 | class FormValues( object ): |
---|
| 1578 | def __init__(self, form_def=None, content=None): |
---|
| 1579 | self.form_definition = form_def |
---|
| 1580 | self.content = content |
---|
| 1581 | |
---|
| 1582 | class Request( object ): |
---|
| 1583 | states = Bunch( NEW = 'New', |
---|
| 1584 | SUBMITTED = 'In Progress', |
---|
| 1585 | REJECTED = 'Rejected', |
---|
| 1586 | COMPLETE = 'Complete' ) |
---|
| 1587 | def __init__( self, name=None, desc=None, request_type=None, user=None, form_values=None, notification=None ): |
---|
| 1588 | self.name = name |
---|
| 1589 | self.desc = desc |
---|
| 1590 | self.type = request_type |
---|
| 1591 | self.values = form_values |
---|
| 1592 | self.user = user |
---|
| 1593 | self.notification = notification |
---|
| 1594 | self.samples_list = [] |
---|
| 1595 | @property |
---|
| 1596 | def state( self ): |
---|
| 1597 | latest_event = self.latest_event |
---|
| 1598 | if latest_event: |
---|
| 1599 | return latest_event.state |
---|
| 1600 | return None |
---|
| 1601 | @property |
---|
| 1602 | def latest_event( self ): |
---|
| 1603 | if self.events: |
---|
| 1604 | return self.events[0] |
---|
| 1605 | return None |
---|
| 1606 | @property |
---|
| 1607 | def samples_have_common_state( self ): |
---|
| 1608 | """ |
---|
| 1609 | Returns the state of this request's samples when they are all |
---|
| 1610 | in one common state. Otherwise returns False. |
---|
| 1611 | """ |
---|
| 1612 | state_for_comparison = self.samples[0].state |
---|
| 1613 | if state_for_comparison is None: |
---|
| 1614 | for s in self.samples: |
---|
| 1615 | if s.state is not None: |
---|
| 1616 | return False |
---|
| 1617 | for s in self.samples: |
---|
| 1618 | if s.state.id != state_for_comparison.id: |
---|
| 1619 | return False |
---|
| 1620 | return state_for_comparison |
---|
| 1621 | @property |
---|
| 1622 | def last_comment( self ): |
---|
| 1623 | latest_event = self.latest_event |
---|
| 1624 | if latest_event: |
---|
| 1625 | if latest_event.comment: |
---|
| 1626 | return latest_event.comment |
---|
| 1627 | return '' |
---|
| 1628 | return 'No comment' |
---|
| 1629 | def has_sample( self, sample_name ): |
---|
| 1630 | for s in self.samples: |
---|
| 1631 | if s.name == sample_name: |
---|
| 1632 | return s |
---|
| 1633 | return False |
---|
| 1634 | @property |
---|
| 1635 | def is_unsubmitted( self ): |
---|
| 1636 | return self.state in [ self.states.REJECTED, self.states.NEW ] |
---|
| 1637 | @property |
---|
| 1638 | def is_rejected( self ): |
---|
| 1639 | return self.state == self.states.REJECTED |
---|
| 1640 | @property |
---|
| 1641 | def is_submitted( self ): |
---|
| 1642 | return self.state == self.states.SUBMITTED |
---|
| 1643 | @property |
---|
| 1644 | def is_new( self ): |
---|
| 1645 | return self.state == self.states.NEW |
---|
| 1646 | @property |
---|
| 1647 | def is_complete( self ): |
---|
| 1648 | return self.state == self.states.COMPLETE |
---|
| 1649 | @property |
---|
| 1650 | def samples_without_library_destinations( self ): |
---|
| 1651 | # Return all samples that are not associated with a library |
---|
| 1652 | samples = [] |
---|
| 1653 | for sample in self.samples: |
---|
| 1654 | if not sample.library: |
---|
| 1655 | samples.append( sample ) |
---|
| 1656 | return samples |
---|
| 1657 | def send_email_notification( self, trans, common_state, final_state=False ): |
---|
| 1658 | # Check if an email notification is configured to be sent when the samples |
---|
| 1659 | # are in this state |
---|
| 1660 | if self.notification and common_state.id not in self.notification[ 'sample_states' ]: |
---|
| 1661 | return |
---|
| 1662 | comments = '' |
---|
| 1663 | # Send email |
---|
| 1664 | if trans.app.config.smtp_server is not None and self.notification and self.notification[ 'email' ]: |
---|
| 1665 | host = trans.request.host.split( ':' )[0] |
---|
| 1666 | if host in [ 'localhost', '127.0.0.1' ]: |
---|
| 1667 | host = socket.getfqdn() |
---|
| 1668 | body = """ |
---|
| 1669 | Galaxy Sample Tracking Notification |
---|
| 1670 | =================================== |
---|
| 1671 | |
---|
| 1672 | User: %(user)s |
---|
| 1673 | |
---|
| 1674 | Sequencing request: %(request_name)s |
---|
| 1675 | Sequencer configuration: %(request_type)s |
---|
| 1676 | Sequencing request state: %(request_state)s |
---|
| 1677 | |
---|
| 1678 | Number of samples: %(num_samples)s |
---|
| 1679 | All samples in state: %(sample_state)s |
---|
| 1680 | |
---|
| 1681 | """ |
---|
| 1682 | values = dict( user=self.user.email, |
---|
| 1683 | request_name=self.name, |
---|
| 1684 | request_type=self.type.name, |
---|
| 1685 | request_state=self.state, |
---|
| 1686 | num_samples=str( len( self.samples ) ), |
---|
| 1687 | sample_state=common_state.name, |
---|
| 1688 | create_time=self.create_time, |
---|
| 1689 | submit_time=self.create_time ) |
---|
| 1690 | body = body % values |
---|
| 1691 | # check if this is the final state of the samples |
---|
| 1692 | if final_state: |
---|
| 1693 | txt = "Sample Name -> Data Library/Folder\r\n" |
---|
| 1694 | for s in self.samples: |
---|
| 1695 | txt = txt + "%s -> %s/%s\r\n" % ( s.name, s.library.name, s.folder.name ) |
---|
| 1696 | body = body + txt |
---|
| 1697 | to = self.notification['email'] |
---|
| 1698 | frm = 'galaxy-no-reply@' + host |
---|
| 1699 | subject = "Galaxy Sample Tracking notification: '%s' sequencing request" % self.name |
---|
| 1700 | message = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" % ( frm, ", ".join( to ), subject, body ) |
---|
| 1701 | try: |
---|
| 1702 | s = smtplib.SMTP() |
---|
| 1703 | s.connect( trans.app.config.smtp_server ) |
---|
| 1704 | s.sendmail( frm, to, message ) |
---|
| 1705 | s.quit() |
---|
| 1706 | comments = "Email notification sent to %s." % ", ".join( to ).strip().strip( ',' ) |
---|
| 1707 | except: |
---|
| 1708 | comments = "Email notification failed." |
---|
| 1709 | # update the request history with the email notification event |
---|
| 1710 | elif not trans.app.config.smtp_server: |
---|
| 1711 | comments = "Email notification failed as SMTP server not set in config file" |
---|
| 1712 | if comments: |
---|
| 1713 | event = trans.app.model.RequestEvent( self, self.state, comments ) |
---|
| 1714 | trans.sa_session.add( event ) |
---|
| 1715 | trans.sa_session.flush() |
---|
| 1716 | return comments |
---|
| 1717 | |
---|
| 1718 | class RequestEvent( object ): |
---|
| 1719 | def __init__(self, request=None, request_state=None, comment=''): |
---|
| 1720 | self.request = request |
---|
| 1721 | self.state = request_state |
---|
| 1722 | self.comment = comment |
---|
| 1723 | |
---|
| 1724 | class RequestType( object ): |
---|
| 1725 | rename_dataset_options = Bunch( NO = 'Do not rename', |
---|
| 1726 | SAMPLE_NAME = 'Preprend sample name', |
---|
| 1727 | EXPERIMENT_NAME = 'Prepend experiment name', |
---|
| 1728 | EXPERIMENT_AND_SAMPLE_NAME = 'Prepend experiment and sample name') |
---|
| 1729 | permitted_actions = get_permitted_actions( filter='REQUEST_TYPE' ) |
---|
| 1730 | def __init__( self, name=None, desc=None, request_form=None, sample_form=None, datatx_info=None ): |
---|
| 1731 | self.name = name |
---|
| 1732 | self.desc = desc |
---|
| 1733 | self.request_form = request_form |
---|
| 1734 | self.sample_form = sample_form |
---|
| 1735 | self.datatx_info = datatx_info |
---|
| 1736 | @property |
---|
| 1737 | def state( self ): |
---|
| 1738 | # The states mapper for this object orders ascending |
---|
| 1739 | return self.states[-1] |
---|
| 1740 | |
---|
| 1741 | class RequestTypePermissions( object ): |
---|
| 1742 | def __init__( self, action, request_type, role ): |
---|
| 1743 | self.action = action |
---|
| 1744 | self.request_type = request_type |
---|
| 1745 | self.role = role |
---|
| 1746 | |
---|
| 1747 | class Sample( object ): |
---|
| 1748 | bulk_operations = Bunch( CHANGE_STATE = 'Change state', |
---|
| 1749 | SELECT_LIBRARY = 'Select data library and folder' ) |
---|
| 1750 | transfer_status = Bunch( NOT_STARTED = 'Not started', |
---|
| 1751 | IN_QUEUE = 'In queue', |
---|
| 1752 | TRANSFERRING = 'Transferring dataset', |
---|
| 1753 | ADD_TO_LIBRARY = 'Adding to data library', |
---|
| 1754 | COMPLETE = 'Complete', |
---|
| 1755 | ERROR = 'Error' ) |
---|
| 1756 | def __init__(self, name=None, desc=None, request=None, form_values=None, bar_code=None, library=None, folder=None): |
---|
| 1757 | self.name = name |
---|
| 1758 | self.desc = desc |
---|
| 1759 | self.request = request |
---|
| 1760 | self.values = form_values |
---|
| 1761 | self.bar_code = bar_code |
---|
| 1762 | self.library = library |
---|
| 1763 | self.folder = folder |
---|
| 1764 | @property |
---|
| 1765 | def state( self ): |
---|
| 1766 | latest_event = self.latest_event |
---|
| 1767 | if latest_event: |
---|
| 1768 | return latest_event.state |
---|
| 1769 | return None |
---|
| 1770 | @property |
---|
| 1771 | def latest_event( self ): |
---|
| 1772 | if self.events: |
---|
| 1773 | return self.events[0] |
---|
| 1774 | return None |
---|
| 1775 | @property |
---|
| 1776 | def untransferred_dataset_files( self ): |
---|
| 1777 | untransferred_datasets = [] |
---|
| 1778 | for dataset in self.datasets: |
---|
| 1779 | if dataset.status == self.transfer_status.NOT_STARTED: |
---|
| 1780 | untransferred_datasets.append( dataset ) |
---|
| 1781 | return untransferred_datasets |
---|
| 1782 | @property |
---|
| 1783 | def inprogress_dataset_files( self ): |
---|
| 1784 | inprogress_datasets = [] |
---|
| 1785 | for dataset in self.datasets: |
---|
| 1786 | if dataset.status not in [ self.transfer_status.NOT_STARTED, self.transfer_status.COMPLETE ]: |
---|
| 1787 | inprogress_datasets.append( dataset ) |
---|
| 1788 | return inprogress_datasets |
---|
| 1789 | @property |
---|
| 1790 | def transferred_dataset_files( self ): |
---|
| 1791 | transferred_datasets = [] |
---|
| 1792 | for dataset in self.datasets: |
---|
| 1793 | if dataset.status == self.transfer_status.COMPLETE: |
---|
| 1794 | transferred_datasets.append( dataset ) |
---|
| 1795 | return transferred_datasets |
---|
| 1796 | def dataset_size( self, filepath ): |
---|
| 1797 | def print_ticks(d): |
---|
| 1798 | pass |
---|
| 1799 | datatx_info = self.request.type.datatx_info |
---|
| 1800 | cmd = 'ssh %s@%s "du -sh \'%s\'"' % ( datatx_info['username'], |
---|
| 1801 | datatx_info['host'], |
---|
| 1802 | filepath) |
---|
| 1803 | output = pexpect.run(cmd, events={'.ssword:*': datatx_info['password']+'\r\n', |
---|
| 1804 | pexpect.TIMEOUT:print_ticks}, |
---|
| 1805 | timeout=10) |
---|
| 1806 | return output.replace(filepath, '').strip() |
---|
| 1807 | |
---|
| 1808 | class SampleState( object ): |
---|
| 1809 | def __init__(self, name=None, desc=None, request_type=None): |
---|
| 1810 | self.name = name |
---|
| 1811 | self.desc = desc |
---|
| 1812 | self.request_type = request_type |
---|
| 1813 | |
---|
| 1814 | class SampleEvent( object ): |
---|
| 1815 | def __init__(self, sample=None, sample_state=None, comment=''): |
---|
| 1816 | self.sample = sample |
---|
| 1817 | self.state = sample_state |
---|
| 1818 | self.comment = comment |
---|
| 1819 | |
---|
| 1820 | class SampleDataset( object ): |
---|
| 1821 | def __init__(self, sample=None, name=None, file_path=None, |
---|
| 1822 | status=None, error_msg=None, size=None): |
---|
| 1823 | self.sample = sample |
---|
| 1824 | self.name = name |
---|
| 1825 | self.file_path = file_path |
---|
| 1826 | self.status = status |
---|
| 1827 | self.error_msg = error_msg |
---|
| 1828 | self.size = size |
---|
| 1829 | |
---|
| 1830 | class UserAddress( object ): |
---|
| 1831 | def __init__(self, user=None, desc=None, name=None, institution=None, |
---|
| 1832 | address=None, city=None, state=None, postal_code=None, |
---|
| 1833 | country=None, phone=None): |
---|
| 1834 | self.user = user |
---|
| 1835 | self.desc = desc |
---|
| 1836 | self.name = name |
---|
| 1837 | self.institution = institution |
---|
| 1838 | self.address = address |
---|
| 1839 | self.city = city |
---|
| 1840 | self.state = state |
---|
| 1841 | self.postal_code = postal_code |
---|
| 1842 | self.country = country |
---|
| 1843 | self.phone = phone |
---|
| 1844 | def get_html(self): |
---|
| 1845 | html = '' |
---|
| 1846 | if self.name: |
---|
| 1847 | html = html + self.name |
---|
| 1848 | if self.institution: |
---|
| 1849 | html = html + '<br/>' + self.institution |
---|
| 1850 | if self.address: |
---|
| 1851 | html = html + '<br/>' + self.address |
---|
| 1852 | if self.city: |
---|
| 1853 | html = html + '<br/>' + self.city |
---|
| 1854 | if self.state: |
---|
| 1855 | html = html + ' ' + self.state |
---|
| 1856 | if self.postal_code: |
---|
| 1857 | html = html + ' ' + self.postal_code |
---|
| 1858 | if self.country: |
---|
| 1859 | html = html + '<br/>' + self.country |
---|
| 1860 | if self.phone: |
---|
| 1861 | html = html + '<br/>' + 'Phone: ' + self.phone |
---|
| 1862 | return html |
---|
| 1863 | |
---|
| 1864 | class Page( object ): |
---|
| 1865 | def __init__( self ): |
---|
| 1866 | self.id = None |
---|
| 1867 | self.user = None |
---|
| 1868 | self.title = None |
---|
| 1869 | self.slug = None |
---|
| 1870 | self.latest_revision_id = None |
---|
| 1871 | self.revisions = [] |
---|
| 1872 | self.importable = None |
---|
| 1873 | self.published = None |
---|
| 1874 | |
---|
| 1875 | class PageRevision( object ): |
---|
| 1876 | def __init__( self ): |
---|
| 1877 | self.user = None |
---|
| 1878 | self.title = None |
---|
| 1879 | self.content = None |
---|
| 1880 | |
---|
| 1881 | class PageUserShareAssociation( object ): |
---|
| 1882 | def __init__( self ): |
---|
| 1883 | self.page = None |
---|
| 1884 | self.user = None |
---|
| 1885 | |
---|
| 1886 | class Visualization( object ): |
---|
| 1887 | def __init__( self ): |
---|
| 1888 | self.id = None |
---|
| 1889 | self.user = None |
---|
| 1890 | self.type = None |
---|
| 1891 | self.title = None |
---|
| 1892 | self.latest_revision = None |
---|
| 1893 | self.revisions = [] |
---|
| 1894 | |
---|
| 1895 | class VisualizationRevision( object ): |
---|
| 1896 | def __init__( self ): |
---|
| 1897 | self.id = None |
---|
| 1898 | self.visualization = None |
---|
| 1899 | self.title = None |
---|
| 1900 | self.config = None |
---|
| 1901 | |
---|
| 1902 | class VisualizationUserShareAssociation( object ): |
---|
| 1903 | def __init__( self ): |
---|
| 1904 | self.visualization = None |
---|
| 1905 | self.user = None |
---|
| 1906 | |
---|
| 1907 | class Tag ( object ): |
---|
| 1908 | def __init__( self, id=None, type=None, parent_id=None, name=None ): |
---|
| 1909 | self.id = id |
---|
| 1910 | self.type = type |
---|
| 1911 | self.parent_id = parent_id |
---|
| 1912 | self.name = name |
---|
| 1913 | |
---|
| 1914 | def __str__ ( self ): |
---|
| 1915 | return "Tag(id=%s, type=%i, parent_id=%s, name=%s)" % ( self.id, self.type, self.parent_id, self.name ) |
---|
| 1916 | |
---|
| 1917 | class ItemTagAssociation ( object ): |
---|
| 1918 | def __init__( self, id=None, user=None, item_id=None, tag_id=None, user_tname=None, value=None ): |
---|
| 1919 | self.id = id |
---|
| 1920 | self.user = user |
---|
| 1921 | self.item_id = item_id |
---|
| 1922 | self.tag_id = tag_id |
---|
| 1923 | self.user_tname = user_tname |
---|
| 1924 | self.value = None |
---|
| 1925 | self.user_value = None |
---|
| 1926 | |
---|
| 1927 | class HistoryTagAssociation ( ItemTagAssociation ): |
---|
| 1928 | pass |
---|
| 1929 | |
---|
| 1930 | class DatasetTagAssociation ( ItemTagAssociation ): |
---|
| 1931 | pass |
---|
| 1932 | |
---|
| 1933 | class HistoryDatasetAssociationTagAssociation ( ItemTagAssociation ): |
---|
| 1934 | pass |
---|
| 1935 | |
---|
| 1936 | class PageTagAssociation ( ItemTagAssociation ): |
---|
| 1937 | pass |
---|
| 1938 | |
---|
| 1939 | class WorkflowStepTagAssociation ( ItemTagAssociation ): |
---|
| 1940 | pass |
---|
| 1941 | |
---|
| 1942 | class StoredWorkflowTagAssociation ( ItemTagAssociation ): |
---|
| 1943 | pass |
---|
| 1944 | |
---|
| 1945 | class VisualizationTagAssociation ( ItemTagAssociation ): |
---|
| 1946 | pass |
---|
| 1947 | |
---|
| 1948 | # Item annotation classes. |
---|
| 1949 | |
---|
| 1950 | class HistoryAnnotationAssociation( object ): |
---|
| 1951 | pass |
---|
| 1952 | |
---|
| 1953 | class HistoryDatasetAssociationAnnotationAssociation( object ): |
---|
| 1954 | pass |
---|
| 1955 | |
---|
| 1956 | class StoredWorkflowAnnotationAssociation( object ): |
---|
| 1957 | pass |
---|
| 1958 | |
---|
| 1959 | class WorkflowStepAnnotationAssociation( object ): |
---|
| 1960 | pass |
---|
| 1961 | |
---|
| 1962 | class PageAnnotationAssociation( object ): |
---|
| 1963 | pass |
---|
| 1964 | |
---|
| 1965 | class VisualizationAnnotationAssociation( object ): |
---|
| 1966 | pass |
---|
| 1967 | |
---|
| 1968 | # Item rating classes. |
---|
| 1969 | |
---|
| 1970 | class ItemRatingAssociation( object ): |
---|
| 1971 | def __init__( self, id=None, user=None, item=None, rating=0 ): |
---|
| 1972 | self.id = id |
---|
| 1973 | self.user = user |
---|
| 1974 | self.item = item |
---|
| 1975 | self.rating = rating |
---|
| 1976 | |
---|
| 1977 | def set_item( self, item ): |
---|
| 1978 | """ Set association's item. """ |
---|
| 1979 | pass |
---|
| 1980 | |
---|
| 1981 | class HistoryRatingAssociation( ItemRatingAssociation ): |
---|
| 1982 | def set_item( self, history ): |
---|
| 1983 | self.history = history |
---|
| 1984 | |
---|
| 1985 | class HistoryDatasetAssociationRatingAssociation( ItemRatingAssociation ): |
---|
| 1986 | def set_item( self, history_dataset_association ): |
---|
| 1987 | self.history_dataset_association = history_dataset_association |
---|
| 1988 | |
---|
| 1989 | class StoredWorkflowRatingAssociation( ItemRatingAssociation ): |
---|
| 1990 | def set_item( self, stored_workflow ): |
---|
| 1991 | self.stored_workflow = stored_workflow |
---|
| 1992 | |
---|
| 1993 | class PageRatingAssociation( ItemRatingAssociation ): |
---|
| 1994 | def set_item( self, page ): |
---|
| 1995 | self.page = page |
---|
| 1996 | |
---|
| 1997 | class VisualizationRatingAssociation( ItemRatingAssociation ): |
---|
| 1998 | def set_item( self, visualization ): |
---|
| 1999 | self.visualization = visualization |
---|
| 2000 | |
---|
| 2001 | class UserPreference ( object ): |
---|
| 2002 | def __init__( self, name=None, value=None ): |
---|
| 2003 | self.name = name |
---|
| 2004 | self.value = value |
---|
| 2005 | |
---|
| 2006 | class UserAction( object ): |
---|
| 2007 | def __init__( self, id=None, create_time=None, user_id=None, session_id=None, action=None, params=None, context=None): |
---|
| 2008 | self.id = id |
---|
| 2009 | self.create_time = create_time |
---|
| 2010 | self.user_id = user_id |
---|
| 2011 | self.session_id = session_id |
---|
| 2012 | self.action = action |
---|
| 2013 | self.params = params |
---|
| 2014 | self.context = context |
---|
| 2015 | |
---|
| 2016 | class APIKeys( object ): |
---|
| 2017 | pass |
---|
| 2018 | |
---|
| 2019 | ## ---- Utility methods ------------------------------------------------------- |
---|
| 2020 | |
---|
| 2021 | def directory_hash_id( id ): |
---|
| 2022 | s = str( id ) |
---|
| 2023 | l = len( s ) |
---|
| 2024 | # Shortcut -- ids 0-999 go under ../000/ |
---|
| 2025 | if l < 4: |
---|
| 2026 | return [ "000" ] |
---|
| 2027 | # Pad with zeros until a multiple of three |
---|
| 2028 | padded = ( ( 3 - len( s ) % 3 ) * "0" ) + s |
---|
| 2029 | # Drop the last three digits -- 1000 files per directory |
---|
| 2030 | padded = padded[:-3] |
---|
| 2031 | # Break into chunks of three |
---|
| 2032 | return [ padded[i*3:(i+1)*3] for i in range( len( padded ) // 3 ) ] |
---|