• contact@spgoo.org

Web Service sous Python : Flask (Janvier 2026)

Mise en oeuvre et développement de web service sécurisé sous Flask pour desservir plusieurs plateformes web : PASSY, Emolgine et d’autres.

Nous allons détailler deux aspects de ces possibilités de couplage :

  • interfaçage avec des bases de données
  • exécution de module python ou autres langage.

Et pour finir faire une comparaison avec des environnement Java de type Spring Boot.

Emolgine plateforme de production et de sélection de molécules à usage pharmacologique. Le besoin de la plateforme est de pouvoir lors de la manipulation d’un ensemble de molécules de définir en fonction de la méthode proposée des similarités entre elles. Ce calcul de similarité doit être également effectué au niveau des clusters. Pour ce faire deux codes dédiés et mis au point par PK ont été développés et testés. Le tout est d’offrir à l’utilisateur lors de sa sélection de molécules la possibilité d’identifier des niveaux de similarité au niveau de son interface. Pour ce faire nous avons mis en place via des appels fetch en javascript des liens vers l’exécution de ces scripts.

from flask import Flask, request, jsonify
import sys
from rdkit import Chem
from rdkit.Chem import  rdMolDescriptors
from numba import njit
import numpy as np
from create_sim_csv_from_smi import *
from create_sim_csv_from_cluster_mol_site import *
from create_sim_csv_from_Lcluster_with_smile import *
from get_info import *
import uuid

app = Flask(__name__)

@app.route("/exec", methods=["POST"])
def index():
    request_data = request.get_json()
    smiles = request_data['smiles']
    path="./tmp/"
    name_file=str(uuid.uuid4())
    with open(path+name_file, 'w') as f:
       for smile in smiles:
         f.write(smile+"\n")
    tp=appel_exec(path,name_file)
    code=""
    with open(path+name_file+".csv", mode="r", encoding="utf-8") as hello:
         code = hello.read()

    # lecture du fichier resultat pour renvoi
    return format(code)

@app.route("/exec_ws_sim_mol", methods=["POST"])
def index_ws():
    request_data = request.get_json()
    smiles = request_data['smiles']
    similarites = request_data['similarites']
    fingerprints = request_data['fingerprints']
    tp=appel_exec_ws(smiles,similarites,fingerprints)
    return format(tp)

@app.route("/exec_ws_sim_cluster", methods=["POST"])
def index_wss_sim_cluster():
    request_data = request.get_json()
    cluster = request_data['cluster']
    tp=appel_exec_ws_sim_cluster(cluster)
    return format(tp)

@app.route("/exec_ws_sim_cluster_molecules", methods=["POST"])
def index_wss_sim_cluster_molecules():
    request_data = request.get_json()
    inchikey = request_data['inchikey']
    cluster = request_data['cluster']
    tp=appel_exec_ws_cluster_mol(inchikey,cluster)
    return format(tp)

@app.route("/exec_get_info", methods=["POST"])
def index_wss_get_info():
    request_data = request.get_json()
    cas = request_data['cas']
    elemnt = request_data['id']
    tp=appel_info_emolgine(cas,elemnt)
    return format(tp)

if __name__ == "__main__":
    from waitress import serve
    serve(app,host="0.0.0.0", port=8090)

Détail d’un exemple d’exécution :

import sys
from rdkit import Chem
from rdkit.Chem import  rdFingerprintGenerator
from rdkit.Chem import  rdMolDescriptors
from numba import njit
import numpy as np
import json
# -----------------------------------------------
# avec fonction de regroupement implementée 
# ---------------------------------------------------
def get_Vfp_from_mol( mol, radius=2, nBits=2048, with_chirality=False, with_features=True, as_bitVect=True,):
    """
    Gets a  Morgan fingerprint for a molecule
    """
    rfp=rdMolDescriptors.GetMorganFingerprintAsBitVect(mol,radius,nBits=nBits,useFeatures=with_features,
             useChirality=with_chirality,)
    Vfp =  np.frombuffer(rfp.ToBitString().encode(), 'u1') - ord('0') # https://github.com/rdkit/rdkit/discussions/3863
    return Vfp.astype(bool)

