def update_sample(user: User, sample: Sample, new_data: Dict[str, Any], filename: str = None) -> Sample: """ Change the attributes of the sample file with sample_id If a key exists in both the file and the db, it will be updated in both. :param user: :param sample: :param new_data: :param filename: :return: """ if is_write_permitted(user, sample): # file attributes and database attributes should be separated if 'id' in new_data: if sample.id != int(new_data['id']) and Sample.query.filter_by( id=new_data['id']) is not None: raise ValueError( f'Sample with id {new_data["id"]} already exists!') if 'sample_group_ids' in new_data: new_data['sample_group_ids'] = [ int(sample_group_id) for sample_group_id in new_data['sample_group_ids'] ] new_sample_groups = [ get_sample_group(user, sample_group_id) for sample_group_id in new_data['sample_group_ids'] ] remove_sample_groups = [ sample_group for sample_group in sample.sample_groups if sample_group.id not in new_data['sample_group_ids'] ] for sample_group in new_sample_groups: if not is_write_permitted(user, sample_group): raise AuthException( f'User {user.email} is not permitted to attach sample {sample.id} to sample group {sample_group.id}' ) for sample_group in remove_sample_groups: if not is_write_permitted(user, sample_group): raise AuthException( f'User {user.email} is not permitted to detach sample {sample.id} from sample group {sample_group.id}' ) sample.sample_groups = new_sample_groups sample.update(new_data) if 'file_info' in new_data: mdt.update_metadata(sample.filename, new_data['file_info']) if filename is not None: os.remove(sample.filename) shutil.copy(filename, sample.filename) os.remove(filename) sample.last_editor = user sample.filename = f'/data/samples/{sample.id}.h5' db.session.commit() return sample raise AuthException( f'User {user.email} is not permitted to modify sample {sample.id}')
def update_workflow(user: User, workflow: Workflow, new_data: Dict[str, Any], filename: str = None) -> Workflow: """ Update workflow metadata. :param user: :param workflow: :param new_data: :parma filename: :return: """ if is_write_permitted(user, workflow): if 'id' in new_data: if workflow.id != int(new_data['id']) and Workflow.query.filter_by( id=new_data['id']) is not None: raise ValueError( f'Workflow with id {new_data["id"]} already exists!') if 'analysis_ids' in new_data: new_analyses = [ get_analysis(user, analysis_id) for analysis_id in new_data['analysis_ids'] ] remove_analyses = [ analysis for analysis in workflow.analyses if analysis.id not in new_data['analysis_ids'] ] for analysis in new_analyses: if not is_write_permitted(user, analysis): raise AuthException( f'User {user.email} is not permitted to attach workflow {workflow.id} to analysis {analysis.id}' ) for analysis in remove_analyses: if not is_write_permitted(user, analysis): raise AuthException( f'User {user.email} is not permitted to detach workflow {workflow.id} from analysis {analysis.id}' ) workflow.analyses = new_analyses workflow.update(new_data) if 'workflow_definition' in new_data: if workflow.file_type == 'json': json.dump(new_data['workflow_definition'], open(workflow.filename, 'w+')) elif workflow.file_type == 'yaml': yaml.dump(new_data['workflow_definition'], open(workflow.filename, 'w+')) else: open(workflow.filename, 'w+').write(new_data['workflow_definition']) if filename is not None: os.remove(workflow.filename) shutil.copy(filename, workflow.filename) os.remove(filename) db.session.commit() return workflow raise AuthException( f'User {user.email} is not permitted to modify workflow {workflow.id}')
def update_external_file(user: User, external_file: ExternalFile, new_data: Dict[str, Any], move_file: bool = False, filename: str = None) -> ExternalFile: """ Update the data in the external file record :param user: :param external_file: :param new_data: :param move_file: :param filename: :return: """ if is_write_permitted(user, external_file): if 'id' in new_data: if external_file.id != int( new_data['id']) and ExternalFile.query.filter_by( id=new_data['id']) is not None: raise ValueError( f'External file with id {new_data["id"]} already exists!') if 'analysis_ids' in new_data: new_analyses = [ get_analysis(user, analysis_id) for analysis_id in new_data['analysis_ids'] ] remove_analyses = [ analysis for analysis in external_file.analyses if analysis.id not in new_data['analysis_ids'] ] for analysis in new_analyses: if not is_write_permitted(user, analysis): raise AuthException( f'User {user.email} is not permitted to attach external file {external_file.id} to analysis {analysis.id}' ) for analysis in remove_analyses: if not is_write_permitted(user, analysis): raise AuthException( f'User {user.email} is not permitted to detach external file {external_file.id} from analysis {analysis.id}' ) external_file.analyses = new_analyses if move_file and 'filename' in new_data: original_filename = external_file.filename shutil.copy(original_filename, new_data['filename']) os.remove(original_filename) if filename is not None: os.remove(external_file.filename) shutil.copy(filename, external_file.filename) os.remove(filename) external_file.update(new_data) external_file.last_editor = user db.session.commit() return external_file raise AuthException( f'User {user.id} is not permitted to modify external file record {external_file.id}' )
def __init__(self, current_user: User, records: List[Union[Sample, Collection]], title: str, special_vals: List[bool] = None, special_val_heading=None): super(FileListTableData, self).__init__(current_user) self.title = title self.special_val_heading = special_val_heading if special_val_heading is not None else '' if special_vals is None: special_vals = [None for _ in records] self.rows = [ FileListTableRow(record, is_write_permitted(current_user, record), special_val, special_val_heading) for record, special_val in zip(records, special_vals) ] if len(self.rows): self.headings = ['ID'] + [ key for key in self.rows[0].values.keys() if key not in {'ID', special_val_heading} and all([key in row.values for row in self.rows]) ] if 'ID' not in self.rows[0].values.keys(): self.headings.remove('ID') if len(special_vals) and special_vals[0] is not None: self.headings.remove('ID') self.headings = [special_val_heading] + self.headings if 'ID' in self.rows[0].values.keys(): self.headings = ['ID'] + self.headings
def attach_collection(user: User, analysis: Analysis, collection: Collection) -> Dict[str, Any]: """ Add a collection to the list of collections belonging to an analysis :param user: :param analysis: :param collection: :return: """ # check read permissions on analysis and collection if is_read_permitted(user, collection) and is_write_permitted( user, analysis): if collection not in analysis.collections: analysis.collections.append(collection) db.session.commit() return { 'message': f'collection {collection.id} attached to analysis {analysis.id}' } return { 'message': f'Collection {collection.id} already attached to analysis {analysis.id}' } raise AuthException( f'User {user.email} is not permitted to attach collection {collection.id} ' f'to analysis {analysis.id}')
def delete_collection(user: User, collection: Collection) -> Dict[str, str]: if is_write_permitted(user, collection): collection_id = collection.id db.session.delete(collection) db.session.commit() # event will handle file deletion return {'message': f'collection {collection_id} removed'} raise AuthException(f'User {user.email} is not permitted to modify collection {collection.id}')
def create_new_label_dataset(user: User, collection: Collection, name: str, data_type: str = 'string') -> Dict[str, str]: if is_write_permitted(user, collection): if re.match('^[^\d\W]\w*$', name): collection.create_label_column(name, data_type) return {'message': f'Created dataset {name} in collection {collection.id}.'} raise ValueError(f'Suggested name {name} is not valid.') raise AuthException(f'User {user.email} not permitted to modify collection {collection.id}.')
def __init__(self, current_user: User, record: FileRecordMixin): super(FileAttributeTableData, self).__init__(current_user, is_write_permitted(current_user, record)) dtypes = record.get_attribute_types() self.rows = [ FileAttributeTableRow(key, value, dtypes[key], self.editable) for key, value in record.get_file_attributes().items() ]
def __init__(self, current_user: User, record: FileRecordMixin): super(FileGroupAttributeTableData, self).__init__(current_user, is_write_permitted(current_user, record)) self.rows = [ FileAttributeTableRow(key, value, type(value).__name__) for key, value in record.get_group_attributes().items() ]
def delete_sample_group(user: User, sample_group: SampleGroup) -> Dict[str, str]: """ Delete a sample group. :param user: :param sample_group: :return: """ if is_write_permitted(user, sample_group): db.session.delete(sample_group) db.session.commit() return {'message': f'User group {sample_group.id} deleted'} raise AuthException(f'User {user.email} not permitted to modify sample group {sample_group.id}')
def __init__(self, current_user: User, record: Union[OmicsRecordMixin, WorkflowModule, Job], record_type: str): super(EntryPageData, self).__init__(current_user, is_write_permitted(current_user, record)) self.id = record.id self.name = record.name self.update_url = get_update_url(record) self.list_url = get_list_url(record) self.attribute_table_data = AttributeTableData(current_user, record) self.record_type = record_type self.admin = current_user.admin
def update_collection(user: User, collection: Collection, new_data: Dict[str, Any], filename: str = None) -> Collection: """ Update collection attributes :param user: :param collection: :param new_data: :return: """ if is_write_permitted(user, collection): # file attributes and database attributes should be separated if 'id' in new_data: if collection.id != int(new_data['id']) and Collection.query.filter_by(id=new_data['id']) is not None: raise ValueError(f'Collection with id {new_data["id"]} already exists!') # verify write permissions on analyses to attach to or detach from if 'analysis_ids' in new_data: new_analyses = [get_analysis(user, analysis_id) for analysis_id in new_data['analysis_ids']] remove_analyses = [analysis for analysis in collection.analyses if analysis.id not in new_data['analysis_ids']] for analysis in new_analyses: if not is_write_permitted(user, analysis): raise AuthException(f'User {user.email} is not permitted to attach collection {collection.id} ' f'to analysis {analysis.id}') for analysis in remove_analyses: if not is_write_permitted(user, analysis): raise AuthException(f'User {user.email} is not permitted to detach collection {collection.id} ' f'from analysis {analysis.id}') collection.analyses = new_analyses collection.update(new_data) if filename is not None: os.remove(collection.filename) shutil.copy(filename, collection.filename) os.remove(filename) if 'file_info' in new_data: mdt.update_metadata(collection.filename, {key: value for key, value in new_data['file_info'].items()}) collection.last_editor = user db.session.commit() return collection raise AuthException(f'User {user.email} is not permitted to modify collection {collection.id}')
def delete_analysis(user: User, analysis: Analysis) -> Dict[str, str]: """ Remove the record associated with this analysis from the database :param user: :param analysis: :return: """ if is_write_permitted(user, analysis): db.session.delete(analysis) db.session.commit() return {'message': f'Analysis {analysis.id} deleted'} raise AuthException( f'User {user.email} is not permitted to modify analysis {analysis.id}')
def update_sample_group_attachments(user: User, sample_group: SampleGroup, samples: List[Sample]) -> SampleGroup: """ Make the only samples attached to a group those in sample_ids :param user: :param sample_group: :param samples: :return: """ if is_write_permitted(user, sample_group) and all([is_read_permitted(user, sample) for sample in samples]): sample_group.samples = samples sample_group.last_editor = user db.session.commit() return sample_group raise AuthException(f'User {user.email} not authorized to modify group {sample_group.id}')
def delete_workflow(user: User, workflow: Workflow) -> Dict[str, str]: """ Delete a workflow from the database and filesystem :param user: :param workflow: :return: """ if is_write_permitted(user, workflow): workflow_id = workflow.id db.session.delete(workflow) db.session.commit() return {'message': f'Workflow {workflow_id} deleted.'} raise AuthException( f'User {user.email} is not permitted to modify analysis {workflow.id}')
def detach_sample(user: User, sample: Sample, sample_group: SampleGroup) -> SampleGroup: """ Remove a sample from a sample group. :param user: :param sample: :param sample_group: :return: """ if is_write_permitted(user, sample_group): sample_group.samples.remove(sample) sample_group.last_editor = user db.session.commit() return sample_group raise AuthException(f'User {user.email} not permitted to modify group {sample_group.id}')
def attach_sample(user: User, sample: Sample, sample_group: SampleGroup) -> SampleGroup: """ Make a sample a member of a sample group. :param user: :param sample: :param sample_group: :return: """ if is_write_permitted(user, sample_group) and is_read_permitted(user, sample): if sample not in sample_group.samples: sample_group.samples.append(sample) sample_group.last_editor = user db.session.commit() return sample_group raise AuthException(f'User {user.email} is not permitted to attach {sample.id} to group {sample_group.id}')
def delete_sample(user: User, sample: Sample) -> Dict[str, Any]: """ Delete the sample file with sample_id :param user: :param sample: :return: """ if is_write_permitted(user, sample): sample_id = sample.id db.session.delete(sample) db.session.commit() # event will handle file deletion return {'message': f'Sample {sample_id} removed'} raise AuthException( f'User {user.email} is not permitted to modify collection {sample.id}')
def update_collection_array(user: User, collection: Collection, path: str, i: int, j: int, val) -> Collection: """ Update one point of one array in a collection :param user: :param collection: :param path: :param i: :param j: :param val: :return: """ if is_write_permitted(user, collection): ct.update_array(collection.filename, path, i, j, val) return collection raise AuthException(f'User {user.email} is not permitted to modify collection {collection.id}.')
def __init__(self, current_user: User, collection: Collection): super(LabelColumnTableData, self).__init__(current_user, is_write_permitted(current_user, collection)) file_info = collection.get_file_info() label_datasets = [ dataset for dataset in file_info['datasets'] if dataset['rows'] == file_info['max_row_count'] and dataset['cols'] == 1 ] self.headings = [dataset['path'] for dataset in label_datasets] self.columns = { path: get_dataset(collection.filename, path, True) for path in self.headings } self.row_count = file_info['max_row_count']
def update_sample_group(user: User, sample_group: SampleGroup, new_data: Dict[str, Any]) -> SampleGroup: """ Update the data for a sample group. :param user: :param sample_group: :param new_data: :return: """ if is_write_permitted(user, sample_group): if 'id' in new_data: if sample_group.id != new_data['id'] and SampleGroup.query.filter_by(id=new_data['id']) is not None: raise ValueError(f'Sample group with id {new_data["id"]} already exists!') sample_group.update(new_data) sample_group.last_editor = user db.session.commit() return sample_group raise AuthException(f'User {user.email} is not permitted to modify group {sample_group.id}')
def detach_collection(user: User, analysis: Analysis, collection: Collection) -> Dict[str, Any]: """ Remove a collection from the list of collections belonging to an analysis :param user: :param analysis: :param collection: :return: """ if is_write_permitted(user, analysis): analysis.collections.remove(collection) db.session.commit() return { 'message': f'collection {collection.id} detached from analysis {analysis.id}' } raise AuthException( f'User {user.email} is not permitted to modify analysis {analysis.id}')
def delete_external_file(user: User, external_file: ExternalFile, delete_file: bool = False): if is_write_permitted(user, external_file): filename = external_file.filename record_id = external_file.id db.session.delete(external_file) db.session.commit() if delete_file: os.remove(filename) return { 'message': f'Deleted external file record {record_id} and file {filename}' } else: return { 'message': f'Deleted external file record {record_id}. File {filename} still exists on disk.' }
def update_analysis(user: User, analysis: Analysis, new_data: Dict[str, Any]) -> Analysis: """ Update the analysis with the data in new_data :param user: :param analysis: :param new_data: :return: """ if is_write_permitted(user, analysis): if 'id' in new_data: if analysis.id != int(new_data['id']) and Analysis.query.filter_by( id=new_data['id']) is not None: raise ValueError( f'Analysis with id {new_data["id"]} already exists!') analysis.update(new_data) analysis.last_editor = user db.session.commit() return analysis raise AuthException( f'User {user.email} is not permitted to modify analysis {analysis.id}')
def __init__(self, current_user: User, record: Any): super(AttributeTableData, self).__init__(current_user, is_write_permitted(current_user, record)) self.values = { 'ID': AttributeTableRow('id', record.id, False), 'Name': AttributeTableRow('name', record.name, self.editable) } if hasattr(record, 'description'): self.values['Description'] = AttributeTableRow( 'description', record.description if record.description is not None else '', self.editable) if isinstance(record, SampleGroup): job = get_job(record.upload_job_id) self.values['Job'] = AttributeTableRow('job', job.name, href=get_item_link(job)) if isinstance(record, User): self.values['Email'] = AttributeTableRow( 'email', record.email, False, href=f'mailto:{record.email}') if is_read_permitted(current_user, record.primary_user_group): self.values['Primary User Group'] = AttributeTableRow( 'primary_user_group', record.primary_user_group.name if record.primary_user_group is not None else None, False, get_item_link(record.primary_user_group) if record.primary_user_group is not None else None) if isinstance(record, OmicsRecordMixin): if is_read_permitted(current_user, record.owner): self.values['Owner'] = AttributeTableRow( 'owner', record.owner.name if record.owner is not None else None, False, get_item_link(record.owner) if record.owner is not None else None) user_group_options = [ SelectOption(group.id, group.name, group is record.user_group) for group in UserGroup.query.filter( UserGroup.members.contains(current_user)).all() ] self.values['User Group'] = AttributeTableRow( 'user_group_id', record.user_group.name if record.user_group is not None else None, self.editable, None, user_group_options, False) permissions_options = [ SelectOption('all_can_read', 'Anyone can view?', record.all_can_read), SelectOption('group_can_read', 'User group members can view?', record.group_can_read), SelectOption('all_can_write', 'Anyone can edit or delete?', record.all_can_write), SelectOption('group_can_write', 'User group members can edit or delete', record.group_can_write) ] self.values['Permissions'] = AttributeTableRow( 'permissions', None, is_write_permitted(current_user, record), select_options=permissions_options, select_multiple=True, select_composite=True) if isinstance(record, Collection): if record.parent is not None and is_read_permitted( current_user, record.parent): self.values['Parent Collection'] = AttributeTableRow( 'parent', f'{record.parent.name} (Collection {record.parent.id})', href=get_item_link(record.parent)) else: self.values['Parent Collection'] = AttributeTableRow( 'parent', 'None') self.values['Kind'] = AttributeTableRow('kind', record.kind) if isinstance(record, Job): self.values['Type'] = AttributeTableRow('type', record.type) self.values['Status'] = AttributeTableRow('status', record.status) self.values['Submitted'] = AttributeTableRow( 'submitted', record.submission) self.values['Started'] = AttributeTableRow('start', record.start) self.values['Ended'] = AttributeTableRow('end', record.end) if is_read_permitted(current_user, record.owner): self.values['Submitted By'] = AttributeTableRow( 'owner', record.owner.name, href=get_item_link(record.owner)) if isinstance(record, ExternalFile): self.values['Path'] = AttributeTableRow('filename', record.filename) self.values['File Type'] = AttributeTableRow( 'file_type', record.file_type) file_info = record.get_file_info() if file_info is not None: size_mb = file_info['st_size'] / (1024 * 1024.0) self.values['Size'] = AttributeTableRow( 'file_size', f'{size_mb:.3f} MB') self.values['File Modified'] = AttributeTableRow( 'file_mtime', datetime.fromtimestamp( file_info['st_mtime']).strftime('%-d %b %Y %H:%M')) if isinstance(record, Base): self.values['Date Created'] = AttributeTableRow( 'created_on', record.created_on.strftime('%-d %b %Y %H:%M')) self.values['Date Modified'] = AttributeTableRow( 'updated_on', record.updated_on.strftime('%-d %b %Y %H:%M'))