"""
.. 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
from flask import Flask, request
from flask_restful import Api, Resource
from reader.hdf5_coord import coord
from mg_rest_util.mg_auth import authorized
APP = Flask(__name__)
#app.config['DEBUG'] = False
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 = {
'user_id': ['User ID', 'str', 'REQUIRED'],
'file_id': ['File ID', 'str', 'REQUIRED'],
'chrom': ['Chromosome', 'str', 'REQUIRED'],
'start': ['Start', 'int', 'REQUIRED'],
'end': ['End', 'int', 'REQUIRED'],
'res': ['Resolution', 'int', 'REQUIRED'],
'region': ['Region ID', 'int', 'REQUIRED'],
'model': ['Model ID', 'str', 'REQUIRED'],
'page': ['Page number (default: 0)', 'int', 'OPTIONAL'],
'mpp': ['Models per page (default: 10; max: 100)', 'int', '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 != None:
message['error'] = error_message
return message
def _get_dm_api(user_id, file_id, resolution=None):
cnf_loc = os.path.dirname(os.path.abspath(__file__)) + '/mongodb.cnf'
return coord(user_id["user_id"], file_id, resolution, cnf_loc)
[docs]class GetEndPoints(Resource):
"""
Class to handle the http requests for returning information about the end
points
"""
[docs] @staticmethod
def get():
"""
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:5001/mug/api/3dcoord
"""
return {
'_links': {
'_self': request.base_url,
'_resolutions': request.url_root + 'mug/api/3dcoord/resolutions',
'_chromosomes': request.url_root + 'mug/api/3dcoord/chromosomes',
'_regions': request.url_root + 'mug/api/3dcoord/regions',
'_models': request.url_root + 'mug/api/3dcoord/models',
'_model': request.url_root + 'mug/api/3dcoord/model',
'_ping': request.url_root + 'mug/api/3dcoord/ping',
'_parent': request.url_root + 'mug/api'
}
}
[docs]class GetResolutions(Resource):
"""
Class to handle the http requests for returning information about the
resolutions that models have been generated for
"""
[docs] @authorized
def get(self, user_id):
"""
GET List available resolutions from dataset
Parameters
----------
user_id : str
User ID
file_id : str
Identifier of the file to retrieve data from
Returns
-------
file : json
JSON file listing the available resolutions within the dataset
Examples
--------
.. code-block:: none
:linenos:
curl -X GET http://localhost:5001/mug/api/3dcoord/resolutions?user_id=test&file_id=test_file
"""
if user_id is not None:
file_id = request.args.get('file_id')
params_required = ['file_id']
params = [user_id, file_id]
# Display the parameters available
if sum([x is None for x in params]) == len(params):
return help_usage(None, 200, params_required, {})
# 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,
params_required,
{'file_id': file_id}
)
hdf5_handle = _get_dm_api(user_id, file_id)
resolution_list = hdf5_handle.get_resolutions()
hdf5_handle.close()
data = {}
resolutions = []
for res in resolution_list:
chr_url = request.url_root
chr_url += 'mug/api/3dcoord/chromosomes?file_id=' + file_id
chr_url += '&res=' + str(res)
resolutions.append(
{
'resolution': res,
'_links': {
'_chromosomes': chr_url
}
}
)
data['resolutions'] = resolutions
data['_links'] = {
'_self': request.base_url + '?file_id=' + file_id,
'_parent': request.url_root + 'mug/api/3dcoord'
}
return data
return help_usage('Forbidden', 403, ['file_id'], {})
[docs]class GetChromosomes(Resource):
"""
Class to handle the http requests for returning information about the
chromosomes that the models have been generated across
"""
[docs] @authorized
def get(self, user_id):
"""
GET List available chromosomes from dataset
Parameters
----------
user_id : str
User ID
file_id : str
Identifier of the file to retrieve data from
res : int
Resolution
Returns
-------
file : json
JSON file listing the available chromosomes within a dataset at a
given resolution
Examples
--------
.. code-block:: none
:linenos:
curl -X GET http://localhost:5001/mug/api/3dcoord/chromosomes?user_id=test&file_id=test_file
"""
if user_id is not None:
file_id = request.args.get('file_id')
resolution = request.args.get('res')
params_required = ['file_id', 'res']
params = [user_id, file_id, resolution]
# Display the parameters available
if sum([x is None for x in params]) == len(params):
return help_usage(None, 200, params_required, {})
# 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,
params_required,
{'file_id': file_id}
)
try:
resolution = int(resolution)
except ValueError:
# ERROR - one of the parameters is not of integer type
return help_usage(
'IncorrectParameterType',
400,
params_required,
{'file_id': file_id, 'res': resolution}
)
hdf5_handle = _get_dm_api(user_id, file_id, resolution)
chromosome_list = hdf5_handle.get_chromosomes()
hdf5_handle.close()
data = {}
chromosomes = []
for chrom in chromosome_list:
region_url = request.url_root
region_url += 'mug/api/3dcoord/regions?file_id=' + file_id
region_url += '&res=' + str(resolution)
region_url += '&chrom=' + str(chrom)
region_url += '&start=0&end=1000000000'
chromosomes.append(
{
'chromosome': chrom,
'_links': {
'_regions': region_url
}
}
)
data['resolution'] = resolution
data['chromosomes'] = chromosomes
self_url = request.base_url + '?file_id=' + file_id + '&res=' + str(resolution),
res_url = request.url_root + 'mug/api/3dcoord/resolutions?file_id=' + file_id
data['_links'] = {
'_self': self_url,
'_parent': request.url_root + 'mug/api/3dcoord',
'_resolution': res_url
}
return data
return help_usage('Forbidden', 403, ['file_id', 'res'], {})
[docs]class GetRegions(Resource):
"""
Class to handle the http requests for returning information about the
regions that are available in a given region and level of resolution
"""
[docs] @authorized
def get(self, user_id):
"""
GET List available models from dataset
Parameters
----------
user_id : str
User ID
file_id : str
Identifier of the file to retrieve data from
res : int
Resolution
chrom : str
Chromosome identifier (1, 2, 3, chr1, chr2, chr3, I, II, III, etc)
for the chromosome of interest
start : int
Start position for a selected region
end : int
End position for a selected region
Returns
-------
file : json
JSON file listing the available models within a dataset at a
given resolution and chromosomal region
Examples
--------
.. code-block:: none
:linenos:
curl -X GET http://localhost:5001/mug/api/3dcoord/regions?user_id=test&file_id=test_file&res=1000000&chrom=1&start=1&end=1000000
"""
if user_id is not None:
file_id = request.args.get('file_id')
resolution = request.args.get('res')
chr_id = request.args.get('chrom')
start = request.args.get('start')
end = request.args.get('end')
params_required = ['file_id', 'res', 'chrom', 'start', 'end']
params = [user_id, file_id, resolution, chr_id, start, end]
# Display the parameters available
if sum([x is None for x in params]) == len(params):
return help_usage(None, 200, params_required, {})
# 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,
params_required,
{
'file_id': file_id,
'res': resolution,
'chrom': chr_id,
'start': start,
'end': end
}
)
try:
start = int(start)
end = int(end)
resolution = int(resolution)
except ValueError:
# ERROR - one of the parameters is not of integer type
return help_usage(
'IncorrectParameterType',
400,
params_required,
{
'file_id': file_id,
'res': resolution,
'chrom': chr_id,
'start': start,
'end': end
}
)
hdf5_handle = _get_dm_api(user_id, file_id, resolution)
region_list = hdf5_handle.get_regions(chr_id, start, end)
hdf5_handle.close()
data = {}
regions = []
for reg in region_list:
model_url = request.url_root + 'mug/api/3dcoord/models?file_id=' + file_id
model_url += '&res=' + str(resolution)
model_url += '®ion=' + reg
regions.append(
{
'region_id': reg,
'_links': {
'_models': model_url
}
}
)
data['resolution'] = resolution,
data['chromosome'] = chr_id,
data['regions'] = regions
data['_links'] = {
'_self': request.base_url + '?file_id=' + file_id + '&res=' + str(resolution) + '&chrom=' + str(chr_id) + '&start=' + str(start) + '&end=' + str(end),
'_parent': request.url_root + 'mug/api/3dcoord',
'_resolution': request.url_root + 'mug/api/3dcoord/resolutions?file_id=' + file_id,
'_chromosomes': request.url_root + 'mug/api/3dcoord/chromosomes?file_id=' + file_id + '&res=' + str(resolution)
}
return data
return help_usage('Forbidden', 403, ['file_id', 'res', 'chrom', 'start', 'end'], {})
[docs]class GetModels(Resource):
"""
Class to handle the http requests for returning information about the models
that are available within a given region.
"""
[docs] @authorized
def get(self, user_id):
"""
GET List available models from dataset
Parameters
----------
user_id : str
User ID
file_id : str
Identifier of the file to retrieve data from
res : int
Resolution
region : str
Region ID
Returns
-------
file : json
JSON file listing the available models within a dataset at a
given resolution and chromosomal region
Examples
--------
.. code-block:: none
:linenos:
curl -X GET http://localhost:5001/mug/api/3dcoord/models?user_id=test&file_id=test_file&res=1000000®ion=1
"""
if user_id is not None:
file_id = request.args.get('file_id')
resolution = request.args.get('res')
region_id = request.args.get('region')
params_required = ['file_id', 'res', 'region']
params = [user_id, file_id, resolution, region_id]
# Display the parameters available
if sum([x is None for x in params]) == len(params):
return help_usage(None, 200, params_required, {})
# 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,
params_required,
{
'file_id': file_id,
'res': resolution,
'region': region_id
}
)
try:
resolution = int(resolution)
except ValueError:
# ERROR - one of the parameters is not of integer type
return help_usage(
'IncorrectParameterType',
400,
params_required,
{
'file_id': file_id,
'res': resolution,
'region': region_id
}
)
hdf5_handle = _get_dm_api(user_id, file_id, resolution)
model_list = hdf5_handle.get_models(region_id)
region_list = hdf5_handle.get_region_order(region=region_id)
hdf5_handle.close()
models = {}
models['model_list'] = [
{
'model': str(m[0]),
'cluster': str(m[1]),
'_links': {
'_model': request.url_root + 'mug/api/3dcoord/model?file_id=' + file_id + '&res=' + str(resolution) + '®ion=' + str(region_id) + '&model=' + str(m[0])
}
} for m in model_list
]
models['_links'] = {
'_self': request.base_url + '?file_id=' + file_id + '&res=' + str(resolution) + '®ion=' + str(region_id),
'_parent': request.url_root + 'mug/api/3dcoord',
'_models_all': request.url_root + 'mug/api/3dcoord/model?file_id=' + file_id + '&res=' + str(resolution) + '®ion=' + str(region_id) + '&model=all'
}
current_region = region_list.index(region_id)
next_region = current_region+1
previous_region = current_region-1
if current_region < (len(region_list)-1):
models['_links']['_next_region'] = request.url_root + 'mug/api/3dcoord/models?file_id=' + file_id + '&res=' + str(resolution) + '®ion=' + region_list[next_region]
if current_region > 0:
models['_links']['_previous_region'] = request.url_root + 'mug/api/3dcoord/models?file_id=' + file_id + '&res=' + str(resolution) + '®ion=' + region_list[previous_region]
return models
return help_usage('Forbidden', 403, ['file_id', 'res', 'region'], {})
[docs]class GetModel(Resource):
"""
Class to handle the http requests for returning the models from a given
region. The list of models is a comma separated list that can return
multiple models from the same region
"""
[docs] @authorized
def get(self, user_id):
"""
GET List available model from dataset
Parameters
----------
user_id : str
User ID
file_id : str
Identifier of the file to retrieve data from
res : int
Resolution
region : str
Region ID
model : str
model ID
Returns
-------
file : json
JSON file listing the available models within a dataset at a
given resolution and chromosomal region
Examples
--------
.. code-block:: none
:linenos:
curl -X GET http://localhost:5001/mug/api/3dcoord/model?user_id=test&file_id=test_file®ion=1&model=model1
"""
if user_id is not None:
file_id = request.args.get('file_id')
resolution = request.args.get('res')
region_id = request.args.get('region')
model_str = request.args.get('model')
page = request.args.get('page')
mpp = request.args.get('mpp')
params_required = ['file_id', 'res', 'region', 'model']
params = [user_id, file_id, resolution, region_id, model_str]
# Display the parameters available
if sum([x is None for x in params]) == len(params):
return help_usage(None, 200, params_required, {})
# 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,
params_required,
{
'file_id': file_id,
'res': resolution,
'region': region_id,
'model': model_str
}
)
if page is None:
page = 1
if mpp is None:
mpp = 10
try:
resolution = int(resolution)
page = int(page)
mpp = int(mpp)
except ValueError:
# ERROR - one of the parameters is not of integer type
return help_usage(
'IncorrectParameterType',
400,
params_required,
{
'file_id': file_id,
'res': resolution,
'region': region_id,
'model': model_str
}
)
if page < 1:
page = 1
hdf5_handle = _get_dm_api(user_id, file_id, resolution)
model_ids = model_str.split(',')
models, model_meta = hdf5_handle.get_model(region_id, model_ids, page-1, mpp)
hdf5_handle.close()
models['_links'] = {
'_self': request.base_url + '?file_id=' + file_id + '&res=' + str(resolution) + '®ion=' + str(region_id) + '&model=' + str(model_str) + '&mpp=' + str(mpp) + '&page=' +str(page),
'_parent': request.url_root + 'mug/api/3dcoord',
}
models['query_data'] = {
'model_count': model_meta['model_count'],
'page_count': model_meta['page_count'],
'page': page,
'mpp': mpp
}
if (page) < model_meta['page_count']:
models['_links']['_next_page'] = request.url_root + 'mug/api/3dcoord/model?file_id=' + file_id + '&res=' + str(resolution) + '®ion=' + str(region_id) + '&model=' + str(model_str) + '&mpp=' + str(mpp) + '&page=' +str(page+1)
if (page) > 1:
models['_links']['_previous_page'] = request.url_root + 'mug/api/3dcoord/model?file_id=' + file_id + '&res=' + str(resolution) + '®ion=' + str(region_id) + '&model=' + str(model_str) + '&mpp=' + str(mpp) + '&page=' +str(page-1)
return models
return help_usage('Forbidden', 403, ['file_id', 'res', 'region', 'model'], {})
[docs]class Ping(Resource):
"""
Class to handle the http requests to ping a service
"""
[docs] @staticmethod
def get():
"""
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:5001/mug/api/3dcoord/ping
"""
from . import release
res = {
"status": "ready",
"version": release.__version__,
"author": release.__author__,
"license": release.__license__,
"name": release.__rest_name__,
"description": release.__description__,
"_links": {
'_self': request.url_root + 'mug/api/3dcoord/ping',
'_parent': request.url_root + 'mug/api/3dcoord'
}
}
return res
################################################################################
API = Api(APP)
sys._auth_meta_json = os.path.dirname(os.path.realpath(__file__)) + '/auth_meta.json'
"""
Define the URIs and their matching methods
"""
# List the available end points for this service
API.add_resource(GetEndPoints, "/mug/api/3dcoord", endpoint='3dcoord_root')
# Show the available resolutions
API.add_resource(GetResolutions, "/mug/api/3dcoord/resolutions", endpoint='resolutions')
# Show the available chromosomes for a given resolution
API.add_resource(GetChromosomes, "/mug/api/3dcoord/chromosomes", endpoint='chromosomes')
# Show the available regions for a given chromosome, start, end and resolution
API.add_resource(GetRegions, "/mug/api/3dcoord/regions", endpoint='regions')
# Show the available models for a given region_id
API.add_resource(GetModels, "/mug/api/3dcoord/models", endpoint='models')
# Show the 3D coordinates of a model for a given region_id
API.add_resource(GetModel, "/mug/api/3dcoord/model", endpoint='model')
# Service ping
API.add_resource(Ping, "/mug/api/3dcoord/ping", endpoint='adjacency-ping')
# Initialise the server
if __name__ == "__main__":
APP.run()