From ea5dcc622a2add1d19ba4b3b5b0e37bd724b9a8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 29 Sep 2022 16:51:05 +0200
Subject: [PATCH] For production run services with gunicorn

---
 services/Dockerfile               |   2 +-
 services/src/anis_services/app.py | 331 +++++++++++++++---------------
 2 files changed, 169 insertions(+), 164 deletions(-)

diff --git a/services/Dockerfile b/services/Dockerfile
index 44ecce1e..64c6b6d2 100755
--- a/services/Dockerfile
+++ b/services/Dockerfile
@@ -6,4 +6,4 @@ COPY requirements.txt ./
 COPY src src
 RUN pip install --no-cache-dir -r requirements.txt
 
-CMD ["python3.8", "src/anis_services/app.py"]
+CMD ["gunicorn", "-w", "8", "--timeout", "600", "-b", "0.0.0.0:80", "anis_services.app:create_app()"]
diff --git a/services/src/anis_services/app.py b/services/src/anis_services/app.py
index 742b8598..bb514aba 100644
--- a/services/src/anis_services/app.py
+++ b/services/src/anis_services/app.py
@@ -12,172 +12,177 @@ import plot
 import spectra
 import utils
 
-app = Flask(__name__)
-CORS(app)
-
-@app.route('/')
-def index():
-    return {
-        "message": "it works!"
-    }
-
-@app.route('/get-fits-image-limits/<dname>')
-def get_fits_image_limits(dname):
-    filename = request.args.get('filename', default=None)
-    if filename is None:
-        return {"message": "Parameter filename is mandatory"}, 400
-
-    try:
-        file_path = utils.get_file_path(dname, filename)
-    except utils.DatasetNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.FileForbidden as e:
-        return {"message": str(e)}, 403
-    except utils.FileNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.AnisServerError as e:
-        return {"message": str(e)}, 500
-    
-    return cut.getImageLimits(file_path)
-
-@app.route('/fits-cut/<dname>')
-def fits_cut(dname):
-    filename = request.args.get('filename', default=None)
-    if filename is None:
-        return {"message": "Parameter filename is mandatory"}, 400
-
-    ra = float(request.args.get('ra', ''))
-    dec = float(request.args.get('dec', ''))
-    radius = float(request.args.get('radius', ''))
-
-    if ra is None:
-        return {"message": "Parameter ra is mandatory"}, 400
-    if dec is None:
-        return {"message": "Parameter dec is mandatory"}, 400
-    if radius is None:
-        return {"message": "Parameter radius is mandatory"}, 400
-
-    try:
-        file_path = utils.get_file_path(dname, filename)
-    except utils.DatasetNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.FileForbidden as e:
-        return {"message": str(e)}, 403
-    except utils.FileNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.AnisServerError as e:
-        return {"message": str(e)}, 500
-
-    fits_file = cut.fits_cut(file_path, ra, dec, radius)
-
-    output = io.BytesIO()
-    fits_file.writeto(output, overwrite=True)
-    return Response(output.getvalue(), mimetype='image/fits')
-
-@app.route('/fits-cut-to-png/<dname>')
-def fits_cut_to_png(dname):
-    filename = request.args.get('filename', default=None)
-    if filename is None:
-        return {"message": "Parameter filename is mandatory"}, 400
-
-    ra = request.args.get('ra', default=None, type=float)
-    dec = request.args.get('dec', default=None, type=float)
-    radius = request.args.get('radius', default=None, type=float)
-    stretch = request.args.get('stretch', default=None)
-    pmin = request.args.get('pmin', default=None, type=float)
-    pmax = request.args.get('pmax', default=None, type=float)
-    axes = request.args.get('axes', default=None)
-    theme = request.args.get('theme', default=None)
-
-    if ra is None:
-        return {"message": "Parameter ra is mandatory"}, 400
-    if dec is None:
-        return {"message": "Parameter dec is mandatory"}, 400
-    if radius is None:
-        return {"message": "Parameter radius is mandatory"}, 400
-    if stretch is None:
-        return {"message": "Parameter stretch is mandatory"}, 400
-    if pmin is None:
-        return {"message": "Parameter pmin is mandatory"}, 400
-    if pmax is None:
-        return {"message": "Parameter pmax is mandatory"}, 400
-    if axes is None:
-        return {"message": "Parameter axes is mandatory"}, 400
-
-    try:
-        file_path = utils.get_file_path(dname, filename)
-    except utils.DatasetNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.FileForbidden as e:
-        return {"message": str(e)}, 403
-    except utils.FileNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.AnisServerError as e:
-        return {"message": str(e)}, 500
-
-    fits_file = cut.fits_cut(file_path, ra, dec, radius)
-
-    output = io.BytesIO()
-    fig = plot.create_figure(fits_file, stretch, pmin, pmax, axes, theme)
-    if axes == 'true':
-        fig.savefig(output, format="png")
-    else:
-        fig.savefig(output, format="png", bbox_inches='tight', pad_inches = 0)
-
-    return Response(output.getvalue(), mimetype='image/png')
-
-@app.route('/fits-to-png/<dname>')
-def fits_to_png(dname):
-    filename = request.args.get('filename', default=None)
-    if filename is None:
-        return {"message": "Parameter filename is mandatory"}, 400
-
-    stretch = request.args.get('stretch', default=None)
-    pmin = request.args.get('pmin', default=None, type=float)
-    pmax = request.args.get('pmax', default=None, type=float)
-    axes = request.args.get('axes', default=None)
-    theme = request.args.get('theme', default=None)
-
-    try:
-        file_path = utils.get_file_path(dname, filename)
-    except utils.DatasetNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.FileForbidden as e:
-        return {"message": str(e)}, 403
-    except utils.FileNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.AnisServerError as e:
-        return {"message": str(e)}, 500
-
-    fig = plot.create_figure(file_path, stretch, pmin, pmax, axes, theme)
-    output = io.BytesIO()
-    fig.save(output, format="png")
-
-    return Response(output.getvalue(), mimetype='image/png')
-
-@app.route('/spectra-to-csv/<dname>')
-def spectra_to_csv(dname):
-    filename = request.args.get('filename', default=None)
-    if filename is None:
-        return {"message": "Parameter filename is mandatory"}, 400
-
-    try:
-        file_path = utils.get_file_path(dname, filename)
-        csv = spectra.spectra_to_csv(file_path, filename)
-    except utils.DatasetNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.FileForbidden as e:
-        return {"message": str(e)}, 403
-    except utils.FileNotFound as e:
-        return {"message": str(e)}, 404
-    except utils.AnisServerError as e:
-        return {"message": str(e)}, 500
-
-    return Response(csv, mimetype='text/csv')
+def create_app():
+    utils.check_config()
+
+    app = Flask(__name__)
+    CORS(app)
+
+    @app.route('/')
+    def index():
+        return {
+            "message": "it works!"
+        }
+
+    @app.route('/get-fits-image-limits/<dname>')
+    def get_fits_image_limits(dname):
+        filename = request.args.get('filename', default=None)
+        if filename is None:
+            return {"message": "Parameter filename is mandatory"}, 400
+
+        try:
+            file_path = utils.get_file_path(dname, filename)
+        except utils.DatasetNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.FileForbidden as e:
+            return {"message": str(e)}, 403
+        except utils.FileNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.AnisServerError as e:
+            return {"message": str(e)}, 500
+        
+        return cut.getImageLimits(file_path)
+
+    @app.route('/fits-cut/<dname>')
+    def fits_cut(dname):
+        filename = request.args.get('filename', default=None)
+        if filename is None:
+            return {"message": "Parameter filename is mandatory"}, 400
+
+        ra = float(request.args.get('ra', ''))
+        dec = float(request.args.get('dec', ''))
+        radius = float(request.args.get('radius', ''))
+
+        if ra is None:
+            return {"message": "Parameter ra is mandatory"}, 400
+        if dec is None:
+            return {"message": "Parameter dec is mandatory"}, 400
+        if radius is None:
+            return {"message": "Parameter radius is mandatory"}, 400
+
+        try:
+            file_path = utils.get_file_path(dname, filename)
+        except utils.DatasetNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.FileForbidden as e:
+            return {"message": str(e)}, 403
+        except utils.FileNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.AnisServerError as e:
+            return {"message": str(e)}, 500
+
+        fits_file = cut.fits_cut(file_path, ra, dec, radius)
+
+        output = io.BytesIO()
+        fits_file.writeto(output, overwrite=True)
+        return Response(output.getvalue(), mimetype='image/fits')
+
+    @app.route('/fits-cut-to-png/<dname>')
+    def fits_cut_to_png(dname):
+        filename = request.args.get('filename', default=None)
+        if filename is None:
+            return {"message": "Parameter filename is mandatory"}, 400
+
+        ra = request.args.get('ra', default=None, type=float)
+        dec = request.args.get('dec', default=None, type=float)
+        radius = request.args.get('radius', default=None, type=float)
+        stretch = request.args.get('stretch', default=None)
+        pmin = request.args.get('pmin', default=None, type=float)
+        pmax = request.args.get('pmax', default=None, type=float)
+        axes = request.args.get('axes', default=None)
+        theme = request.args.get('theme', default=None)
+
+        if ra is None:
+            return {"message": "Parameter ra is mandatory"}, 400
+        if dec is None:
+            return {"message": "Parameter dec is mandatory"}, 400
+        if radius is None:
+            return {"message": "Parameter radius is mandatory"}, 400
+        if stretch is None:
+            return {"message": "Parameter stretch is mandatory"}, 400
+        if pmin is None:
+            return {"message": "Parameter pmin is mandatory"}, 400
+        if pmax is None:
+            return {"message": "Parameter pmax is mandatory"}, 400
+        if axes is None:
+            return {"message": "Parameter axes is mandatory"}, 400
+
+        try:
+            file_path = utils.get_file_path(dname, filename)
+        except utils.DatasetNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.FileForbidden as e:
+            return {"message": str(e)}, 403
+        except utils.FileNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.AnisServerError as e:
+            return {"message": str(e)}, 500
+
+        fits_file = cut.fits_cut(file_path, ra, dec, radius)
+
+        output = io.BytesIO()
+        fig = plot.create_figure(fits_file, stretch, pmin, pmax, axes, theme)
+        if axes == 'true':
+            fig.savefig(output, format="png")
+        else:
+            fig.savefig(output, format="png", bbox_inches='tight', pad_inches = 0)
+
+        return Response(output.getvalue(), mimetype='image/png')
+
+    @app.route('/fits-to-png/<dname>')
+    def fits_to_png(dname):
+        filename = request.args.get('filename', default=None)
+        if filename is None:
+            return {"message": "Parameter filename is mandatory"}, 400
+
+        stretch = request.args.get('stretch', default=None)
+        pmin = request.args.get('pmin', default=None, type=float)
+        pmax = request.args.get('pmax', default=None, type=float)
+        axes = request.args.get('axes', default=None)
+        theme = request.args.get('theme', default=None)
+
+        try:
+            file_path = utils.get_file_path(dname, filename)
+        except utils.DatasetNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.FileForbidden as e:
+            return {"message": str(e)}, 403
+        except utils.FileNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.AnisServerError as e:
+            return {"message": str(e)}, 500
+
+        fig = plot.create_figure(file_path, stretch, pmin, pmax, axes, theme)
+        output = io.BytesIO()
+        fig.save(output, format="png")
+
+        return Response(output.getvalue(), mimetype='image/png')
+
+    @app.route('/spectra-to-csv/<dname>')
+    def spectra_to_csv(dname):
+        filename = request.args.get('filename', default=None)
+        if filename is None:
+            return {"message": "Parameter filename is mandatory"}, 400
+
+        try:
+            file_path = utils.get_file_path(dname, filename)
+            csv = spectra.spectra_to_csv(file_path, filename)
+        except utils.DatasetNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.FileForbidden as e:
+            return {"message": str(e)}, 403
+        except utils.FileNotFound as e:
+            return {"message": str(e)}, 404
+        except utils.AnisServerError as e:
+            return {"message": str(e)}, 500
+
+        return Response(csv, mimetype='text/csv')
+
+    return app
 
 if __name__ == "__main__":
     try:
-        utils.check_config()
+        app = create_app()
         app.run(host="0.0.0.0")
     except utils.ConfigKeyNotFound as e:
         app.logger.error("Config error")
-- 
GitLab