Source code for rest.app

"""
.. See the NOTICE file distributed with this work for additional information
   regarding copyright ownership.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
"""

from __future__ import print_function

import os
import sys
#import logging

# Required for ReadTheDocs
from functools import wraps # pylint: disable=unused-import

from flask import Flask, Response, request, make_response
from flask_restful import Api, Resource

from dmp import dmp
from reader.bigbed import bigbed_reader
from reader.bigwig import bigwig_reader
#from reader.tabix import tabix_reader
from reader.hdf5_reader import hdf5_reader

from mg_rest_util.mg_auth import authorized

APP = Flask(__name__)
#logging.basicConfig()

def help_usage(error_message, status_code,
               parameters_required, parameters_provided):
    """
    Usage Help

    Description of the basic usage patterns for GET functions for the app,
    including any parameters that were provided byt he user along with the
    available parameters that are required/optional.

    Parameters
    ----------
    error_message : str | None
        Error message detailing what has gone wrong. If there are no errors then
        None should be passed.
    status_code : int
        HTTP status code.
    parameters_required : list
        List of the text names for each paramter required by the end point. An
        empty list should be provided if there are no parameters required
    parameters_provided : dict
        Dictionary of the parameters and the matching values provided by the
        user. An empyt dictionary should be passed if there were no parameters
        provided by the user.

    Returns
    -------
    str
        JSON formated status message to display to the user
    """
    parameters = {
        'by_user': ['By User [0|1]', 'int', 'OPTIONAL'],
        'file_id': ['File ID', 'str', 'REQUIRED'],
        'region': ['Chromosome:Start:End', 'str:int:int', 'OPTIONAL'],
        'file_type': ['File type (bb, bw, tsv, fasta, fastq, ...)', 'str', 'OPTIONAL'],
        'data_type': ['Data type (chip-seq, rna-seq, wgbs, ...)', 'str', 'OPTIONAL'],
        'assembly': ['Assembly', 'str', 'REQUIRED'],
        'chrom': ['Chromosome', 'str', 'OPTIONAL'],
        'start': ['Start', 'int', 'OPTIONAL'],
        'end': ['End', 'int', 'OPTIONAL'],
        'type': ['add_meta|remove_meta', 'str', 'OPTIONAL'],
        'output': [
            "Default is None. State 'original' to return the original whole file",
            'str', 'OPTIONAL'],
    }

    used_param = {k : parameters[k] for k in parameters_required if k in parameters}

    usage = {
        '_links' : {
            '_self' : request.base_url,
            '_parent' : request.url_root + 'mug/api/dmp'
        },
        'parameters' : used_param
    }
    message = {
        'usage' : usage,
        'status_code' : status_code
    }

    if parameters_provided:
        message['provided_parameters'] = parameters_provided

    if error_message is not None:
        message['error'] = error_message

    return message

def _get_dm_api(user_id=None):
    cnf_loc = os.path.dirname(os.path.abspath(__file__)) + '/mongodb.cnf'

    if user_id == 'test':
        print("TEST USER DM API")
        return dmp(cnf_loc, test=True)

    if os.path.isfile(cnf_loc) is True:
        print("LIVE DM API")
        return dmp(cnf_loc)

    print("TEST DM API")
    return dmp(cnf_loc, test=True)

