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') |