Example #1
0
 def check_and_call(program, project, *args, **kwargs):
     with flask.current_app.db.session_scope():
         # Check that the program exists
         program_node = (
             flask.current_app
             .db
             .nodes(models.Program)
             .props(name=program)
             .first()
         )
         if not program_node:
             raise NotFoundError('Program {} not found'.format(program))
         # Check that the project exists
         project_node = (
             flask.current_app
             .db
             .nodes(models.Project)
             .props(code=project)
             .path('programs')
             .ids(program_node.node_id)
             .first()
         )
         if not project_node:
             raise NotFoundError('Project {} not found'.format(project))
         phsids = [program_node.dbgap_accession_number, project_node.dbgap_accession_number]
     return func(program, project, *args, **kwargs)
Example #2
0
    def get_role(self, node):
        state = node.state
        file_state = node.file_state
        # if state is live, it's a processed legacy file
        if state == 'live':
            return ROLES['GENERAL']
        elif node.project_id:
            with flask.current_app.db.session_scope():
                program, project = node.project_id.split('-', 1)
                try:
                    project = (flask.current_app.db.nodes(
                        models.Project).props(
                            code=project).path('programs').props(
                                name=program).one())
                except sqlalchemy.orm.exc.MultipleResultsFound:
                    raise InternalError(
                        "Multiple results found for file {}'s project {}".
                        format(node.node_id, node.project_id))
                except sqlalchemy.orm.exc.NoResultFound:
                    raise InternalError(
                        "No results found for file {}'s project {}".format(
                            node.node_id, node.project_id))

                # for general users with '_member_' role, allow
                # download if project is released and file is submitted
                # and file_state is at or after "submitted"
                allow_general_access = (project.released is True
                                        and state == 'submitted' and file_state
                                        in MEMBER_DOWNLOADABLE_STATES)

                # for submitters with "download" role, allow download
                # if file_state is at or after "uploaded"
                allow_submitter_access = file_state in SUBMITTER_DOWNLOADABLE_STATES

                if allow_general_access:
                    return ROLES['GENERAL']

                elif allow_submitter_access:
                    return ROLES['DOWNLOAD']

                else:
                    raise NotFoundError("Data with id {} not found".format(
                        node.node_id))

        else:
            # node does not have project_id and is not live
            raise NotFoundError("Data with id {} not found".format(
                node.node_id))
Example #3
0
 def check(program, *args, **kwargs):
     with flask.current_app.db.session_scope():
         programs = flask.current_app.db.nodes(
             models.Program).props(name=program)
         if not programs.count():
             raise NotFoundError("program {} not found".format(program))
     return func(program, *args, **kwargs)
Example #4
0
def close_transaction(program, project, transaction_id):
    """
    Close a transaction. The transaction is prevented from being committed in
    the future.
    """
    with flask.current_app.db.session_scope():
        try:
            tx_log = (flask.current_app.db.nodes(
                models.submission.TransactionLog).filter(
                    models.submission.TransactionLog.id ==
                    transaction_id).one())
        except sqlalchemy.orm.exc.NoResultFound:
            project_id = '{}-{}'.format(program, project)
            raise NotFoundError(
                'Unable to find transaction_log with id {} for project {}'.
                format(transaction_id, project_id))
        # Check if already closed.
        if tx_log.closed:
            raise UserError("This transaction log is already closed.")
        # Check if dry_run.
        if tx_log.is_dry_run is False:
            raise UserError("This transaction log is not a dry run. "
                            "Closing it would have no effect.")
        # Check if already committed.
        if tx_log.committed_by is not None:
            raise UserError("This transaction log has already been committed. "
                            "Closing it would have no effect.")
        tx_log.closed = True

    return flask.jsonify({
        'code': 200,
        'message': 'Closed transaction.',
        'transaction_id': transaction_id,
    })
