Mercurial > repos > itaxotools > mold
comparison api.py @ 0:4e8e2f836d0f draft default tip
planemo upload commit 232ce39054ce38be27c436a4cabec2800e14f988-dirty
| author | itaxotools |
|---|---|
| date | Sun, 29 Jan 2023 16:25:48 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4e8e2f836d0f |
|---|---|
| 1 #!/usr/bin/env python | |
| 2 # -*- coding: utf-8 -*- | |
| 3 | |
| 4 from flask import Flask, make_response, request, current_app, send_file, url_for | |
| 5 from flask_restful import Resource, Api, reqparse, abort | |
| 6 from flask.json import jsonify | |
| 7 from flasgger import Swagger | |
| 8 from flask_cors import CORS, cross_origin | |
| 9 from flask_restful.utils import cors | |
| 10 import logging | |
| 11 import base64 | |
| 12 import uuid | |
| 13 import tempfile | |
| 14 import os | |
| 15 import html2text | |
| 16 | |
| 17 # for emails | |
| 18 import smtplib | |
| 19 from email.message import EmailMessage | |
| 20 from email.mime.multipart import MIMEMultipart | |
| 21 from email.mime.text import MIMEText | |
| 22 from email.mime.image import MIMEImage | |
| 23 from celery import Celery | |
| 24 from celery.schedules import crontab | |
| 25 | |
| 26 import json | |
| 27 | |
| 28 #from MolD_sDNC import SortedDisplayDict | |
| 29 #from MolD_sDNC import Step1 | |
| 30 #from MolD_sDNC import C_VP_PP | |
| 31 #from MolD_sDNC import random_position | |
| 32 #from MolD_sDNC import step_reduction_complist | |
| 33 #from MolD_sDNC import ConditionD | |
| 34 #from MolD_sDNC import RemoveRedundantPositions | |
| 35 #from MolD_sDNC import Diagnostic_combinations | |
| 36 #from MolD_sDNC import IndependentKey | |
| 37 #from MolD_sDNC import random_sequence2 | |
| 38 #from MolD_sDNC import GenerateBarcode2 | |
| 39 #from MolD_sDNC import Screwed_dataset31 | |
| 40 from MolD_sDNCFASTA import mainprocessing | |
| 41 | |
| 42 | |
| 43 logging.basicConfig(format='%(levelname)s: %(asctime)s - %(message)s', | |
| 44 level=logging.DEBUG, datefmt='%d.%m.%Y %I:%M:%S %p') | |
| 45 | |
| 46 app = Flask(__name__) | |
| 47 app.config.from_envvar('APPSETTINGS') | |
| 48 API_VERSION = app.config.get('API_VERSION', 1) | |
| 49 | |
| 50 cors = CORS(app, resources={r"/*": {"origins": "*"}}, methods=['GET', 'POST', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']) | |
| 51 api = Api(app, prefix=f"/api/v{API_VERSION}") | |
| 52 | |
| 53 gunicorn_logger = logging.getLogger('gunicorn.error') | |
| 54 app.logger.handlers = gunicorn_logger.handlers | |
| 55 app.logger.setLevel(gunicorn_logger.level) | |
| 56 | |
| 57 REDIS_HOST = app.config.get('REDIS_HOST', 'localhost') | |
| 58 REDIS_PORT = app.config.get('REDIS_PORT', 6379) | |
| 59 REDIS_DB = app.config.get('REDIS_DB', 0) | |
| 60 | |
| 61 MAILUSER = app.config.get('MAILUSER') | |
| 62 MAILPASS = app.config.get('MAILPASS') | |
| 63 | |
| 64 app.config['SWAGGER'] = { | |
| 65 'uiversion': 3 | |
| 66 } | |
| 67 swtemplate = { | |
| 68 "info": { | |
| 69 "title": "MoID API", | |
| 70 "description": "MoID API is the online version of a tree independent algorithm to retrieve diagnostic nucleotide characters from monolocus datasets. BioRxiv. DOI: 10.1101/838151", | |
| 71 "version": f"{API_VERSION}", | |
| 72 }, | |
| 73 "schemes": [ | |
| 74 "https" | |
| 75 ] | |
| 76 } | |
| 77 | |
| 78 swagger_config = { | |
| 79 "headers": [ | |
| 80 ], | |
| 81 "specs": [ | |
| 82 { | |
| 83 "endpoint": 'apidescr', | |
| 84 "route": '/apidescr.json', | |
| 85 "rule_filter": lambda rule: True, | |
| 86 "model_filter": lambda tag: True, | |
| 87 } | |
| 88 ], | |
| 89 "static_url_path": "/flasgger_static", | |
| 90 "swagger_ui": True, | |
| 91 "specs_route": "/docs/", | |
| 92 'uiversion': 3 | |
| 93 } | |
| 94 | |
| 95 swagger = Swagger(app, config=swagger_config, template=swtemplate) | |
| 96 | |
| 97 SEND_EMAILS = True | |
| 98 | |
| 99 app.config['CELERY_BROKER_URL'] = 'redis://{}:{}/{}'.format(REDIS_HOST, REDIS_PORT, REDIS_DB) | |
| 100 app.config['CELERY_RESULT_BACKEND'] = 'redis://{}:{}/{}'.format(REDIS_HOST, REDIS_PORT, REDIS_DB) | |
| 101 | |
| 102 def make_celery(app): | |
| 103 celery = Celery(app.import_name, broker=app.config['CELERY_BROKER_URL']) | |
| 104 celery.conf.update(app.config) | |
| 105 TaskBase = celery.Task | |
| 106 class ContextTask(TaskBase): | |
| 107 abstract = True | |
| 108 def __call__(self, *args, **kwargs): | |
| 109 with app.app_context(): | |
| 110 return TaskBase.__call__(self, *args, **kwargs) | |
| 111 celery.Task = ContextTask | |
| 112 return celery | |
| 113 | |
| 114 celery = make_celery(app) | |
| 115 | |
| 116 #@celery.on_after_configure.connect | |
| 117 #def setup_periodic_tasks(sender, **kwargs): | |
| 118 # sender.add_periodic_task( | |
| 119 # crontab(minute=0, hour='*/3'), | |
| 120 # check_pending_notifications.s(), | |
| 121 # ) | |
| 122 | |
| 123 | |
| 124 @celery.task | |
| 125 def send_email_notification(email, status, parameters, taxalist, orig_filename): | |
| 126 print("Sending email") | |
| 127 sender = MAILUSER | |
| 128 taxa = ", ".join(taxalist) | |
| 129 print(taxalist) | |
| 130 taxa1 = "-".join([t.replace(" ", "_") for t in taxalist]) | |
| 131 msg = MIMEMultipart('related') | |
| 132 msg['Subject'] = f'MolD results: {taxa}' | |
| 133 msg['From'] = sender | |
| 134 msg['To'] = email | |
| 135 | |
| 136 # TODO: add three txt files: | |
| 137 # | |
| 138 | |
| 139 email_body = """\ | |
| 140 <html> | |
| 141 <head></head> | |
| 142 <body> | |
| 143 | |
| 144 Results: | |
| 145 {} | |
| 146 | |
| 147 *************** | |
| 148 | |
| 149 <p>Citation: <b>Fedosov A.E.</b>, Achaz G., Puillandre N. 2019. Revisiting use of DNA characters in taxonomy with MolD – a tree independent algorithm to retrieve diagnostic nucleotide characters from monolocus datasets. BioRxiv, published online on 11.11.2019. DOI: 10.1101/838151 </p> | |
| 150 </body> | |
| 151 </html> | |
| 152 """.format(status) | |
| 153 | |
| 154 plain_text_results = html2text.html2text(status) | |
| 155 | |
| 156 message_text = MIMEText(email_body, 'html') | |
| 157 msg.attach(message_text) | |
| 158 | |
| 159 text_attachment = f""" | |
| 160 Results: | |
| 161 {plain_text_results} | |
| 162 | |
| 163 *************** | |
| 164 | |
| 165 Citation: Fedosov A.E., Achaz G., Puillandre N. 2019. Revisiting use of DNA characters in taxonomy with MolD – a tree independent algorithm to retrieve diagnostic nucleotide characters from monolocus datasets. BioRxiv, published online on 11.11.2019. DOI: 10.1101/838151 | |
| 166 """ | |
| 167 | |
| 168 mail_attachment = MIMEText(text_attachment) | |
| 169 fname = f"{orig_filename}.results.txt" | |
| 170 mail_attachment.add_header('Content-Disposition', 'attachment', filename=fname) | |
| 171 msg.attach(mail_attachment) | |
| 172 | |
| 173 print("mail ready to be sent") | |
| 174 s = smtplib.SMTP('smtp.gmail.com', 587) | |
| 175 s.ehlo() | |
| 176 s.starttls() | |
| 177 s.login(MAILUSER, MAILPASS) | |
| 178 s.sendmail(sender, email, msg.as_string()) | |
| 179 s.quit() | |
| 180 print("mail sent") | |
| 181 | |
| 182 | |
| 183 @celery.task | |
| 184 def process_data(email, gapsaschars, taxalist, taxonrank, cutoff, numnucl, numiter, maxlenraw, maxlenrefined, pdiff, nmax, thresh, tmpfname, orig_fname): | |
| 185 print("Processing data") | |
| 186 | |
| 187 results, qclades = mainprocessing(gapsaschars, taxalist, taxonrank, cutoff, numnucl, numiter, maxlenraw, maxlenrefined, pdiff, nmax, thresh, tmpfname, orig_fname) | |
| 188 | |
| 189 parameters = f""" | |
| 190 List of focus taxa: {taxalist} | |
| 191 Taxon rank: {taxonrank} | |
| 192 Cutoff: {cutoff} | |
| 193 NNNNN...: {numnucl} | |
| 194 Num iterations: {numiter} | |
| 195 Max length for the raw mDNCs: {maxlenraw} | |
| 196 Max length for the refined mDNCs: {maxlenrefined} | |
| 197 Pdiff: {pdiff} | |
| 198 NMaxSeq: {nmax} | |
| 199 Threshold of rDNC rating: {thresh} | |
| 200 """ | |
| 201 | |
| 202 send_email_notification.delay(email, results, parameters, qclades, orig_fname) | |
| 203 | |
| 204 @app.errorhandler(404) | |
| 205 def not_found(error): | |
| 206 return make_response(jsonify({'error': 'Not found'}), 404) | |
| 207 | |
| 208 | |
| 209 class DataAPI(Resource): | |
| 210 def __init__(self): | |
| 211 self.method_decorators = [] | |
| 212 | |
| 213 def options(self, *args, **kwargs): | |
| 214 return jsonify([]) | |
| 215 | |
| 216 | |
| 217 #@token_required | |
| 218 @cross_origin() | |
| 219 def post(self): | |
| 220 """ | |
| 221 POST data to analyse | |
| 222 --- | |
| 223 parameters: | |
| 224 - in: formData | |
| 225 name: file | |
| 226 type: file | |
| 227 required: true | |
| 228 description: Data file | |
| 229 - in: formData | |
| 230 name: tags | |
| 231 type: array | |
| 232 required: false | |
| 233 description: A list of image tags to link with the image | |
| 234 items: | |
| 235 type: string | |
| 236 description: Tag text | |
| 237 - in: formData | |
| 238 name: width | |
| 239 type: integer | |
| 240 required: false | |
| 241 description: Image width | |
| 242 - in: formData | |
| 243 name: height | |
| 244 type: integer | |
| 245 required: false | |
| 246 description: Image height | |
| 247 | |
| 248 responses: | |
| 249 200: | |
| 250 description: Data processing | |
| 251 400: | |
| 252 description: Bad request | |
| 253 """ | |
| 254 app.logger.debug(request.files) | |
| 255 app.logger.debug(request.values) | |
| 256 email = request.values.get('email', None) | |
| 257 gapsaschars = request.values.get('gapsaschars', "no") | |
| 258 taxalist = request.values.get('taxalist', "ALL") | |
| 259 taxalist = taxalist.replace(" ", '') | |
| 260 taxonrank = int(request.values.get('taxonrank', 2)) | |
| 261 cutoff = request.values.get('cutoff', ">0") | |
| 262 numnucl = int(request.values.get('numnucl', 5)) | |
| 263 numiter = int(request.values.get('numites', 10000)) | |
| 264 maxlenraw = int(request.values.get('maxlenraw', 12)) | |
| 265 maxlenrefined = int(request.values.get('maxlenrefined', 7)) | |
| 266 pdiff = int(request.values.get('pdiff', 1)) | |
| 267 #prseq = float(request.values.get('prseq', 0.1)) | |
| 268 nmax = int(request.values.get('nmax', 20)) | |
| 269 thresh = int(request.values.get('thresh', 85)) | |
| 270 | |
| 271 if not all([email, gapsaschars, taxalist, taxonrank, cutoff, numnucl, numiter, maxlenraw, maxlenrefined, pdiff, nmax, thresh]): | |
| 272 return make_response(jsonify({'error': 'Parameter missing'}), 400) | |
| 273 | |
| 274 if not request.files: | |
| 275 return make_response(jsonify({'error': 'No file provided'}), 400) | |
| 276 | |
| 277 | |
| 278 data_file = [request.files.get(f) for f in request.files][-1] | |
| 279 | |
| 280 thumb_height = request.values.get('height', None) | |
| 281 tmpdname = "/tmp" | |
| 282 tmpfname = str(uuid.uuid4()) | |
| 283 tmp_upl_file = os.path.join(tmpdname, tmpfname) | |
| 284 data_file.save(tmp_upl_file) | |
| 285 app.logger.debug(tmp_upl_file) | |
| 286 process_data.delay(email, gapsaschars, taxalist, taxonrank, cutoff, numnucl, numiter, maxlenraw, maxlenrefined, pdiff, nmax, thresh, tmp_upl_file, data_file.filename) | |
| 287 | |
| 288 return jsonify("OK"), 200 | |
| 289 | |
| 290 | |
| 291 api.add_resource(DataAPI, '/data', endpoint='processdata') | |
| 292 | |
| 293 if __name__ == '__main__': | |
| 294 app.debug = True | |
| 295 app.run(host='0.0.0.0') |
