diff --git a/README.md b/README.md index 81f5494..6c3b97c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![CI-CD](https://github.com/openCONTRABASS/CONTRABASS-webservice/actions/workflows/main.yml/badge.svg)](https://github.com/openCONTRABASS/CONTRABASS-webservice/actions/workflows/main.yml) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=openCONTRABASS_CONTRABASS-webservice&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=openCONTRABASS_CONTRABASS-webservice) +[![codecov](https://codecov.io/gh/openCONTRABASS/CONTRABASS-webservice/branch/main/graph/badge.svg?token=T1S7E99XJJ)](https://codecov.io/gh/openCONTRABASS/CONTRABASS-webservice) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + +```CONTRABASS RESTful API``` is a python-based asynchronous-tasks RESTful API intended for the computation and analysis of vulnerabilities in genome-scale metabolic models. These webservices are just a backed for [CONTRABASS python tool](https://github.com/openCONTRABASS/CONTRABASS). ## Table of Contents - [License](#license) diff --git a/src/restapi/app.py b/src/restapi/app.py index c062dfc..3a4ab71 100644 --- a/src/restapi/app.py +++ b/src/restapi/app.py @@ -16,6 +16,7 @@ along with this program. If not, see . """ import eventlet + eventlet.monkey_patch() import os @@ -45,13 +46,13 @@ from .exception_handler import init_exception_handler basedir = os.path.abspath(os.path.dirname(__file__)) -load_dotenv(os.path.join(basedir, '../../.env')) +load_dotenv(os.path.join(basedir, "../../.env")) LOGGER = logging.getLogger(__name__) app = Flask(__name__) -app.config.from_object(os.environ['APP_SETTINGS']) +app.config.from_object(os.environ["APP_SETTINGS"]) # Logger config logging.config.dictConfig(app.config["DICT_LOGGER"]) @@ -59,7 +60,7 @@ # Database config # MONGODB -''' +""" from .database import db app.config['MONGODB_SETTINGS'] = { @@ -69,25 +70,22 @@ 'alias': '' } db.init_app(app) -''' +""" # MYSQL db.init_app(app) LOGGER.info("Successfully init db connection") # Swagger config -app.config['SWAGGER'] = { - 'title': 'CONTRABASS API', - 'uiversion': 3 -} -Swagger(app, template_file='swagger.yml') +app.config["SWAGGER"] = {"title": "CONTRABASS API", "uiversion": 3} +Swagger(app, template_file="swagger.yml") LOGGER.info("Successfully init Swagger") # Register blueprints -app.register_blueprint(submit_bp, url_prefix='/') -app.register_blueprint(models_bp, url_prefix='/') -app.register_blueprint(results_bp, url_prefix='/') -app.register_blueprint(websockets_bp, url_prefix='/') +app.register_blueprint(submit_bp, url_prefix="/") +app.register_blueprint(models_bp, url_prefix="/") +app.register_blueprint(results_bp, url_prefix="/") +app.register_blueprint(websockets_bp, url_prefix="/") LOGGER.info("Successfully init blueprints") # Register exception handlers @@ -98,32 +96,38 @@ CORS(app) # Init websockets -socketio = SocketIO(app, \ - cors_allowed_origins='*', \ - logger=True, \ - engineio_logger=True, \ - message_queue=app.config['REDIS_BROKER_URL']) +socketio = SocketIO( + app, + cors_allowed_origins="*", + logger=True, + engineio_logger=True, + message_queue=app.config["REDIS_BROKER_URL"], +) @app.errorhandler(404) def page_not_found(e): - #return render_template('404.html'), 404 - response = jsonify({'message': "Not found"}) + # return render_template('404.html'), 404 + response = jsonify({"message": "Not found"}) response.status_code = 404 return response -@app.route('/hola/') + +@app.route("/hola/") def hello_name(name): return "Hello {}!".format(name) -@socketio.on('connect') + +@socketio.on("connect") def test_connect(): LOGGER.info("Client connected via websocket") -@socketio.on('join') + +@socketio.on("join") def on_join(room_name): LOGGER.info(f"socket.io/join {room_name}") join_room(room_name) -if __name__ == '__main__': + +if __name__ == "__main__": socketio.run(app) diff --git a/src/restapi/beans/Chokepoint.py b/src/restapi/beans/Chokepoint.py index fa36e32..49b940e 100644 --- a/src/restapi/beans/Chokepoint.py +++ b/src/restapi/beans/Chokepoint.py @@ -1,7 +1,4 @@ - - class Chokepoint: - def __init__(self, reaction, metabolite): self.__reaction = reaction self.__metabolite = metabolite diff --git a/src/restapi/beans/ConfigChokepoints.py b/src/restapi/beans/ConfigChokepoints.py index d8f23bd..9b7e147 100644 --- a/src/restapi/beans/ConfigChokepoints.py +++ b/src/restapi/beans/ConfigChokepoints.py @@ -1,6 +1,4 @@ - class ConfigReactionsSets: - def __init__(self): self.__objective = None self.__fraction_of_optimum = None diff --git a/src/restapi/beans/ConfigReactionsSets.py b/src/restapi/beans/ConfigReactionsSets.py index 3e36243..6451d5d 100644 --- a/src/restapi/beans/ConfigReactionsSets.py +++ b/src/restapi/beans/ConfigReactionsSets.py @@ -1,8 +1,8 @@ from .OptimizationEnum import * from .MediumEnum import * -class ConfigReactionsSets: +class ConfigReactionsSets: def __init__(self): self.__objective = None self.__fraction_of_optimum = None diff --git a/src/restapi/beans/MediumEnum.py b/src/restapi/beans/MediumEnum.py index 8f9aab2..6091520 100644 --- a/src/restapi/beans/MediumEnum.py +++ b/src/restapi/beans/MediumEnum.py @@ -1,5 +1,6 @@ from enum import Enum + class MediumEnum(Enum): DEFAULT = 1 - COMPLETE = 2 \ No newline at end of file + COMPLETE = 2 diff --git a/src/restapi/beans/ModelId.py b/src/restapi/beans/ModelId.py index f712e6d..7382b27 100644 --- a/src/restapi/beans/ModelId.py +++ b/src/restapi/beans/ModelId.py @@ -1,6 +1,4 @@ - class ModelId: - def __init__(self, model, metabolites, reactions, genes): self.model_uuid = model self.metabolites = metabolites diff --git a/src/restapi/beans/OptimizationEnum.py b/src/restapi/beans/OptimizationEnum.py index e6e5f05..a007a29 100644 --- a/src/restapi/beans/OptimizationEnum.py +++ b/src/restapi/beans/OptimizationEnum.py @@ -1,5 +1,6 @@ from enum import Enum + class OptimizationEnum(Enum): FBA = 1 - pFBA = 2 \ No newline at end of file + pFBA = 2 diff --git a/src/restapi/beans/ResponseChannel.py b/src/restapi/beans/ResponseChannel.py index 90b79b7..812379a 100644 --- a/src/restapi/beans/ResponseChannel.py +++ b/src/restapi/beans/ResponseChannel.py @@ -2,7 +2,6 @@ class ResponseChannel: - def __init__(self, channel=None): self.channel = channel @@ -14,6 +13,7 @@ def channel_attr(self): def channel_attr(self, channel): self.channel = channel + class ResponseChannelEncoder(JSONEncoder): def default(self, o): - return o.__dict__ \ No newline at end of file + return o.__dict__ diff --git a/src/restapi/beans/ResponseChokepoints.py b/src/restapi/beans/ResponseChokepoints.py index 824bd32..7db9270 100644 --- a/src/restapi/beans/ResponseChokepoints.py +++ b/src/restapi/beans/ResponseChokepoints.py @@ -2,7 +2,6 @@ class ResponseChokepoints: - def __init__(self, status=None, finished=None, result=None, pending_length=None): self.__status = status self.__finished = finished @@ -41,6 +40,7 @@ def pending_length(self): def pending_length(self, pending_length): self.__pending_length = pending_length + class ResponseChokepointsEncoder(JSONEncoder): def default(self, o): - return o.__dict__ \ No newline at end of file + return o.__dict__ diff --git a/src/restapi/beans/ResponseEndpoint.py b/src/restapi/beans/ResponseEndpoint.py index 5c9dc5b..73b6c66 100644 --- a/src/restapi/beans/ResponseEndpoint.py +++ b/src/restapi/beans/ResponseEndpoint.py @@ -2,7 +2,6 @@ class ResponseEndpoint: - def __init__(self, endpoint=None): self.endpoint = endpoint @@ -14,6 +13,7 @@ def endpoint_attr(self): def endpoint_attr(self, endpoint): self.endpoint = endpoint + class ResponseEndpointEncoder(JSONEncoder): def default(self, o): - return o.__dict__ \ No newline at end of file + return o.__dict__ diff --git a/src/restapi/beans/ResponseReport.py b/src/restapi/beans/ResponseReport.py index bb1d6c8..b75153a 100644 --- a/src/restapi/beans/ResponseReport.py +++ b/src/restapi/beans/ResponseReport.py @@ -2,8 +2,14 @@ class ResponseReport: - - def __init__(self, status=None, finished=None, file_spreadsheet=None, file_html=None, pending_length=None): + def __init__( + self, + status=None, + finished=None, + file_spreadsheet=None, + file_html=None, + pending_length=None, + ): self.status = status self.finished = finished self.file_spreadsheet = file_spreadsheet @@ -49,4 +55,3 @@ def pending_length_attr(self): @pending_length_attr.setter def pending_length_attr(self, pending_length): self.pending_length = pending_length - diff --git a/src/restapi/beans/TaskFormCriticalReactions.py b/src/restapi/beans/TaskFormCriticalReactions.py index 18820ac..85485ee 100644 --- a/src/restapi/beans/TaskFormCriticalReactions.py +++ b/src/restapi/beans/TaskFormCriticalReactions.py @@ -1,8 +1,18 @@ from wtforms import Form, StringField, DecimalField, validators + class TaskFormCriticalReactions(Form): - objective = StringField('objective', - [validators.optional(), validators.Length(min=1, max=256)]) - fraction_of_optimum = DecimalField('fraction_of_optimum', - [validators.optional(), - validators.NumberRange(min=0.0, max=1.0, message='Fraction of optimum must be in the range [0, 1]')]) + objective = StringField( + "objective", [validators.optional(), validators.Length(min=1, max=256)] + ) + fraction_of_optimum = DecimalField( + "fraction_of_optimum", + [ + validators.optional(), + validators.NumberRange( + min=0.0, + max=1.0, + message="Fraction of optimum must be in the range [0, 1]", + ), + ], + ) diff --git a/src/restapi/beans/TaskFormReactionsSets.py b/src/restapi/beans/TaskFormReactionsSets.py index c1267dc..19f266f 100644 --- a/src/restapi/beans/TaskFormReactionsSets.py +++ b/src/restapi/beans/TaskFormReactionsSets.py @@ -1,19 +1,40 @@ -from wtforms import Form, StringField, DecimalField, SelectField, BooleanField, validators +from wtforms import ( + Form, + StringField, + DecimalField, + SelectField, + BooleanField, + validators, +) from .OptimizationEnum import * from .MediumEnum import * + class TaskFormReactionsSets(Form): - objective = StringField('objective', - [validators.optional(), validators.Length(min=1, max=256)]) - fraction_of_optimum = DecimalField('fraction_of_optimum', - [validators.optional(), - validators.NumberRange(min=0.0, max=1.0, message='Fraction of optimum must be in the range [0, 1]')]) - medium = SelectField(u'Growth medium', - [validators.optional()], - choices=[name for name, member in MediumEnum.__members__.items()]) - optimization = SelectField(u'Growth Optimization', - [validators.optional()], - choices=[name for name, member in OptimizationEnum.__members__.items()]) - skip_knockout = BooleanField(u'Skip knock-out computation', - [validators.optional()], - default=True) + objective = StringField( + "objective", [validators.optional(), validators.Length(min=1, max=256)] + ) + fraction_of_optimum = DecimalField( + "fraction_of_optimum", + [ + validators.optional(), + validators.NumberRange( + min=0.0, + max=1.0, + message="Fraction of optimum must be in the range [0, 1]", + ), + ], + ) + medium = SelectField( + u"Growth medium", + [validators.optional()], + choices=[name for name, member in MediumEnum.__members__.items()], + ) + optimization = SelectField( + u"Growth Optimization", + [validators.optional()], + choices=[name for name, member in OptimizationEnum.__members__.items()], + ) + skip_knockout = BooleanField( + u"Skip knock-out computation", [validators.optional()], default=True + ) diff --git a/src/restapi/beans/TaskInit.py b/src/restapi/beans/TaskInit.py index 04ff848..e626043 100644 --- a/src/restapi/beans/TaskInit.py +++ b/src/restapi/beans/TaskInit.py @@ -1,7 +1,4 @@ - - class TaskInit: - def __init__(self, task_id, pending_length=None): self.task_id = task_id self.pending_length = pending_length @@ -20,4 +17,4 @@ def pending_length_attr(self): @pending_length_attr.setter def pending_length_attr(self, pending_length): - self.pending_length = pending_length \ No newline at end of file + self.pending_length = pending_length diff --git a/src/restapi/beans/WebsocketEvent.py b/src/restapi/beans/WebsocketEvent.py index 353a30a..5dd067c 100644 --- a/src/restapi/beans/WebsocketEvent.py +++ b/src/restapi/beans/WebsocketEvent.py @@ -1,7 +1,7 @@ from json import JSONEncoder -class WebsocketEvent: +class WebsocketEvent: def __init__(self, event, message): self.event = event self.message = message @@ -22,6 +22,7 @@ def message_attr(self): def message_attr(self, message): self.message = message + class ResponseWebsocketEventEncoder(JSONEncoder): def default(self, o): - return o.__dict__ \ No newline at end of file + return o.__dict__ diff --git a/src/restapi/blueprints/models/models_bp.py b/src/restapi/blueprints/models/models_bp.py index c29c528..279677c 100644 --- a/src/restapi/blueprints/models/models_bp.py +++ b/src/restapi/blueprints/models/models_bp.py @@ -21,8 +21,11 @@ from flask import Blueprint, jsonify, current_app, request -from src.restapi.tasks.tasks import compute_chokepoints_task, task_compute_critical_reactions, \ - task_compute_growth_dependent_reactions +from src.restapi.tasks.tasks import ( + compute_chokepoints_task, + task_compute_critical_reactions, + task_compute_growth_dependent_reactions, +) from src.restapi.celery_app import celery_app, get_pending_tasks_length from src.restapi.service.ModelService import ModelService @@ -35,17 +38,20 @@ from src.restapi.validation import sanitize_string, empty_string -models_bp = Blueprint('models_bp', __name__, - template_folder='templates', - static_folder='static', - static_url_path='assets') +models_bp = Blueprint( + "models_bp", + __name__, + template_folder="templates", + static_folder="static", + static_url_path="assets", +) LOGGER = logging.getLogger(__name__) model_service = ModelService() # Temporarily disabled -#@models_bp.route('/models//chokepoints', methods=['POST']) +# @models_bp.route('/models//chokepoints', methods=['POST']) def compute_chokepoints(uuid): LOGGER.info(f"POST /models/{uuid}/chokepoints") @@ -53,7 +59,7 @@ def compute_chokepoints(uuid): sanitize_string(str(uuid)) filename = model_service.query_by_uuid(uuid).url - path = os.path.join(current_app.config['UPLOAD_FOLDER'], filename) + path = os.path.join(current_app.config["UPLOAD_FOLDER"], filename) task = compute_chokepoints_task.delay(path) task_uuid = task.id @@ -63,7 +69,7 @@ def compute_chokepoints(uuid): return response -@models_bp.route('/models//critical_reactions', methods=['POST']) +@models_bp.route("/models//critical_reactions", methods=["POST"]) def compute_critical_reactions(uuid): request_data = request.form @@ -86,17 +92,17 @@ def compute_critical_reactions(uuid): fraction_of_optimum = float(form.fraction_of_optimum.data) filename = model_service.query_by_uuid(uuid).url - pathname = current_app.config['UPLOAD_FOLDER'] + pathname = current_app.config["UPLOAD_FOLDER"] path = os.path.join(pathname, filename) - output_dir = current_app.config['STATIC_FOLDER'] - output_filename = str(uuid) + current_app.config['OUTPUT_FILE_EXTENSION'] + output_dir = current_app.config["STATIC_FOLDER"] + output_filename = str(uuid) + current_app.config["OUTPUT_FILE_EXTENSION"] task = task_compute_critical_reactions.delay( path, # model path output_dir + "/" + output_filename, # output path objective=objective, fraction_of_optimum=fraction_of_optimum, - model_uuid=str(uuid) + model_uuid=str(uuid), ) task_uuid = task.id @@ -104,7 +110,8 @@ def compute_critical_reactions(uuid): LOGGER.info(f"Response: {response}") return response -@models_bp.route('/models//growth_dependent_reactions', methods=['POST']) + +@models_bp.route("/models//growth_dependent_reactions", methods=["POST"]) def compute_growth_dependent_reactions(uuid): request_data = request.form @@ -123,24 +130,23 @@ def compute_growth_dependent_reactions(uuid): objective = str(form.objective.data) filename = model_service.query_by_uuid(uuid).url - pathname = current_app.config['UPLOAD_FOLDER'] + pathname = current_app.config["UPLOAD_FOLDER"] path = os.path.join(pathname, filename) - output_dir = current_app.config['STATIC_FOLDER'] - output_filename = str(uuid) + current_app.config['OUTPUT_FILE_EXTENSION'] + output_dir = current_app.config["STATIC_FOLDER"] + output_filename = str(uuid) + current_app.config["OUTPUT_FILE_EXTENSION"] task = task_compute_growth_dependent_reactions.delay( - path, - output_dir + "/" + output_filename, # output path - objective, - str(uuid)) + path, output_dir + "/" + output_filename, objective, str(uuid) # output path + ) task_uuid = task.id response = jsonify(vars(TaskInit(task_uuid, get_pending_tasks_length()))) LOGGER.info(f"Response: {response}") return response + # Temporarily disabled -#@models_bp.route('/models//report_reactions', methods=['POST']) +# @models_bp.route('/models//report_reactions', methods=['POST']) def compute_report_reactions(uuid): request_data = request.form @@ -171,10 +177,10 @@ def compute_report_reactions(uuid): skip_knockout = bool(form.skip_knockout.data) filename = model_service.query_by_uuid(uuid).url - pathname = current_app.config['UPLOAD_FOLDER'] + pathname = current_app.config["UPLOAD_FOLDER"] path = os.path.join(pathname, filename) - output_dir = current_app.config['STATIC_FOLDER'] - output_filename = str(uuid) + current_app.config['OUTPUT_FILE_EXTENSION'] + output_dir = current_app.config["STATIC_FOLDER"] + output_filename = str(uuid) + current_app.config["OUTPUT_FILE_EXTENSION"] config = ConfigReactionsSets() config.fraction_of_optimum = fraction_of_optimum @@ -183,7 +189,9 @@ def compute_report_reactions(uuid): config.optimization = optimization config.skip_knockout_computation = skip_knockout - task = compute_sets_report_task.delay(path, output_dir, output_filename, str(uuid), config) + task = compute_sets_report_task.delay( + path, output_dir, output_filename, str(uuid), config + ) task_uuid = task.id response = jsonify(vars(TaskInit(task_uuid, get_pending_tasks_length()))) diff --git a/src/restapi/blueprints/results/results_bp.py b/src/restapi/blueprints/results/results_bp.py index 1a8eed6..0524a3a 100644 --- a/src/restapi/blueprints/results/results_bp.py +++ b/src/restapi/blueprints/results/results_bp.py @@ -23,7 +23,11 @@ from flask import Blueprint, jsonify from celery.result import AsyncResult -from src.restapi.celery_app import celery_app, get_pending_tasks_length, get_task_pending_position +from src.restapi.celery_app import ( + celery_app, + get_pending_tasks_length, + get_task_pending_position, +) from src.restapi.service.ModelService import ModelService from src.restapi.beans.ResponseChokepoints import * @@ -33,17 +37,20 @@ from src.restapi.constants import RESPONSE_TASK_NONE -results_bp = Blueprint('results_bp', __name__, - template_folder='templates', - static_folder='static', - static_url_path='assets') +results_bp = Blueprint( + "results_bp", + __name__, + template_folder="templates", + static_folder="static", + static_url_path="assets", +) LOGGER = logging.getLogger(__name__) model_service = ModelService() # Temporarily disabled -#@results_bp.route('/results//chokepoints', methods=['GET']) +# @results_bp.route('/results//chokepoints', methods=['GET']) def get_chokepoints(uuid): LOGGER.info(f"GET /results/{uuid}/chokepoints") @@ -68,7 +75,8 @@ def get_chokepoints(uuid): LOGGER.info(f"Response: {response}") return response -@results_bp.route('/results//critical_reactions', methods=['GET']) + +@results_bp.route("/results//critical_reactions", methods=["GET"]) def get_critical_reactions(uuid): LOGGER.info(f"GET /results/{uuid}/critical_reactions") @@ -96,7 +104,8 @@ def get_critical_reactions(uuid): LOGGER.info(f"Response: {response}") return response -@results_bp.route('/results//growth_dependent_reactions', methods=['GET']) + +@results_bp.route("/results//growth_dependent_reactions", methods=["GET"]) def get_growth_dependent_reactions(uuid): LOGGER.info(f"GET /results/{uuid}/growth_dependent_reactions") @@ -124,8 +133,9 @@ def get_growth_dependent_reactions(uuid): LOGGER.info(f"Response: {response}") return response + # Temporarily disabled -#@results_bp.route('/models//report_reactions', methods=['GET']) +# @results_bp.route('/models//report_reactions', methods=['GET']) def get_report_reactions(uuid): LOGGER.info(f"GET /models/{uuid}/report_reactions") @@ -150,8 +160,9 @@ def get_report_reactions(uuid): LOGGER.info(f"Response: {response}") return response + # Terminate a running task -@results_bp.route('/results//terminate', methods=['POST']) +@results_bp.route("/results//terminate", methods=["POST"]) def terminate_task(uuid): LOGGER.info(f"POST /results/{uuid}/terminate ") @@ -160,8 +171,7 @@ def terminate_task(uuid): result = ResponseReport() async_result = AsyncResult(id=str(uuid), app=celery_app) - async_result.revoke(signal='SIGKILL') + async_result.revoke(signal="SIGKILL") LOGGER.info(f"Response: 'success':True 200") - return json.dumps({'success': True}), 200, {'ContentType': 'application/json'} - + return json.dumps({"success": True}), 200, {"ContentType": "application/json"} diff --git a/src/restapi/blueprints/submit/submit_bp.py b/src/restapi/blueprints/submit/submit_bp.py index 7ee13ad..1335d44 100644 --- a/src/restapi/blueprints/submit/submit_bp.py +++ b/src/restapi/blueprints/submit/submit_bp.py @@ -32,35 +32,39 @@ from werkzeug.utils import secure_filename -submit_bp = Blueprint('submit_bp', __name__, - template_folder='templates', - static_folder='static', - static_url_path='assets') +submit_bp = Blueprint( + "submit_bp", + __name__, + template_folder="templates", + static_folder="static", + static_url_path="assets", +) LOGGER = logging.getLogger(__name__) model_service = ModelService() -@submit_bp.route('/submit', methods=['POST']) + +@submit_bp.route("/submit", methods=["POST"]) def submit_model(): LOGGER.info("POST /submit") - if 'file' not in request.files: + if "file" not in request.files: raise ValidationException("No file part") - file = request.files['file'] - if file.filename == '': + file = request.files["file"] + if file.filename == "": raise ValidationException("No selected file") - if not (file and allowed_file(file.filename)) : + if not (file and allowed_file(file.filename)): raise ValidationException("Invalid file extension") filename, file_extension = os.path.splitext(secure_filename(file.filename)) model_uuid = str(uuid.uuid4()) filename = f"{model_uuid}{file_extension}" - path = os.path.join(current_app.config['UPLOAD_FOLDER'], filename) + path = os.path.join(current_app.config["UPLOAD_FOLDER"], filename) LOGGER.info(f"Saving model to: {filename}") file.save(path) @@ -75,6 +79,11 @@ def submit_model(): model_service.insert(model_uuid, filename) - response = ModelId(model_uuid, len(cobra_model.metabolites), len(cobra_model.reactions), len(cobra_model.genes)).__dict__ + response = ModelId( + model_uuid, + len(cobra_model.metabolites), + len(cobra_model.reactions), + len(cobra_model.genes), + ).__dict__ LOGGER.info(f"Response: {response}") return jsonify(response) diff --git a/src/restapi/blueprints/websockets/websockets_bp.py b/src/restapi/blueprints/websockets/websockets_bp.py index d267656..203af9e 100644 --- a/src/restapi/blueprints/websockets/websockets_bp.py +++ b/src/restapi/blueprints/websockets/websockets_bp.py @@ -28,27 +28,32 @@ from src.restapi.constants import * from src.restapi.validation import * -websockets_bp = Blueprint('websockets_bp', __name__, - template_folder='templates', - static_folder='static', - static_url_path='assets') +websockets_bp = Blueprint( + "websockets_bp", + __name__, + template_folder="templates", + static_folder="static", + static_url_path="assets", +) LOGGER = logging.getLogger(__name__) model_service = ModelService() -@websockets_bp.route('/websockets/get_endpoint', methods=['GET']) + +@websockets_bp.route("/websockets/get_endpoint", methods=["GET"]) def get_endpoint(): LOGGER.info(f"GET /websockets/get_endpoint") - result = ResponseEndpoint(current_app.config['WEBSOCKETS_URL']) + result = ResponseEndpoint(current_app.config["WEBSOCKETS_URL"]) response = jsonify(vars(result)) LOGGER.info(f"Response: {response}") return response -@websockets_bp.route('/websockets/notification_channel/', methods=['GET']) + +@websockets_bp.route("/websockets/notification_channel/", methods=["GET"]) def get_notification_channel(uuid): LOGGER.info(f"GET /websockets/notification_channel/{uuid}") @@ -65,7 +70,8 @@ def get_notification_channel(uuid): LOGGER.info(f"Response: {response}") return response -@websockets_bp.route('/websockets/example_event_join', methods=['GET']) + +@websockets_bp.route("/websockets/example_event_join", methods=["GET"]) def get_example_join_event(): LOGGER.info(f"GET /websockets/example_event_join") @@ -76,12 +82,15 @@ def get_example_join_event(): LOGGER.info(f"Response: {response}") return response -@websockets_bp.route('/websockets/example_event_message', methods=['GET']) + +@websockets_bp.route("/websockets/example_event_message", methods=["GET"]) def get_example_event_message(): LOGGER.info(f"GET /websockets/example_event_message") - result = WebsocketEvent("00000000-0000-0000-0000-000000000000", "<>") + result = WebsocketEvent( + "00000000-0000-0000-0000-000000000000", "<>" + ) response = jsonify(vars(result)) LOGGER.info(f"Response: {response}") diff --git a/src/restapi/celery_app.py b/src/restapi/celery_app.py index 52b95be..4ffdab2 100644 --- a/src/restapi/celery_app.py +++ b/src/restapi/celery_app.py @@ -22,14 +22,14 @@ from dotenv import load_dotenv basedir = os.path.abspath(os.path.dirname(__file__)) -load_dotenv(os.path.join(basedir, '../../.env')) +load_dotenv(os.path.join(basedir, "../../.env")) from celery import Celery celery_app = Celery( - broker=os.environ.get('CELERY_BROKER_URL'), - backend=os.environ.get('CELERY_RESULT_BACKEND'), + broker=os.environ.get("CELERY_BROKER_URL"), + backend=os.environ.get("CELERY_RESULT_BACKEND"), ) celery_app.conf.update( @@ -40,12 +40,13 @@ result_expires=7200, # 2 hours # Always restart workers after finishing. worker_max_tasks_per_child=1, - task_serializer='pickle', - result_serializer='pickle', - accept_content=['pickle'], - imports = (os.environ.get('CELERY_IMPORTS'),) + task_serializer="pickle", + result_serializer="pickle", + accept_content=["pickle"], + imports=(os.environ.get("CELERY_IMPORTS"),), ) + def get_pending_tasks_length(): i = celery_app.control.inspect() if i.reserved() is None: @@ -54,6 +55,7 @@ def get_pending_tasks_length(): len_tasks = len(list(itertools.chain.from_iterable(i.reserved().values()))) return len_tasks + def get_task_pending_position(task_uuid): i = celery_app.control.inspect() if i.reserved() is None: @@ -63,4 +65,4 @@ def get_task_pending_position(task_uuid): g = [i for i, e in enumerate(v) if e["id"] == task_uuid] if len(g) != 0: return g[0] - return 0 # Default value \ No newline at end of file + return 0 # Default value diff --git a/src/restapi/config.py b/src/restapi/config.py index d81fa13..15b628e 100644 --- a/src/restapi/config.py +++ b/src/restapi/config.py @@ -20,7 +20,7 @@ from dotenv import load_dotenv basedir = os.path.abspath(os.path.dirname(__file__)) -load_dotenv(os.path.join(basedir, '.env')) +load_dotenv(os.path.join(basedir, ".env")) class Config(object): @@ -28,20 +28,18 @@ class Config(object): TESTING = False CSRF_ENABLED = True - SECRET_KEY = os.environ.get('SECRET_KEY') + SECRET_KEY = os.environ.get("SECRET_KEY") OUTPUT_FILE_EXTENSION = ".xls" MAX_CONTENT_LENGTH = 1024 * 1024 * 30 # 30MB max file size DICT_LOGGER = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'simple': { - 'format': ( - "%(asctime)s [%(levelname)s] [%(name)s] | %(message)s" - ) + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": { + "format": ("%(asctime)s [%(levelname)s] [%(name)s] | %(message)s") }, }, } @@ -50,43 +48,43 @@ class Config(object): class ProductionConfig(Config): DEBUG = False - SQLALCHEMY_DATABASE_URI = f"mysql+pymysql:" \ - + f"//{os.environ.get('MYSQL_USER')}:" \ - + f"{os.environ.get('MYSQL_PASSWORD')}" \ - + f"@{os.environ.get('MYSQL_HOST')}" \ - f"/{os.environ.get('MYSQL_DATABASE')}" + SQLALCHEMY_DATABASE_URI = ( + f"mysql+pymysql:" + + f"//{os.environ.get('MYSQL_USER')}:" + + f"{os.environ.get('MYSQL_PASSWORD')}" + + f"@{os.environ.get('MYSQL_HOST')}" + f"/{os.environ.get('MYSQL_DATABASE')}" + ) SQLALCHEMY_POOL_SIZE = 1 - CELERY_IMPORTS = ("app") - CELERY_BROKER_URL = os.environ.get('CELERY_BROKER_URL') - CELERY_RESULT_BACKEND = os.environ.get('CELERY_RESULT_BACKEND') - REDIS_BROKER_URL = os.environ.get('REDIS_BROKER_URL') - WEBSOCKETS_URL = os.environ.get('WEBSOCKETS_URL') + CELERY_IMPORTS = "app" + CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL") + CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND") + REDIS_BROKER_URL = os.environ.get("REDIS_BROKER_URL") + WEBSOCKETS_URL = os.environ.get("WEBSOCKETS_URL") - UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER') - STATIC_FOLDER = os.environ.get('STATIC_FOLDER') + UPLOAD_FOLDER = os.environ.get("UPLOAD_FOLDER") + STATIC_FOLDER = os.environ.get("STATIC_FOLDER") DICT_LOGGER = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'simple': { - 'format': ( - "%(asctime)s [%(levelname)s] [%(name)s] | %(message)s" - ) + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": { + "format": ("%(asctime)s [%(levelname)s] [%(name)s] | %(message)s") }, }, - 'handlers': { - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'simple' + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "simple", }, }, - 'root': { - 'level': 'INFO', - 'handlers': ['console'], - 'propagate': True, + "root": { + "level": "INFO", + "handlers": ["console"], + "propagate": True, }, } @@ -95,64 +93,66 @@ class StagingConfig(Config): DEVELOPMENT = True DEBUG = True - SQLALCHEMY_DATABASE_URI = f"mysql+pymysql:" \ - + f"//{os.environ.get('MYSQL_USER')}:" \ - + f"{os.environ.get('MYSQL_PASSWORD')}" \ - + f"@{os.environ.get('MYSQL_HOST')}" \ - f"/{os.environ.get('MYSQL_DATABASE')}" + SQLALCHEMY_DATABASE_URI = ( + f"mysql+pymysql:" + + f"//{os.environ.get('MYSQL_USER')}:" + + f"{os.environ.get('MYSQL_PASSWORD')}" + + f"@{os.environ.get('MYSQL_HOST')}" + f"/{os.environ.get('MYSQL_DATABASE')}" + ) SQLALCHEMY_POOL_SIZE = 1 - CELERY_IMPORTS = ("app") + CELERY_IMPORTS = "app" CELERY_BROKER_URL = "redis://localhost:6379" CELERY_RESULT_BACKEND = "redis://localhost:6379" REDIS_BROKER_URL = "redis://localhost:6379" WEBSOCKETS_URL = "localhost:5000" - UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER') - STATIC_FOLDER = os.environ.get('STATIC_FOLDER') + UPLOAD_FOLDER = os.environ.get("UPLOAD_FOLDER") + STATIC_FOLDER = os.environ.get("STATIC_FOLDER") class DevelopmentConfig(Config): DEVELOPMENT = True DEBUG = True - SQLALCHEMY_DATABASE_URI = f"mysql+pymysql:" \ - + f"//{os.environ.get('MYSQL_USER')}:" \ - + f"{os.environ.get('MYSQL_PASSWORD')}" \ - + f"@{os.environ.get('MYSQL_HOST')}" \ - f"/{os.environ.get('MYSQL_DATABASE')}" + SQLALCHEMY_DATABASE_URI = ( + f"mysql+pymysql:" + + f"//{os.environ.get('MYSQL_USER')}:" + + f"{os.environ.get('MYSQL_PASSWORD')}" + + f"@{os.environ.get('MYSQL_HOST')}" + f"/{os.environ.get('MYSQL_DATABASE')}" + ) SQLALCHEMY_POOL_SIZE = 1 - CELERY_IMPORTS = ("app") + CELERY_IMPORTS = "app" CELERY_BROKER_URL = "redis://localhost:6379" CELERY_RESULT_BACKEND = "redis://localhost:6379" REDIS_BROKER_URL = "redis://localhost:6379" WEBSOCKETS_URL = "localhost:5000" - UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER') - STATIC_FOLDER = os.environ.get('STATIC_FOLDER') + UPLOAD_FOLDER = os.environ.get("UPLOAD_FOLDER") + STATIC_FOLDER = os.environ.get("STATIC_FOLDER") class TestingConfig(Config): TESTING = True - ''' + """ If DEBUG is set to True it causes an AssertionError. See: - https://github.com/ga4gh/ga4gh-server/issues/791 - https://github.com/alexmclarty/mirror/issues/6 - ''' + """ DEBUG = False SQLALCHEMY_DATABASE_URI = "sqlite://" SQLALCHEMY_POOL_SIZE = None SQLALCHEMY_POOL_TIMEOUT = None - CELERY_IMPORTS = ("app") + CELERY_IMPORTS = "app" CELERY_BROKER_URL = "redis://" CELERY_RESULT_BACKEND = "redis://" REDIS_BROKER_URL = "redis://" WEBSOCKETS_URL = "localhost:5000" - UPLOAD_FOLDER = '/tmp' - STATIC_FOLDER = '/tmp' - - + UPLOAD_FOLDER = "/tmp" + STATIC_FOLDER = "/tmp" diff --git a/src/restapi/constants.py b/src/restapi/constants.py index c77c1ce..a5a55f2 100644 --- a/src/restapi/constants.py +++ b/src/restapi/constants.py @@ -21,7 +21,7 @@ RESPONSE_TASK_NONE = "none" MONGODB_DATABASE = "MONGODB" -MYSQL_DATABASE = "MYSQL" +MYSQL_DATABASE = "MYSQL" AVAILABLE_DATABASES = [MONGODB_DATABASE, MYSQL_DATABASE] -SESSION_ROOM_ID_KEY = "session_room" \ No newline at end of file +SESSION_ROOM_ID_KEY = "session_room" diff --git a/src/restapi/exception_handler.py b/src/restapi/exception_handler.py index baaa517..ca488cf 100644 --- a/src/restapi/exception_handler.py +++ b/src/restapi/exception_handler.py @@ -32,37 +32,42 @@ def init_exception_handler(app): app.register_error_handler(InvalidException, handle_invalid_exception) app.register_error_handler(Exception, handle_exception) + def handle_duplicate_exception(error): LOGGER.info(error) LOGGER.info("Response : 409") - response = jsonify({'message': "Value already exists"}) + response = jsonify({"message": "Value already exists"}) response.status_code = 409 return response + def handle_validation_exception(error): LOGGER.info(error) LOGGER.info("Response : 400") - response = jsonify({'message': str(error)}) + response = jsonify({"message": str(error)}) response.status_code = 400 return response + def handle_not_found_exception(error): LOGGER.info(error) LOGGER.info("Response : 404") - response = jsonify({'message': "Not found"}) + response = jsonify({"message": "Not found"}) response.status_code = 404 return response + def handle_exception(error): LOGGER.error(traceback.format_exc()) LOGGER.info("Response : 500") - response = jsonify({'message': "Internal server error"}) + response = jsonify({"message": "Internal server error"}) response.status_code = 500 return response + def handle_invalid_exception(error): LOGGER.info(error) LOGGER.info("Response : 401") - response = jsonify({'message': str(error)}) + response = jsonify({"message": str(error)}) response.status_code = 401 - return response \ No newline at end of file + return response diff --git a/src/restapi/exceptions.py b/src/restapi/exceptions.py index 9392e4f..ad43b2b 100644 --- a/src/restapi/exceptions.py +++ b/src/restapi/exceptions.py @@ -16,18 +16,22 @@ along with this program. If not, see . """ + class ValidationException(Exception): def __init__(self, message): super().__init__(message) + class DuplicateException(Exception): def __init__(self): super().__init__() + class NotFoundException(Exception): def __init__(self): super().__init__() + class InvalidException(Exception): def __init__(self, message): - super().__init__(message) \ No newline at end of file + super().__init__(message) diff --git a/src/restapi/models/ModelBean.py b/src/restapi/models/ModelBean.py index 76aa20f..8cd2f74 100644 --- a/src/restapi/models/ModelBean.py +++ b/src/restapi/models/ModelBean.py @@ -1,7 +1,4 @@ - - class Model: - def __init__(self, uuid=None, url=None): self.__uuid = uuid self.__url = url @@ -21,4 +18,3 @@ def url(self): @url.setter def url(self, url): self.__url = url - diff --git a/src/restapi/models/ModelMongo.py b/src/restapi/models/ModelMongo.py index 0ce9aef..966f9ef 100644 --- a/src/restapi/models/ModelMongo.py +++ b/src/restapi/models/ModelMongo.py @@ -1,9 +1,9 @@ from src.restapi.database import db -db.connect('models') +db.connect("models") + class Model(db.Document): uuid = db.StringField(unique=True, required=True) - url = db.StringField(required=True) - + url = db.StringField(required=True) diff --git a/src/restapi/models/ModelMysql.py b/src/restapi/models/ModelMysql.py index da96467..054f64d 100644 --- a/src/restapi/models/ModelMysql.py +++ b/src/restapi/models/ModelMysql.py @@ -1,6 +1,7 @@ from src.restapi.database import db + class Models(db.Model): - __tablename__ = 'MODELS' + __tablename__ = "MODELS" uuid = db.Column(db.String(100), unique=True, nullable=False, primary_key=True) url = db.Column(db.Text, unique=True, nullable=False) diff --git a/src/restapi/repository/ModelRepositoryMongo.py b/src/restapi/repository/ModelRepositoryMongo.py index 19e57a3..ebcaea6 100644 --- a/src/restapi/repository/ModelRepositoryMongo.py +++ b/src/restapi/repository/ModelRepositoryMongo.py @@ -10,7 +10,6 @@ class ModelRepository: - def insert(self, uuid, url): try: LOGGER.info(f"Saving Model(uuid={uuid}, url={url})") @@ -18,7 +17,6 @@ def insert(self, uuid, url): except (NotUniqueError, DuplicateKeyError) as err: raise DuplicateException() - def query_by_uuid(self, uuid): LOGGER.info(f"Quering Model(uuid={uuid})") value = Model.objects(uuid=str(uuid)).first() @@ -28,4 +26,4 @@ def query_by_uuid(self, uuid): return value def query(self): - return Model.objects.all() \ No newline at end of file + return Model.objects.all() diff --git a/src/restapi/repository/ModelRepositoryMysql.py b/src/restapi/repository/ModelRepositoryMysql.py index 683fea6..a4adf6a 100644 --- a/src/restapi/repository/ModelRepositoryMysql.py +++ b/src/restapi/repository/ModelRepositoryMysql.py @@ -8,39 +8,44 @@ LOGGER = logging.getLogger(__name__) -INSERT_MODEL = 'INSERT INTO MODELS (UUID, URL) VALUES (% s, % s)' -QUERY_MODEL = 'SELECT * FROM MODELS WHERE uuid = % s' +INSERT_MODEL = "INSERT INTO MODELS (UUID, URL) VALUES (% s, % s)" +QUERY_MODEL = "SELECT * FROM MODELS WHERE uuid = % s" -class ModelRepository: +class ModelRepository: def log_insert(self, query, args): query_log = query for par in list(args): - query_log = query_log.replace('% s', par, 1) + query_log = query_log.replace("% s", par, 1) LOGGER.info(query_log) with connection.cursor() as cursor: print("Done cursor") - cursor.execute(query, - args) + cursor.execute(query, args) print("Done execute") # Disabled to avoid: pymysql.err.Error: Already closed - #connection.commit() + # connection.commit() def log_query_one(self, query, args): query_log = query for par in list(args): - query_log = query_log.replace('% s', par, 1) + query_log = query_log.replace("% s", par, 1) LOGGER.info(query_log) with connection.cursor() as cursor: cursor.execute(query, args) result = cursor.fetchone() # Disabled to avoid: pymysql.err.Error: Already closed - #cursor.close() + # cursor.close() return result return None def insert(self, uuid, url): - self.log_insert(INSERT_MODEL, (uuid, url,)) + self.log_insert( + INSERT_MODEL, + ( + uuid, + url, + ), + ) def query_by_uuid(self, uuid): uuid = str(uuid) @@ -51,5 +56,5 @@ def query_by_uuid(self, uuid): model.url = result["URL"] return model - #def query(self): - # return Model.objects.all() \ No newline at end of file + # def query(self): + # return Model.objects.all() diff --git a/src/restapi/repository/ModelRepositoryMysqlAlchemy.py b/src/restapi/repository/ModelRepositoryMysqlAlchemy.py index 040dae4..34c02a8 100644 --- a/src/restapi/repository/ModelRepositoryMysqlAlchemy.py +++ b/src/restapi/repository/ModelRepositoryMysqlAlchemy.py @@ -26,7 +26,6 @@ class ModelRepository: - def insert(self, uuid, url): LOGGER.info(f"Saving Models(uuid={uuid}, url={url})") models = Models(uuid=uuid, url=url) diff --git a/src/restapi/service/ModelService.py b/src/restapi/service/ModelService.py index 5c5a6fa..fb1edbe 100644 --- a/src/restapi/service/ModelService.py +++ b/src/restapi/service/ModelService.py @@ -16,9 +16,10 @@ along with this program. If not, see . """ -#from src.restapi.repository.ModelRepositoryMongo import ModelRepository +# from src.restapi.repository.ModelRepositoryMongo import ModelRepository from src.restapi.repository.ModelRepositoryMysqlAlchemy import ModelRepository + class ModelService: model_repository = ModelRepository() diff --git a/src/restapi/socket_util.py b/src/restapi/socket_util.py index e0b05ff..97f680e 100644 --- a/src/restapi/socket_util.py +++ b/src/restapi/socket_util.py @@ -3,8 +3,8 @@ LOGGER = get_task_logger(__name__) -socketio = SocketIO(message_queue='redis://') +socketio = SocketIO(message_queue="redis://") def send_message_client(room_name, message): - socketio.emit(room_name, {'data': message}, to=room_name, room=room_name) + socketio.emit(room_name, {"data": message}, to=room_name, room=room_name) diff --git a/src/restapi/tasks/tasks.py b/src/restapi/tasks/tasks.py index 5f96154..9ee887e 100644 --- a/src/restapi/tasks/tasks.py +++ b/src/restapi/tasks/tasks.py @@ -27,15 +27,30 @@ def compute_chokepoints_task(model_path, exclude_dead_reactions=True): return compute_chokepoints(model_path, exclude_dead_reactions) + @celery_app.task -def compute_sets_report_task(model_path, output_path, output_filename, model_uuid, config): - generate_sets_report(model_path, output_path + "/" + output_filename, model_uuid, config) +def compute_sets_report_task( + model_path, output_path, output_filename, model_uuid, config +): + generate_sets_report( + model_path, output_path + "/" + output_filename, model_uuid, config + ) return output_filename + @celery_app.task -def task_compute_critical_reactions(model_path, output_path, objective=None, fraction_of_optimum=None, model_uuid=None): - return compute_critical_reactions(model_path, output_path, objective, fraction_of_optimum, model_uuid) +def task_compute_critical_reactions( + model_path, output_path, objective=None, fraction_of_optimum=None, model_uuid=None +): + return compute_critical_reactions( + model_path, output_path, objective, fraction_of_optimum, model_uuid + ) + @celery_app.task -def task_compute_growth_dependent_reactions(model_path, output_path, objective=None, model_uuid=None): - return compute_growth_dependent_reactions(model_path, output_path, objective, model_uuid) +def task_compute_growth_dependent_reactions( + model_path, output_path, objective=None, model_uuid=None +): + return compute_growth_dependent_reactions( + model_path, output_path, objective, model_uuid + ) diff --git a/src/restapi/util/model_utils.py b/src/restapi/util/model_utils.py index 6644a22..95ead5a 100644 --- a/src/restapi/util/model_utils.py +++ b/src/restapi/util/model_utils.py @@ -26,7 +26,7 @@ def read_model(path): try: # check if file exists - open(path, 'r') + open(path, "r") # read submit if path[-4:] == ".xml": cobra_model = cobra.io.read_sbml_model(path) @@ -41,13 +41,14 @@ def read_model(path): return cobra_model -def compute_chokepoints(mdoel_path, exclude_dead_reations = True): + +def compute_chokepoints(mdoel_path, exclude_dead_reations=True): cpmodel = CobraMetabolicModel(mdoel_path) cpmodel.find_chokepoints(exclude_dead_reactions=exclude_dead_reations) return [(r.id, m.id) for r, m in cpmodel.chokepoints()] -def in_range_zero(up , low): +def in_range_zero(up, low): return up + CONST_EPSILON >= 0 and low - CONST_EPSILON <= 0 @@ -74,7 +75,10 @@ def alive_reactions(cobra_model, fva_solution): def reaction_reversible(reaction): - return reaction.upper_bound > CONST_EPSILON and abs(reaction.lower_bound) > CONST_EPSILON + return ( + reaction.upper_bound > CONST_EPSILON + and abs(reaction.lower_bound) > CONST_EPSILON + ) def reversible_alive_flux(cobra_model, cobra_summary): @@ -112,4 +116,4 @@ def fva_fluxes(cobra_model, fva_solution): i = i + 1 - return result \ No newline at end of file + return result diff --git a/src/restapi/util/report_sets_utils.py b/src/restapi/util/report_sets_utils.py index 83561e7..8ba9942 100644 --- a/src/restapi/util/report_sets_utils.py +++ b/src/restapi/util/report_sets_utils.py @@ -52,13 +52,13 @@ def read_config_model(model_path, config): return model -def generate_sets_report(model_path, output_path, model_uuid, config): +def generate_sets_report(model_path, output_path, model_uuid, config): def verbose_f(text, args1=None, args2=None): - ''' + """ args1 = room name args2 = None - ''' + """ LOGGER.info(text) send_message_client(args1, text) @@ -67,7 +67,7 @@ def verbose_f(text, args1=None, args2=None): if config.fraction_of_optimum is not None: fraction_of_optimum = config.fraction_of_optimum - MODEL = model_path + MODEL = model_path # compute essential reactions verbose_f("Computing essential reactions", model_uuid) @@ -109,10 +109,13 @@ def verbose_f(text, args1=None, args2=None): book = s.get_workbook() info = book.add_sheet("submit info") s.spreadsheet_write_reactions(state, "reactions", ordered=True) - s.spreadsheet_write_metabolites(state, "metabolites", ordered=True, print_reactions=True) - + s.spreadsheet_write_metabolites( + state, "metabolites", ordered=True, print_reactions=True + ) - chokepoints = set([r.reaction.id for r in cpmodel.find_chokepoints(exclude_dead_reactions=True)]) + chokepoints = set( + [r.reaction.id for r in cpmodel.find_chokepoints(exclude_dead_reactions=True)] + ) model = cpmodel.model() # FBA @@ -154,12 +157,17 @@ def verbose_f(text, args1=None, args2=None): MGR = alive_flux(model, sol_fba) if config.optimization == OptimizationEnum.FBA: verbose_f("Computing Flux Variability Analysis", model_uuid) - fba_fva = flux_variability_analysis(model, processes=PROCESSES, - fraction_of_optimum=fraction_of_optimum) + fba_fva = flux_variability_analysis( + model, processes=PROCESSES, fraction_of_optimum=fraction_of_optimum + ) elif config.optimization == OptimizationEnum.pFBA: verbose_f("Computing parsimonious Flux Variability Analysis", model_uuid) - fba_fva = flux_variability_analysis(model, pfba_factor = 1.0, processes=PROCESSES, - fraction_of_optimum=fraction_of_optimum) + fba_fva = flux_variability_analysis( + model, + pfba_factor=1.0, + processes=PROCESSES, + fraction_of_optimum=fraction_of_optimum, + ) else: pass @@ -168,27 +176,26 @@ def verbose_f(text, args1=None, args2=None): verbose_f("Computing suboptimal Flux Variability Analysis", model_uuid) cpmodel = read_config_model(MODEL, config) - cpmodel.fva(update_flux = True, threshold=0.0) + cpmodel.fva(update_flux=True, threshold=0.0) DR_0 = set([r.id for r in cpmodel.dead_reactions()]) cpmodel.find_chokepoints(exclude_dead_reactions=True) CP_0 = set([r.id for r, m in cpmodel.chokepoints()]) verbose_f("Computing optimal Flux Variability Analysis", model_uuid) cpmodel = read_config_model(MODEL, config) - cpmodel.fva(update_flux = True, threshold=1.0) + cpmodel.fva(update_flux=True, threshold=1.0) DR_1 = set([r.id for r in cpmodel.dead_reactions()]) cpmodel.find_chokepoints(exclude_dead_reactions=True) CP_1 = set([r.id for r, m in cpmodel.chokepoints()]) verbose_f("Computing suboptimal Flux Variability Analysis", model_uuid) cpmodel = read_config_model(MODEL, config) - cpmodel.fva(update_flux = True, threshold=0.9) + cpmodel.fva(update_flux=True, threshold=0.9) DR_09 = set([r.id for r in cpmodel.dead_reactions()]) cpmodel = read_config_model(MODEL, config) model = cpmodel.model() - info.write(0, 0, "MODEL ID") info.write(0, 1, model.id) info.write(1, 0, "REACTIONS") @@ -235,7 +242,9 @@ def verbose_f(text, args1=None, args2=None): knock_reaction.upper_bound = float(0.0) knock_reaction.lower_bound = float(0.0) - verbose_f("Computing results for reaction: " + knock_reaction.id, model_uuid) + verbose_f( + "Computing results for reaction: " + knock_reaction.id, model_uuid + ) aux_sol = 0 if config.optimization == OptimizationEnum.FBA: @@ -249,19 +258,27 @@ def verbose_f(text, args1=None, args2=None): if not config.skip_knockout_computation: if config.optimization == OptimizationEnum.FBA: - prima_fva_sol = flux_variability_analysis(model, processes=PROCESSES, - fraction_of_optimum=fraction_of_optimum) + prima_fva_sol = flux_variability_analysis( + model, + processes=PROCESSES, + fraction_of_optimum=fraction_of_optimum, + ) elif config.optimization == OptimizationEnum.pFBA: - prima_fva_sol = flux_variability_analysis(model, pfba_factor = 1.0, processes=PROCESSES, - fraction_of_optimum=fraction_of_optimum) + prima_fva_sol = flux_variability_analysis( + model, + pfba_factor=1.0, + processes=PROCESSES, + fraction_of_optimum=fraction_of_optimum, + ) else: pass NR_PLUS_RR_prima = alive_reactions(model, prima_fva_sol) - DR_prima = set([r.id for r in model.reactions]).difference(set(NR_PLUS_RR_prima)) + DR_prima = set([r.id for r in model.reactions]).difference( + set(NR_PLUS_RR_prima) + ) Y = DR_prima.difference(DR_1) - if r in chokepoints: is_choke = "TRUE" else: @@ -316,7 +333,10 @@ def verbose_f(text, args1=None, args2=None): i = counters[sheet] if not config.skip_knockout_computation: - cell_X = "{}: {}".format(len(set(MGR_prima).intersection(DR_1)), str(set(MGR_prima).intersection(DR_1))) + cell_X = "{}: {}".format( + len(set(MGR_prima).intersection(DR_1)), + str(set(MGR_prima).intersection(DR_1)), + ) cell_Y = "{}: {}".format(len(Y), str(Y)) else: cell_X = " " diff --git a/src/restapi/util/report_utils.py b/src/restapi/util/report_utils.py index de6056f..7c29db8 100644 --- a/src/restapi/util/report_utils.py +++ b/src/restapi/util/report_utils.py @@ -29,13 +29,15 @@ LOGGER = get_task_logger(__name__) -def compute_critical_reactions(model_path, output_path, objective=None, fraction_of_optimum=None, model_uuid=None): +def compute_critical_reactions( + model_path, output_path, objective=None, fraction_of_optimum=None, model_uuid=None +): def verbose_f(text, args1=None, args2=None): - ''' + """ args1 = ws room name args2 = None - ''' + """ LOGGER.info(text) send_message_client(args1, text) @@ -46,7 +48,7 @@ def verbose_f(text, args1=None, args2=None): config.args2 = None config.output_path_spreadsheet = output_path if output_path is not None: - config.output_path_html = output_path[:output_path.rfind('.')] + '.html' + config.output_path_html = output_path[: output_path.rfind(".")] + ".html" config.objective = objective config.fraction = fraction_of_optimum config.processes = 1 @@ -60,13 +62,15 @@ def verbose_f(text, args1=None, args2=None): _, filename2 = ntpath.split(config.output_path_spreadsheet) return (filename1, filename2) -def compute_growth_dependent_reactions(model_path, output_path, objective=None, model_uuid=None): +def compute_growth_dependent_reactions( + model_path, output_path, objective=None, model_uuid=None +): def verbose_f(text, args1=None, args2=None): - ''' + """ args1 = ws room name args2 = None - ''' + """ LOGGER.info(text) send_message_client(args1, text) @@ -77,7 +81,7 @@ def verbose_f(text, args1=None, args2=None): config.args2 = None config.output_path_spreadsheet = output_path if output_path is not None: - config.output_path_html = output_path[:output_path.rfind('.')] + '.html' + config.output_path_html = output_path[: output_path.rfind(".")] + ".html" config.objective = objective config.processes = 1 @@ -87,4 +91,3 @@ def verbose_f(text, args1=None, args2=None): _, filename1 = ntpath.split(config.output_path_html) _, filename2 = ntpath.split(config.output_path_spreadsheet) return (filename1, filename2) - diff --git a/src/restapi/validation.py b/src/restapi/validation.py index 276df91..43daa11 100644 --- a/src/restapi/validation.py +++ b/src/restapi/validation.py @@ -20,9 +20,9 @@ from .exceptions import * import re + def allowed_file(filename): - return '.' in filename and \ - filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS def sanitize_string(string): @@ -32,4 +32,4 @@ def sanitize_string(string): def empty_string(string): - return string is None or string == '' + return string is None or string == "" diff --git a/tests/conftest.py b/tests/conftest.py index cc03184..b047492 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,10 @@ - import os, sys import pytest import os from os.path import join, dirname from dotenv import load_dotenv -DOTENV_FILENAME = '.test.env' +DOTENV_FILENAME = ".test.env" sys.path.append("..") @@ -18,9 +17,10 @@ from src.restapi.app import app as _app from src.restapi.database import db as _db + @pytest.fixture def client(): - _app.config.from_object('src.restapi.config.TestingConfig') + _app.config.from_object("src.restapi.config.TestingConfig") with _app.test_client() as client: _db.init_app(_app) @@ -30,4 +30,3 @@ def client(): with _app.app_context(): _db.session.remove() _db.drop_all() - diff --git a/tests/test_ops.py b/tests/test_ops.py index 63983b3..7b20284 100644 --- a/tests/test_ops.py +++ b/tests/test_ops.py @@ -7,25 +7,22 @@ def __read_file(path): - with open(path, 'r') as file: - data = file.read().replace('\n', '') + with open(path, "r") as file: + data = file.read().replace("\n", "") return data def test_websocket_endpoints(client): - assert client.get('/websockets/get_endpoint').status_code == 200 - assert client.get('/websockets/example_event_join').status_code == 200 - assert client.get('/websockets/example_event_message').status_code == 200 + assert client.get("/websockets/get_endpoint").status_code == 200 + assert client.get("/websockets/example_event_join").status_code == 200 + assert client.get("/websockets/example_event_message").status_code == 200 @pytest.mark.parametrize("filename", [DATA_MODEL_FILE]) def test_submit(client, filename): - data = {'file': (io.BytesIO(__read_file(filename).encode()), "model.xml")} + data = {"file": (io.BytesIO(__read_file(filename).encode()), "model.xml")} result = client.post( - '/submit', - data=data, - follow_redirects=True, - content_type='multipart/form-data' + "/submit", data=data, follow_redirects=True, content_type="multipart/form-data" ) assert result.status_code == 200 @@ -35,32 +32,32 @@ def test_submit(client, filename): @pytest.mark.parametrize("filename", [DATA_MODEL_FILE]) @pytest.mark.skip(reason="Error running celery on github action") def test_critical_reactions(client, filename): - data = {'file': (io.BytesIO(__read_file(filename).encode()), "model.xml")} + data = {"file": (io.BytesIO(__read_file(filename).encode()), "model.xml")} result = client.post( - '/submit', - data=data, - follow_redirects=True, - content_type='multipart/form-data' + "/submit", data=data, follow_redirects=True, content_type="multipart/form-data" ) assert result.status_code == 200 data = json.loads(result.get_data(as_text=True)) - assert client.get(f"/results/{data['model_uuid']}/critical_reactions").status_code == 200 + assert ( + client.get(f"/results/{data['model_uuid']}/critical_reactions").status_code + == 200 + ) + # TODO: fix celery execution on github action @pytest.mark.parametrize("filename", [DATA_MODEL_FILE]) @pytest.mark.skip(reason="Error running celery on github action") def test_growth_dependent(client, filename): - data = {'file': (io.BytesIO(__read_file(filename).encode()), "model.xml")} + data = {"file": (io.BytesIO(__read_file(filename).encode()), "model.xml")} result = client.post( - '/submit', - data=data, - follow_redirects=True, - content_type='multipart/form-data' + "/submit", data=data, follow_redirects=True, content_type="multipart/form-data" ) assert result.status_code == 200 data = json.loads(result.get_data(as_text=True)) - assert client.get(f"/results/{data['model_uuid']}/critical_reactions").status_code == 200 + assert ( + client.get(f"/results/{data['model_uuid']}/critical_reactions").status_code + == 200 + ) print(client.get(f"/results/{data['model_uuid']}/critical_reactions").get_data()) - diff --git a/tests/test_tasks.py b/tests/test_tasks.py index 2c74b98..e779ad6 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -4,10 +4,14 @@ from flask import g, session sys.path.append("..") -from src.restapi.tasks.tasks import task_compute_critical_reactions, task_compute_growth_dependent_reactions +from src.restapi.tasks.tasks import ( + task_compute_critical_reactions, + task_compute_growth_dependent_reactions, +) DATA_MODEL_FILE = "tests/data/MODEL1507180056_url.xml" + @pytest.mark.parametrize("filename", [DATA_MODEL_FILE]) def test_compute_critical_reactions(client, filename): task_compute_critical_reactions( @@ -15,13 +19,12 @@ def test_compute_critical_reactions(client, filename): "output_path.xls", objective=None, fraction_of_optimum=None, - model_uuid=None) + model_uuid=None, + ) @pytest.mark.parametrize("filename", [DATA_MODEL_FILE]) def test_compute_growth_dependent_reactions(client, filename): task_compute_growth_dependent_reactions( - filename, - "output_path.xls", - objective=None, - model_uuid=None) \ No newline at end of file + filename, "output_path.xls", objective=None, model_uuid=None + )