Example #1
0
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}')
Example #2
0
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}')
Example #3
0
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}'
    )
Example #4
0
def download_external_file(user: User,
                           external_file: ExternalFile) -> Dict[str, str]:
    if is_read_permitted(user, external_file):
        return {'filename': os.path.basename(external_file.filename)}
    raise AuthException(
        f'User {user.id} is not permitted to access external file {external_file.id}'
    )
Example #5
0
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}.')
Example #6
0
def create_collection(user: User,
                      samples: List[Sample],
                      data: Dict[str, Any],
                      sort_by: str = 'base_sample_id') -> Collection:
    """
    Create a new collection by concatenating samples. Collection metadata is set with new_data
    :param user:
    :param samples:
    :param data: Collection attributes
    :param sort_by:
    :return:
    """
    data['owner_id'] = user.id
    data['creator_id'] = user.id
    if 'id' in data:  # cannot create with specified id
        del data['id']
    for sample in samples:
        if not is_read_permitted(user, sample):
            raise AuthException(f'User {user.id} is not permitted to access sample {sample.id}')
    filenames = [sample.filename for sample in samples]
    new_collection = Collection(owner=user, creator=user, last_editor=user, name=data['name'])
    db.session.add(new_collection)
    db.session.commit()
    new_collection.filename = f'{DATADIR}/collections/{new_collection.id}.h5'
    db.session.commit()
    if len(filenames):
        new_collection.merge_samples(samples, sort_by)
    else:
        new_collection.create_empty_file()
    update_collection(user, new_collection, data)
    return new_collection
Example #7
0
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}')
Example #8
0
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}')
Example #9
0
def delete_invitation(user: User,
                      invitation: UserInvitation) -> Dict[str, str]:
    if user.admin:
        db.session.remove(invitation)
        db.session.commit()
        return {'message': f'Invitation {invitation.id} removed.'}
    raise AuthException(
        f'User {user.email} is not an administrator and cannot view or edit invitations.'
    )
Example #10
0
def get_included_sample_groups(user: User, sample: Sample) -> List[SampleGroup]:
    """
    Get a list of sample groups that a sample is found in
    :param user:
    :param sample:
    :return:
    """
    if is_read_permitted(user, sample):
        return get_read_permitted_records(user, sample.sample_groups)
    raise AuthException(f'User {user.email} not permitted to view sample {sample.id}')
Example #11
0
def get_sample_group_members(user: User, sample_group: SampleGroup) -> List[Sample]:
    """
    Get a list of samples which belong to this group.
    :param user:
    :param sample_group:
    :return:
    """
    if is_read_permitted(user, sample_group):
        return get_read_permitted_records(user, sample_group.samples)
    raise AuthException(f'User {user.email} not permitted to view sample group {sample_group.id}')
Example #12
0
def list_collection_paths(user: User, collection: Collection) -> List[str]:
    """
    List the paths corresponding to datasets in the collection
    :param user:
    :param collection:
    :return:
    """
    if is_read_permitted(user, collection):
        return mdt.get_dataset_paths(collection.filename)
    raise AuthException(f'User {user.email} is not permitted to access collection {collection.id}')
Example #13
0
def download_collection(user: User, collection: Collection) -> Dict[str, str]:
    """
    If the user is permitted to read this collection, get the path to the collection, else throw.
    The file is sent via the send_from_directory flask method
    :param user:
    :param collection:
    :return:
    """
    if is_read_permitted(user, collection):
        return {'filename': os.path.basename(collection.filename)}
    raise AuthException(f'User {user.email} is not permitted to access collection {collection.id}')
Example #14
0
def sample_in_sample_group(user: User, sample: Sample, sample_group: SampleGroup) -> bool:
    """
    Determine if a sample belongs to a sample group. NotFoundException,
    :param user:
    :param sample:
    :param sample_group:
    :return:
    """
    if is_read_permitted(user, sample_group) and is_read_permitted(user, sample):
        return sample in sample_group.samples
    raise AuthException(f'User {user.email} not permitted to check the attachment of {sample.id} to sample group {sample_group.id}')
Example #15
0
def validate_login(email: str, password: str) -> User:
    """
    Authenticate a user
    :param email:
    :param password: password in plaintext
    :return:
    """
    user = User.query.filter_by(email=email).first()
    if user is None or not user.check_password(password):
        raise AuthException('Invalid username/password.')
    return user
Example #16
0
def list_sample_paths(user: User, sample: Sample) -> List[str]:
    """
    List all the paths to datasets within the sample file
    :param user:
    :param sample:
    :return:
    """
    if is_read_permitted(user, sample):
        return mdt.get_dataset_paths(sample.filename)
    raise AuthException(
        f'User {user.email} is not permitted to access sample {sample.id}')