[docs]class EndPoints(Resource): """ Class to handle the http requests for returning information about the end points """
[docs] def get(self): """ GET list all end points List of all of the end points for the current service. Example ------- .. code-block:: none :linenos: curl -X GET http://localhost:5003/mug/api/dmp/file """ return { '_links': { '_self': request.base_url, '_getWholeFile': request.url_root + 'mug/api/dmp/file/whole', '_getFileRegion': request.url_root + 'mug/api/dmp/file/region', '_ping': request.url_root + 'mug/api/dmp/file/ping', '_parent': request.url_root + 'mug/api/dmp/file' } }
[docs]class File(Resource): """ Class to handle the http requests for retrieving the data from a file. This class is able to handle big[Bed|Wig] file and serve back the matching region in the relevant format. It is also possible to stream back the whole file of any type for use in other tools. """ @authorized def get(self, user_id): """ GET List values from the file Call to optain regions from the conpressed index files for Bed, Wig and TSV based file formats that contain genomic information. Other files can be streamed. Parameters ---------- file_id : str Identifier of the file to retrieve data from output : str Default is None. State 'original' to return the original whole file Returns ------- file Returns a formated in the relevant file type with any genomic features matching the format of the file. Examples -------- .. code-block:: none :linenos: curl -X GET http://localhost:5003/mug/api/dmp/file/whole?file_id=test_file """ file_id = request.args.get('file_id') public = request.args.get('public') params = [file_id] # Display the parameters available if sum([x is None for x in params]) == len(params): return help_usage(None, 200, ['file_id'], {}) if user_id is not None: selected_user_id = user_id['user_id'] if public is not None: selected_user_id = user_id['public_id'] dmp_api = _get_dm_api(selected_user_id) print(user_id, file_id) file_obj = dmp_api.get_file_by_id(selected_user_id, file_id) print(file_obj) if file_obj is None or 'file_path' not in file_obj: return help_usage('MissingFile', 400, ['file_id'], {}) return Response( self._output_generate(file_obj['file_path']), mimetype='text/text' ) return help_usage('Forbidden', 403, ['file_id'], {}) def _output_generate(self, file_path): """ Function to iterate through a file and stream it back to the user """ if os.path.isfile(file_path): with open(file_path, 'rb') as f_strm: #for chunk in iter(lambda: f_strm.read(4096), b''): for chunk in iter(lambda: f_strm.read(64), b''): yield chunk else: yield ""
[docs]class FileRegion(Resource): """ Class to handle the http requests for retrieving the data from a file. This class is able to handle big[Bed|Wig] file and serve back the matching region in the relevant format. It is also possible to stream back the whole file of any type for use in other tools. """ @authorized def get(self, user_id): """ GET List values from the file Call to optain regions from the conpressed index files for Bed, Wig and TSV based file formats that contain genomic information. Other files can be streamed. Parameters ---------- file_id : str Identifier of the file to retrieve data from chrom : str <chromosome> start : int Start position end : int End position Returns ------- file Returns a formated in the relevant file type with any genomic features matching the format of the file. Examples -------- .. code-block:: none :linenos: curl -X GET http://localhost:5003/mug/api/dmp/file/region?file_id=test_file&chrom=1&start=1000&end=2000 """ file_id = request.args.get('file_id') chrom = request.args.get('chrom') start = request.args.get('start') end = request.args.get('end') public = request.args.get('public') params = [file_id, chrom, start, end] # Display the parameters available if sum([x is None for x in params]) == len(params): return help_usage(None, 200, ['file_id', 'chrom', 'start', 'end'], {}) # ERROR - one of the required parameters is NoneType if sum([x is not None for x in params]) != len(params): return help_usage('MissingParameters', 400, ['file_id', 'chrom', 'start', 'end'], {}) if user_id is not None: selected_user_id = user_id['user_id'] if public is not None: selected_user_id = user_id['public_id'] dmp_api = _get_dm_api(selected_user_id) file_obj = dmp_api.get_file_by_id(selected_user_id, file_id, False) if file_obj is None or 'file_path' not in file_obj: return help_usage( 'MissingParameters', 400, ['file_id', 'chrom', 'start', 'end'], {} ) params = [file_id, chrom, start, end] output_str = '' if file_obj['file_type'] in ['bed', 'bb']: reader = bigbed_reader(user_id, file_obj['file_path']) output_str = reader.get_range(chrom, start, end, 'bed') elif file_obj['file_type'] in ['wig', 'bw']: print(chrom, start, end, 'wig') reader = bigwig_reader(user_id, file_obj['file_path']) output_str = reader.get_range(chrom, start, end, 'wig') # elif file_obj['file_type'] in ['gff3', 'tsv', 'tbi']: # reader = tabix_reader(file_obj['file_path']) # output_str = reader.get_range(chrom, start, end, 'gff3') resp = make_response(output_str, 'application/tsv') resp.headers["Content-Type"] = "text" return resp return help_usage('Forbidden', 403, ['file_id'], {})
[docs]class Ping(Resource): """ Class to handle the http requests to ping a service """
[docs] def get(self): """ GET Status List the current status of the service along with the relevant information about the version. Example ------- .. code-block:: none :linenos: curl -X GET http://localhost:5003/mug/api/dmp/ping """ import rest.release as release res = { "status": "ready", "version": release.__version__, "author": release.__author__, "license": release.__license__, "name": release.__rest_name__, "description": release.__description__, "_links" : { '_self' : request.base_url, '_parent' : request.url_root + 'mug/api/dmp' } } return res
# # For the services where there needs to be an extra layer (adjacency lists), # then there needs to be a way of forwarding for this. But the majority of # things can be redirected to the raw files for use as a track. # sys._auth_meta_json = os.path.dirname(os.path.realpath(__file__)) + '/auth_meta.json' # Define the URIs and their matching methods REST_API = Api(APP) # List the available end points for this service REST_API.add_resource(EndPoints, "/mug/api/dmp/file", endpoint='file_root') # Get the data for a specific track REST_API.add_resource(FileRegion, "/mug/api/dmp/file/region", endpoint='region') # List the available species for which there are datasets available REST_API.add_resource(File, "/mug/api/dmp/file/whole", endpoint='file') # Service ping REST_API.add_resource(Ping, "/mug/api/dmp/file/ping", endpoint='ping') # Initialise the server if __name__ == "__main__": APP.run(port=5003, debug=True, use_reloader=False)