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.
Mise en oeuvre dans EMOLGINE : couplage exécution de code
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)
Sécurisation du web service :
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.
Conteneurisation :
Encapsuler l’ensemble dans une image docker afinde permettre l’intégration avec la plateforme.
Mise en oeuvre dans le cadre de PASSY :
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.