Example #17
0
def download_sample(user: User, sample: Sample) -> Dict[str, str]:
    """
    If the user with user_id is permitted to access sample_id, present the filename for the sample with sample_id
    :param user:
    :param sample:
    :return:
    """

    if is_read_permitted(user, sample):
        return {'filename': f'{sample.id}.h5'}
    raise AuthException(
        f'User {user.email} is not permitted to access sample {sample.id}')
Example #18
0
def resume_job(user: User, job: Job) -> Dict[str, Any]:
    """
    Release the hold on a job on the Cromwell job server.
    :param user:
    :param job:
    :return:
    """
    job.refresh()
    if job.owner == user or user.admin:
        return job.resume()
    raise AuthException(
        f'User {user.email} is not authorized to resume job {job.id}')
Example #19
0
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}')
Example #20
0
def get_analysis(user: User, analysis_id: int) -> Analysis:
    """
    Get analysis information
    :param user:
    :param analysis_id:
    :return:
    """
    analysis = Analysis.query.filter_by(id=analysis_id).first()
    if is_read_permitted(user, analysis):
        return analysis
    raise AuthException(
        f'User {user.email} is not permitted to access analysis {analysis_id}')
Example #21
0
def cancel_job(user: User, job: Job) -> Dict[str, Any]:
    """
    Abort a running job on the Cromwell job server.
    :param user:
    :param job:
    :return:
    """
    job.refresh()
    if job.owner == user or user.admin:
        return job.cancel()
    raise AuthException(
        f'User {user.email} is not authorized to cancel job {job.id}')
Example #22
0
def get_attached_collections(user: User,
                             analysis: Analysis) -> List[Collection]:
    """
    Get all collections which belong to an analysis
    :param user:
    :param analysis:
    :return:
    """
    if is_read_permitted(user, analysis):
        return analysis.collections
    raise AuthException(
        f'User {user.email} is not permitted to access analysis {analysis.id}')
Example #23
0
def get_sample_group(user: User, group_id: int) -> SampleGroup:
    """
    Get a sample group.
    :param user:
    :param group_id:
    :return:
    """
    sample_group = SampleGroup.query.filter_by(id=group_id).first()
    if sample_group is None:
        raise NotFoundException(f'Sample group with id {group_id} not found.')
    if is_read_permitted(user, sample_group):
        return sample_group
    raise AuthException(f'User {user.email} is not authorized to view sample group {group_id}')
Example #24
0
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}')
Example #25
0
def download_collection_dataset(user: User, collection: Collection, path: str) -> Dict[str,  str]:
    """
    If the user is allowed to read a collection, get the contents required to send a file containing a dataset
    as CSV
    :param user:
    :param collection:
    :param path:
    :return:
    """
    csv_filename = f'{os.path.basename(os.path.normpath(path))}.csv'
    if is_read_permitted(user, collection):
        return {'csv': mdt.get_csv(collection.filename, path), 'cd': f'attachment; filename={csv_filename}'}
    raise AuthException(f'User {user.email} is not permitted to access collection {collection.id}')
Example #26
0
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}')
Example #27
0
def get_attached_analyses(user: User,
                          collection: Collection) -> List[Analysis]:
    """
    Get all analysis that a collection belongs to
    :param user:
    :param collection:
    :return:
    """
    if is_read_permitted(user, collection):
        return get_all_read_permitted_records(user, collection.analyses)
    raise AuthException(
        f'User {user.email} not permitted to access collection {collection.id}'
    )
Example #28
0
def delete_user_group(user: User, user_group: UserGroup) -> Dict[str, str]:
    """
    Delete a user group.
    :param user:
    :param user_group:
    :return:
    """
    if is_user_group_admin(user, user_group) or user.admin:
        user_group_id = user_group.id
        db.session.delete(user_group)
        db.session.commit()
        return {'message': f'User group {user_group_id} deleted.'}
    raise AuthException(f'User {user.email} not permitted to modify user group {user_group.id}')
Example #29
0
def update_user_attachments(current_user: User, user_group: UserGroup, users: List[User]) -> UserGroup:
    """
    Change which members belong to a user group.
    :param current_user:
    :param user_group:
    :param users: The only users of the user group after this is called.
    :return:
    """
    if is_user_group_admin(current_user, user_group):
        user_group.members = users
        db.session.commit()
        return user_group
    raise AuthException(f'User {current_user.id} not authorized to modify group {user_group.id}')
Example #30
0
def get_collection(user: User, collection_id: int) -> Collection:
    """
    Get the attributes and dataset information of a collection
    :param user:
    :param collection_id:
    :return:
    """
    collection = Collection.query.filter_by(id=collection_id).first()
    if collection is None:
        raise NotFoundException(f'No collection with id {collection_id}')
    if is_read_permitted(user, collection):
        return collection
    raise AuthException(f'User {user.email} is not authorized to view collection {collection.id}')