def get_Mfp_from_Lmol( Lmol, with_chirality=False, radius=3,):
    mfpgen = rdFingerprintGenerator.GetMorganGenerator(radius=3,fpSize=2048)
    LVfp = []
    for mol in Lmol:
        rfp = mfpgen.GetFingerprint(mol)
        Vfp = np.frombuffer(rfp.ToBitString().encode(), 'u1') - ord('0')
        LVfp.append(Vfp.astype(bool))
    return np.array(LVfp)

def get_sim_bw_Vfp( Vfp1, Vfp2):
    return round(get_sim_bw_Vfp_njit(Vfp1,  Vfp2),2)

@njit 
def get_sim_bw_Vfp_njit(Vfp1,  Vfp2):
    n = Vfp1.shape[0]
    # -----------------
    n11 = 0
    n00 = 0
    for k in range(n):
        n11 += (Vfp1[k] * Vfp2[k])
        if (Vfp1[k] == 0) and ( Vfp2[k] == 0):
            n00 += 1
    return n11 / (n - n00) 

def get_Ls_sim_from_Mfp( Mfp, sim_min=0.6,):
    n = len(Mfp)
    Ls = []
    for i in range(n):
        for j in range(i+1, n):
            sim = get_sim_bw_Vfp(Mfp[i], Mfp[j])
            if sim > sim_min:
                Ls.append([i,j,sim])
                #Ls.append(f"{i}, {j}")
    return Ls

def create_txt_from_L( L, fname,):
    with open(fname, "w") as fw:
        for x in L:
            fw.write(f"{x}\n")

def regroupement(tab):
    groupes={}
    # generation des groupes  par defaut, chaque element forme son propre groupe
    for elem in tab:
     if str(elem[0]) not in groupes.keys():
         groupes[elem[0]]=[]
     if str(elem[1]) not in groupes.keys():
         groupes[elem[1]]=[]
    for elem in tab:
      # on detruit la cle du deuxième element
      if elem[0] in groupes:
        if elem[1] in groupes:
           del groupes[elem[1]]
           groupes[elem[0]].append(elem[1])
      # il est dans un sous groupe il faut trouver lequel et détruire ensuite l'entrée correspondante
      else:
        for grp in groupes:
           if groupes[grp].count(elem[0])>0 and groupes[grp].count(elem[1])==0:
               groupes[grp].append(elem[1])
               if elem[1] in groupes:
                  if len(groupes[elem[1]])==0:
                      del groupes[elem[1]]
                      break;
    return groupes

# ===============================================================

def appel_exec(path,smi):
   with open(path+smi, "r") as fp:
       Lsmile = fp.readlines()
   Lmol = [Chem.MolFromSmiles(smile) for smile in Lsmile]
   Mfp = get_Mfp_from_Lmol(Lmol)
   Ls = get_Ls_sim_from_Mfp(Mfp)
   create_txt_from_L(Ls, path+smi+".csv")

# on fournit un tableau contenant des dict [id, smile]
def appel_exec_ws(Lsmile):
   Lmol = [Chem.MolFromSmiles(smile["smile"]) for smile in Lsmile]
   Mfp = get_Mfp_from_Lmol(Lmol)
   Ls = get_Ls_sim_from_Mfp(Mfp)
   # on a des couples de similarités qu'il faut recomposer en groupes avec le bon id 
   Ls_YS=regroupement(Ls)
   result=[]
   for couple in Ls:
        tab_id=[]
        if len(Ls_YS[couple])==1:
           tab_id.append(Lsmile[couple]["id"])
           tab_id.append(Lsmile[Ls_YS[couple][0]]["id"])
        else:
           for el in Ls_YS[couple]:
              tab_id.append(Lsmile[el]["id"])

        result.append(tab_id)
   return json.dumps(result)

Faire en sorte de limiter les accès à ce service sur le serveur : par l’utilisation de token et de bloquer via le firewall par des règles toute tentative d’une machine externe.

Encapsuler l’ensemble dans une image docker afinde permettre l’intégration avec la plateforme.

L’objectif dans cette plateforme est de fournir des traitements d’analyse sonore. Plusieurs possibilités ont été étudiées : le développement en javascript en s’appuyant sur des librairies dédiées de type WaveSurfer ou autres et l’utilisation d’application métiers.