Example #5
0
def close_transaction(program, project, transaction_id):
    """
    Close a transaction. The transaction is prevented from being committed in
    the future.

    Summary:
        Close a transaction

    Tags:
        dry run

    Args:
        program (str): |program_id|
        project (str): |project_id|
        transaction_id (int): transaction_id

    Responses:
        200: Success
        404: Resource not found.
        403: Unauthorized request.
    """
    with flask.current_app.db.session_scope():
        try:
            tx_log = (
                flask.current_app.db.nodes(models.submission.TransactionLog)
                .filter(models.submission.TransactionLog.id == transaction_id)
                .one()
            )
        except sqlalchemy.orm.exc.NoResultFound:
            project_id = "{}-{}".format(program, project)
            raise NotFoundError(
                "Unable to find transaction_log with id {} for project {}".format(
                    transaction_id, project_id
                )
            )
        # Check if already closed.
        if tx_log.closed:
            raise UserError("This transaction log is already closed.")
        # Check if dry_run.
        if tx_log.is_dry_run is False:
            raise UserError(
                "This transaction log is not a dry run. "
                "Closing it would have no effect."
            )
        # Check if already committed.
        if tx_log.committed_by is not None:
            raise UserError(
                "This transaction log has already been committed. "
                "Closing it would have no effect."
            )
        tx_log.closed = True

    return flask.jsonify(
        {
            "code": 200,
            "message": "Closed transaction.",
            "transaction_id": transaction_id,
        }
    )
Example #6
0
def get_dictionary_entry(entry):
    """
    Return the project level JSON schema definition for a given entity
    type.

    Summary:
        Get the dictionary schema for an entity

    Tags:
        dictionary

    Args:
        entry (str): entity type to retrieve the schema for (e.g. ``aliquot``)

    Responses:
        200 (schema_entity): Success
        404: Resource not found.
        403: Unauthorized request.

    :reqheader Content-Type: |reqheader_Content-Type|
    :reqheader Accept: |reqheader_Accept|
    :reqheader X-Auth-Token: |reqheader_X-Auth-Token|
    :resheader Content-Type: |resheader_Content-Type|

    **Example**

    .. code-block:: http

           GET /v0/submission/_dictionary/case HTTP/1.1
           Host: example.com
           Content-Type: application/json
           X-Auth-Token: MIIDKgYJKoZIhvcNAQcC...
           Accept: application/json

    .. code-block:: JavaScript

           {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "category": "administrative",
             "description": "TODO",
             "id": "case",
             "links": [...]
             ...
           }
    """
    resolvers = {
        key.replace(".yaml", ""): resolver.source
        for key, resolver in dictionary.resolvers.items()
    }
    if entry in resolvers:
        return flask.jsonify(resolvers[entry])
    elif entry == "_all":
        return flask.jsonify(dict(dictionary.schema, **resolvers))
    elif entry not in dictionary.schema:
        raise NotFoundError("Entry {} not in dictionary".format(entry))
    else:
        return flask.jsonify(dictionary.schema[entry])
Example #7
0
def get_projects(program):
    """
    Return the available resources at the top level of program ``program``,
    i.e. registered projects.

    Summary:
        Get the projects

    Tags:
        project

    Responses:
        200 (schema_links): Success
        403: Unauthorized request.
        404: Program not found.

    Args:
        program (str): |program_id|

    :reqheader Content-Type: |reqheader_Content-Type|
    :reqheader Accept: |reqheader_Accept|
    :reqheader X-Auth-Token: |reqheader_X-Auth-Token|
    :resheader Content-Type: |resheader_Content-Type|

    **Example**

    .. code-block:: http

           GET /v0/submission/CGCI/ HTTP/1.1
           Host: example.com
           Content-Type: application/json
           X-Auth-Token: MIIDKgYJKoZIhvcNAQcC...
           Accept: application/json

    .. code-block:: JavaScript

        {
            "links": [
                "/v0/sumission/CGCI/BLGSP"
            ]
        }
    """
    if flask.current_app.config.get("AUTH_SUBMISSION_LIST", True) is True:
        auth.validate_request(aud={"openid"}, purpose=None)
    with flask.current_app.db.session_scope():
        matching_programs = flask.current_app.db.nodes(
            models.Program).props(name=program)
        if not matching_programs.count():
            raise NotFoundError("program {} is not registered".format(program))
        projects = (flask.current_app.db.nodes(
            models.Project.code).path("programs").props(name=program).all())
    links = [
        flask.url_for(".create_entities", program=program, project=p[0])
        for p in projects
    ]
    return flask.jsonify({"links": links})
