https://forgemia.inra.fr/asterics/asterics
Raw File
Tip revision: f56b151c66529b4bada418ade342fd76d1199c70 authored by Nathalie Vialaneix on 07 November 2022, 10:27:35 UTC
Merge branch 'dev-pls_inherit' into 'dev'
Tip revision: f56b151
asterics.py
from email import utils
import os
import json
import re
import socket
from flask import Flask, abort, render_template, jsonify, request, send_from_directory
from flask_uuid import FlaskUUID
from flask_mail import Mail
from pyRserve.rexceptions import RConnectionRefused, REvalError

from backend.asterics.workspace import Workspace
from backend.asterics.rsessionshandler import RSessionsHandler
from backend.asterics.rsession import SessionInUse
from backend.asterics.utils import sendEmail, formatFileSize

from flask_cors import CORS

app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")
app.config.from_pyfile('application.cfg', silent=False)
FlaskUUID(app)
app.mail = Mail(app)
app.rsessions = RSessionsHandler()


if (app.env == "development") :
    cors = CORS(app)

@app.errorhandler(400)
def r_execution_error(e):
    return jsonify(error=str(e)), 400

@app.errorhandler(503)
def r_session_error(e):
    return jsonify(error=str(e)), 503


@app.route('/api/run_r_function', methods=['GET', 'POST'])
def run_r_function() :
    try:
        uuid = request.json.pop('uuid')
        workspace = Workspace.get_workspace(uuid)
        return workspace.run_r_function(**request.json)
    except (SessionInUse) as err:
        abort(503, description=err)
    except (IOError, REvalError, RConnectionRefused) as err:
        abort(400, description=err)

@app.route('/api/get_rsessions', methods=['GET', 'POST'])
def get_rsessions():
    if (app.env == "development"):
        try:        
            all_keys = app.rsessions.get_sessions_keys()
            all_sessions = []
            for key in all_keys:
                session = app.rsessions.get_session(key)
                all_sessions.append({
                    'uuid': key,
                    'content': session.get_content()
                })
            return jsonify(all_sessions)
        except (SessionInUse) as err:
            abort(503, description=err)
        except (IOError, REvalError, RConnectionRefused) as err:
            abort(400, description=err)
    else :
        abort(404, description="Sessions list not available!")

@app.route('/api/add_dataset', methods=['POST'])
def add_dataset():
    try:
        uuid = request.form['uuid']
        dataset = json.loads(request.files['dataset'].read().decode('utf-8'))
        iparameters = json.loads(request.files['parameters'].read().decode('utf-8'))
        cworkspace = Workspace.get_workspace(uuid)
        return cworkspace.add_dataset(request.files['file'], dataset, iparameters)
    except (SessionInUse) as err:
        abort(503, description=err)
    except (IOError, REvalError, RConnectionRefused) as err:
        abort(400, description=err)

@app.route('/api/load_demo_datasets', methods=['POST'])
def load_demo_datasets():
    try:
        uuid = request.json.pop('uuid')
        cworkspace = Workspace.get_workspace(uuid)
        data_dir= os.path.join (os.path.split(app.instance_path)[0],"data","demo")
        dataset={'id': None, 'name': 'mrna', 'nature': 'rna-count', 'status': 'dataset', 'parent': []}
        iparameters={'nature': 'microarray', 'header': True, 'sep': ',', 'quote': '"', 'dec': '.', 'transpose': False, 'encoding': 'unknown', 'na.strings': ['NA'], 'out_graph': True, 'row.names': 1, 'logt': 'yes', 'normalized': 'yes'}
        cworkspace.add_dataset(os.path.join(data_dir,"mrna.csv"), dataset, iparameters)
        dataset={'id': None, 'name': 'protein', 'nature': 'generic', 'status': 'dataset', 'parent': []}
        iparameters={'nature': 'generic', 'header': True, 'sep': ' ', 'quote': '"', 'dec': '.', 'transpose': False, 'encoding': 'unknown', 'na.strings': ['NA'], 'out_graph': True, 'row.names': 1}
        cworkspace.add_dataset(os.path.join(data_dir,"protein.csv"), dataset, iparameters)
        dataset={'id': None, 'name': 'clinical', 'nature': 'metadata', 'status': 'dataset', 'parent': []}
        iparameters={'nature': 'metadata', 'header': True, 'sep': ',', 'quote': '"', 'dec': '.', 'transpose': False, 'encoding': 'unknown', 'na.strings': ['NA'], 'out_graph': True, 'row.names': 1}
        return cworkspace.add_dataset(os.path.join(data_dir,"clinical.csv"), dataset, iparameters)
    except (SessionInUse) as err:
        abort(503, description=err)
    except (IOError, REvalError, RConnectionRefused) as err:
        abort(400, description=err)

