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