Example #8
0
def commit_dry_run_transaction(program, project, transaction_id):
    """
    See documentation for committing a dry run transaction.

    This call should only succeed if:
    1. transaction_id points to a dry_run transaction
    2. transaction_id points to a transaction that hasn't been committed already
    3. transaction_id points to a successful transaction
    """
    with flask.current_app.db.session_scope():
        try:
            tx_log = (
                flask.current_app.db.nodes(models.submission.TransactionLog)
                .filter(models.submission.TransactionLog.id == transaction_id)
                .one()
            )
        except sqlalchemy.orm.exc.NoResultFound:
            raise NotFoundError(
                'Unable to find transaction_log with id: {} for project {}'
                .format(transaction_id, '{}-{}'.format(program, project))
            )
        # Check state.
        if tx_log.state not in STATES_COMITTABLE_DRY_RUN:
            raise UserError(
                'Unable to commit transaction log in state {}.'
                .format(tx_log.state)
            )
        # Check not closed.
        if tx_log.closed:
            raise UserError('Unable to commit closed transaction log.')
        # Check not committed.
        if tx_log.committed_by is not None:
            raise UserError(
                "This transaction_log was committed already by transaction "
                "'{}'.".format(tx_log.committed_by)
            )
        # Check is dry_run
        if tx_log.is_dry_run is not True:
            raise UserError(
                "Cannot submit transaction_log '{}', not a dry_run."
                .format(tx_log.id)
            )
        # Check project
        if tx_log.project != project or tx_log.program != program:
            raise UserError(
                "Cannot submit transaction_log '{}', in project {}-{}."
                .format(tx_log.id, program, project)
            )

        response, code = resubmit_transaction(tx_log)
        response_data = json.loads(response.get_data())
        tx_log.committed_by = response_data['transaction_id']

        return response, code
Example #9
0
def entity_to_template(label, exclude_id=True, file_format="tsv", **kwargs):
    """Return template dict for given label."""
    if label not in dictionary.schema:
        raise NotFoundError("Entity type {} is not in dictionary".format(label))
    if file_format not in SUPPORTED_FORMATS:
        raise UnsupportedError(file_format)
    schema = dictionary.schema[label]
    links = _get_links(file_format, schema["links"], exclude_id)
    if file_format == "json":
        return entity_to_template_json(links, schema, exclude_id)
    else:
        return entity_to_template_delimited(links, schema, exclude_id)
Example #10
0
def get_projects(program):
    """
    Return the available resources at the top level of program ``program``,
    i.e. registered projects.

    Args:
        program (str): |program_id|

    :reqheader Content-Type: |reqheader_Content-Type|
    :reqheader Accept: |reqheader_Accept|
    :reqheader X-Auth-Token: |reqheader_X-Auth-Token|
    :resheader Content-Type: |resheader_Content-Type|
    :statuscode 200: Success
    :statuscode 403: Unauthorized request.
    :statuscode 404: Program not found.

    **Example**

    .. code-block:: http

           GET /v0/submission/CGCI/ HTTP/1.1
           Host: example.com
           Content-Type: application/json
           X-Auth-Token: MIIDKgYJKoZIhvcNAQcC...
           Accept: application/json

    .. code-block:: JavaScript

        {
            "links": [
                "/v0/sumission/CGCI/BLGSP"
            ]
        }
    """
    if flask.current_app.config.get('AUTH_SUBMISSION_LIST', True) is True:
        auth.require_auth()
    with flask.current_app.db.session_scope():
        matching_programs = (flask.current_app.db.nodes(
            models.Program).props(name=program))
        if not matching_programs.count():
            raise NotFoundError('program {} is not registered'.format(program))
        projects = (flask.current_app.db.nodes(
            models.Project.code).path('programs').props(name=program).all())
    links = [
        flask.url_for('.create_entities', program=program, project=p[0])
        for p in projects
    ]
    return flask.jsonify({'links': links})