@app.route('/api/get_remaining_days', methods=['GET', 'POST'])
def get_remaining_days():
    uuid = request.json['uuid']
    workspace = Workspace.get_workspace(uuid)
    return { 'remainingDays': workspace.getRemainingDays() }

@app.route('/api/close_workspace', methods=['GET', 'POST'])
def close_workspace():
    if (app.env != "development"):
        uuid = request.json['uuid']
        cworkspace = Workspace.get_workspace(uuid)
        cworkspace.close()
    return { 'close': True }

@app.route('/api/get_workspace_uuids', methods=['GET', 'POST'])
def get_workspace_uuids():
    return { 'uuids': Workspace.get_workspace_uuids() }


@app.route('/api/get_metadata', methods=['GET', 'POST'])
def get_metadata():
    #convert in Byte
    m = m=re.search('(\d+)\s?(\S*)$', app.config['FILE_SIZE_LIMIT'])
    size_value = int (m.groups()[0] )
    if size_value > 0 :
        size_value = formatFileSize(size_value, m.groups()[1],'B')
    
    return { 'contact': app.config['CONTACT_EMAIL'] ,
    'report_bug_url' : app.config['REPORT_BUG_URL'] ,
    'source_version' : app.config['SOURCE_VERSION'] ,
    'copyright_year' : app.config['COPYRIGHT_YEAR'] ,
    'file_size_limit' : size_value}


@app.route('/api/create_workspace', methods=['GET', 'POST'])
def create_workspace():
    uuid = request.json['uuid']
    email = request.json['email']
    url = request.json['url']
    # Create workspace
    Workspace.create_workspace(uuid, email)
    # Send email to user
    body = '''Dear user,

Your workspace has been successfully created at {}{}.

Thank you for using ASTERICS,
The ASTERICS Team
'''.format(url,uuid)
    sendEmail('Workspace created', body, [email])
    return {}

#Function to secure 
def remote_addr_is_local():
    # Retrieve serveur IP
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("8.8.8.8", 80))
    trusted_proxies = (s.getsockname()[0],'127.0.0.1')    
    #Retrieve user IP
    route = list(request.access_route)
    for ip in route : 
        if ip in trusted_proxies: 
            return True
    return False
    

@app.route('/api/close_old_session', methods=['GET', 'POST'])
def close_old_session():
    #To be tested on server
    if remote_addr_is_local() :
        all_keys = app.rsessions.get_sessions_keys()
        for key in all_keys:
            workspace = Workspace.get_workspace(key,mode="control")
            #if last connexion delay is 1 day 
            if workspace.getDelayLastConnection()>= 0 :
                app.rsessions.close_session(key)
    else :
        abort(405, description="Not allowed!")

    return {}

@app.route('/api/get_file')
def get_file():
    secure_store_dir = app.config['STORAGE_DIR']
    file = request.args.get('file')
    (head, tail) = os.path.split(file)
    if (file.startswith(secure_store_dir)):
        if file.endswith(".png"):
            #TDO test id haed starts with : secure_store_dir
            return send_from_directory(head, tail, mimetype='image/PNG', as_attachment=True)
        else:
            return send_from_directory(head, tail, as_attachment=True)
    else:
        abort(405, description="Path not allowed!")

@app.route('/', defaults={'path': ''})  
@app.route('/<path:path>')
@app.route('/<path:path>/<uuid:uid>')
def catch_all(path, uid=None):
    #requests.get('http://localhost:8080/{}'.format(path)).text
    return render_template("index.html")

if __name__ == "__main__":
    app.run()
back to top