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)
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))
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)
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, })
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, } )
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])
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})
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
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)
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})
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()
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, })
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)
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
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)