Example #11
0
    def get_nodes(self, ids, with_children):
        """Look up nodes and set self.result"""
        ids = parse_ids(ids)
        with flask.current_app.db.session_scope():
            self.nodes = (flask.current_app.db.nodes().ids(ids).props(
                project_id=self.project_id).all())
            found_ids = {node.node_id for node in self.nodes}
            if not found_ids:
                raise NotFoundError("Unable to find {}".format(', '.join(ids)))
            missing_ids = set(ids) - found_ids
            if missing_ids:
                log.warn("Unable to find: %s", ', '.join(missing_ids))
            if with_children:
                parents = copy.copy(self.nodes)
                for node in parents:
                    self.get_entity_tree(node, self.nodes)

            self.get_dictionary()
Example #12
0
def close_transaction(program, project, transaction_id):
    """
    Commit a dry_run transaction by repeating it with a new non-dry_run
    transaction.
    """
    with flask.current_app.db.session_scope():
        try:
            tx_log = (flask.current_app.db.nodes(
                models.submission.TransactionLog).filter(
                    models.submission.TransactionLog.id ==
                    transaction_id).one())
        except sqlalchemy.orm.exc.NoResultFound:
            raise NotFoundError(
                "Unable to find transaction_log with id: {} for project {}".
                format(transaction_id, "{}-{}".format(program, project)))

        # Check if already closed
        if tx_log.closed:
            raise UserError("This transaction log is already closed.")
        # Check if dry_run
        if tx_log.is_dry_run is False:
            raise UserError(
                "This transaction log is not a dry run. Closing it would have"
                " no effect.")
        # Check if dry_run
        if tx_log.committed_by is not None:
            raise UserError(
                "This transaction log has already been committed. Closing it"
                " would have no effect.")

        tx_log.closed = True

    return flask.jsonify({
        "code": 200,
        "message": "Closed transaction.",
        "transaction_id": transaction_id,
    })
Example #13
0
def create_project(program):
    """
    Register a project.

    The content of the request is a JSON containing the information describing
    a project. Authorization for registering projects is limited to
    administrative users.

    Summary:
        Create a project

    Tags:
        project

    Args:
        program (str): |program_id|
        body (schema_project): input body
    
    Responses:
        200: Registered successfully.
        400: User error.
        404: Program not found.
        403: Unauthorized request.

    :reqheader Content-Type: |reqheader_Content-Type|
    :reqheader Accept: |reqheader_Accept|
    :reqheader X-Auth-Token: |reqheader_X-Auth-Token|
    :resheader Content-Type: |resheader_Content-Type|

    Example:

        .. code-block:: http

            POST /v0/submission/CGCI/ HTTP/1.1
            Host: example.com
            Content-Type: application/json
            X-Auth-Token: MIIDKgYJKoZIhvcNAQcC...
            Accept: application/json

        .. code-block:: JavaScript

            {
                "type": "project",
                "code": "BLGSP",
                "disease_type": "Burkitt Lymphoma",
                "name": "Burkitt Lymphoma Genome Sequencing Project",
                "primary_site": "Lymph Nodes",
                "dbgap_accession_number": "phs000527",
                "state": "active"
            }
    """
    auth.current_user.require_admin()
    doc = utils.parse.parse_request_json()
    if not isinstance(doc, dict):
        raise UserError("Program endpoint only supports single documents")
    if doc.get("type") and doc.get("type") not in ["project"]:
        raise UserError(
            "Invalid post to program endpoint with type='{}'".format(
                doc.get("type")))
    # Parse project.code
    project = doc.get("code")
    if not project:
        raise UserError("No project specified in key 'code'")
    project = project.encode("utf-8")
    # Parse dbgap accession number.
    phsid = doc.get("dbgap_accession_number")
    if not phsid:
        raise UserError("No dbGaP accesion number specified.")

    # Create base JSON document.
    base_doc = utils.parse.parse_request_json()
    with flask.current_app.db.session_scope() as session:
        program_node = utils.lookup_program(flask.current_app.db, program)
        if not program_node:
            raise NotFoundError("Program {} is not registered".format(program))
        # Look up project node.
        node = utils.lookup_project(flask.current_app.db, program, project)
        if not node:
            # Create a new project node
            node_uuid = str(uuid.uuid5(PROJECT_SEED, project.encode("utf-8")))
            if flask.current_app.db.nodes(
                    models.Project).ids(node_uuid).first():
                raise UserError(
                    "ERROR: Project {} already exists in DB".format(project))

            node = models.Project(node_uuid)  # pylint: disable=not-callable
            node.programs = [program_node]
            action = "create"
            node.props["state"] = "open"
        else:
            action = "update"

        # silently drop system_properties
        base_doc.pop("type", None)
        base_doc.pop("state", None)
        base_doc.pop("released", None)

        node.props.update(base_doc)

        doc = dict(
            {
                "type": "project",
                "programs": {
                    "id": program_node.node_id
                }
            }, **base_doc)

        # Create transaction
        transaction_args = dict(
            program=program,
            project=project,
            role=ROLES["UPDATE"],
            flask_config=flask.current_app.config,
        )
        with UploadTransaction(**transaction_args) as trans:
            node = session.merge(node)
            session.commit()
            entity = UploadEntityFactory.create(
                trans, doc=None, config=flask.current_app.config)
            entity.action = action
            entity.doc = doc
            entity.entity_type = "project"
            entity.unique_keys = node._secondary_keys_dicts
            entity.node = node
            entity.entity_id = entity.node.node_id
            trans.entities = [entity]
            return flask.jsonify(trans.json)
Example #14
0
def commit_dry_run_transaction(program, project, transaction_id):
    """
    Commit a dry run transaction.

    This call should only succeed if:
    1. transaction_id points to a dry_run transaction;
    2. transaction_id points to a transaction that hasn't been committed already;
    3. transaction_id points to a successful transaction.

    Summary:
        Commit a dry run transaction

    Tags:
        dry run

    Args:
        program (str): |program_id|
        project (str): |project_id|
        transaction_id (int): transaction_id

    Responses:
        200: Success.
        404: Resource not found.
        403: Unauthorized request.
    """
    with flask.current_app.db.session_scope():
        try:
            tx_log = (
                flask.current_app.db.nodes(models.submission.TransactionLog)
                .filter(models.submission.TransactionLog.id == transaction_id)
                .one()
            )
        except sqlalchemy.orm.exc.NoResultFound:
            raise NotFoundError(
                "Unable to find transaction_log with id: {} for project {}".format(
                    transaction_id, "{}-{}".format(program, project)
                )
            )
        # Check state.
        if tx_log.state not in STATES_COMITTABLE_DRY_RUN:
            raise UserError(
                "Unable to commit transaction log in state {}.".format(tx_log.state)
            )
        # Check not closed.
        if tx_log.closed:
            raise UserError("Unable to commit closed transaction log.")
        # Check not committed.
        if tx_log.committed_by is not None:
            raise UserError(
                "This transaction_log was committed already by transaction "
                "'{}'.".format(tx_log.committed_by)
            )
        # Check is dry_run
        if tx_log.is_dry_run is not True:
            raise UserError(
                "Cannot submit transaction_log '{}', not a dry_run.".format(tx_log.id)
            )
        # Check project
        if tx_log.project != project or tx_log.program != program:
            raise UserError(
                "Cannot submit transaction_log '{}', in project {}-{}.".format(
                    tx_log.id, program, project
                )
            )

        response, code = resubmit_transaction(tx_log)
        response_data = json.loads(response.get_data())
        tx_log.committed_by = response_data["transaction_id"]

        return response, code
Example #15
0
def create_project(program):
    """
    Register a project.

    The content of the request is a JSON containing the information describing
    a project. Authorization for registering projects is limited to
    administrative users.

    Args:
        program (str): |program_id|

    :reqheader Content-Type: |reqheader_Content-Type|
    :reqheader Accept: |reqheader_Accept|
    :reqheader X-Auth-Token: |reqheader_X-Auth-Token|
    :resheader Content-Type: |resheader_Content-Type|
    :statuscode 200: Registered successfully.
    :statuscode 404: Program not found.
    :statuscode 403: Unauthorized request.

    Example:

        .. code-block:: http

            POST /v0/submission/CGCI/ HTTP/1.1
            Host: example.com
            Content-Type: application/json
            X-Auth-Token: MIIDKgYJKoZIhvcNAQcC...
            Accept: application/json

        .. code-block:: JavaScript

            {
                "type": "project",
                "code": "BLGSP",
                "disease_type": "Burkitt Lymphoma",
                "name": "Burkitt Lymphoma Genome Sequencing Project",
                "primary_site": "Lymph Nodes",
                "dbgap_accession_number": "phs000527",
                "state": "active"
            }
    """
    auth.admin_auth()
    doc = utils.parse.parse_request_json()
    if not isinstance(doc, dict):
        raise UserError('Program endpoint only supports single documents')
    if doc.get('type') and doc.get('type') not in ['project']:
        raise UserError(
            "Invalid post to program endpoint with type='{}'".format(
                doc.get('type')))
    # Parse project.code
    project = doc.get('code')
    if not project:
        raise UserError("No project specified in key 'code'")
    project = project.encode('utf-8')
    # Parse dbgap accession number.
    phsid = doc.get('dbgap_accession_number')
    if not phsid:
        raise UserError("No dbGaP accesion number specified.")

    # Create base JSON document.
    base_doc = utils.parse.parse_request_json()
    with flask.current_app.db.session_scope() as session:
        program_node = utils.lookup_program(flask.current_app.db, program)
        if not program_node:
            raise NotFoundError('Program {} is not registered'.format(program))
        # Look up project node.
        node = utils.lookup_project(flask.current_app.db, program, project)
        if not node:
            # Create a new project node
            node_uuid = str(uuid.uuid5(PROJECT_SEED, project.encode('utf-8')))
            node = models.Project(node_uuid)  # pylint: disable=not-callable
            node.programs = [program_node]
            action = 'create'
            node.props['state'] = 'open'
        else:
            action = 'update'

        # silently drop system_properties
        base_doc.pop('type', None)
        base_doc.pop('state', None)
        base_doc.pop('released', None)

        node.props.update(base_doc)

        doc = dict(
            {
                'type': 'project',
                'programs': {
                    'id': program_node.node_id
                },
            }, **base_doc)

        # Create transaction
        transaction_args = dict(program=program,
                                project=project,
                                role=ROLES['UPDATE'],
                                flask_config=flask.current_app.config)

        with UploadTransaction(**transaction_args) as trans:
            node = session.merge(node)
            session.commit()
            entity = UploadEntityFactory.create(
                trans, doc=None, config=flask.current_app.config)
            entity.action = action
            entity.doc = doc
            entity.entity_type = 'project'
            entity.unique_keys = node._secondary_keys_dicts
            entity.node = node
            entity.entity_id = entity.node.node_id
            trans.entities = [entity]
            return flask.jsonify(trans.json)