8 min read

GdT-PyR 15: Développement d'applications web avec python

Session du 30/11/2020: développement d’applications web avec python et les librairies Dash et Flask

https://forgemia.inra.fr/gdtpyr/gdt_pyr/-/tree/main/GDT_PyR15_Applications_web

Cet session est un complément à la session du 14/10/2019:

https://forgemia.inra.fr/gdtpyr/gdt_pyr/-/tree/main/GDT_PyR9_devAppWeb

Introduction

L’objectif de ce document est de présenter la création d’applications web depuis Python, en utilisant les technologies Flask et Dash. Ces deux technologies sont bien liées car Dash utilise le moteur de Flask pour construire la base de l’appli web, puis rajoute l’interaction avec le langage javascript.

Flask et Dash sont parmi les plus utilisés pour créer des applications Python. On peut aussi retrouver Django.

La documentation officielle est ici:
https://flask.palletsprojects.com/en/1.1.x/
https://flask.palletsprojects.com/en/1.1.x/quickstart/#routing
https://flask.palletsprojects.com/en/1.1.x/quickstart/#accessing-request-data
https://docs.djangoproject.com/en/3.1/
https://plotly.com/dash/
https://dash.plotly.com/dash-enterprise/application-structure
https://dash.plotly.com/dash-core-components
https://dash.plotly.com/dash-html-components
https://dash.plotly.com/datatable
https://dash.plotly.com/dash-bio
https://dash.plotly.com/cytoscape
https://dash-bootstrap-components.opensource.faculty.ai/

Installation des packages

Pensez aux environnements virtuels (conda/venv).

pip install Flask

pip install dash  
pip install dash-renderer   
pip install dash-html-components  
pip install dash-core-components  
pip install plotly --upgrade

Framework Dash

Dash est un framework Python pour construire des applications web analytiques. Les applications Dash sont entièrement générées à partir de Python, même le code HTML et JavaScript. C’est une framework composé de trois frameworks : - React.js : utilise un système de plugins pour créer ses propres composants - Plotly : créer la partie frontend avec des outils UI de data visualisation - Flask : framework écrit en python pour le backend.

Flask est plus utilisé quand on a des connaissances dans plusieurs langages. Dash est plus général et est basé sur Flask (qui sera présenté plus tard). Dash est pratique car il ne nécessite pas de connaître d’autres langages que python grâce à ses différentes librairies.

  • Dash Core Components : pour les composants de l’interface utilisateur (création de liste déroulante, curseur…)
import dash_core_components as dcc
dcc.Dropdown(
    options=[
        {'label': 'New York City', 'value': 'NYC'},
        {'label': 'Montréal', 'value': 'MTL'},
        {'label': 'San Francisco', 'value': 'SF'}
    ],
    value='MTL'
) 
  • Dash HTML Component : pour composer la structure HTML de l’application.
import dash_html_components as html

html.Div([
    html.H1('Hello Dash'),
    html.Div([
        html.P('Dash converts Python classes into HTML'),
        html.P('This conversion happens behind the scenes by Dash's JavaScript front-end')
    ])
])
  • Dash DataTable : pour ajouter des tableaux de données mais aussi pouvoir explorer et modifier de grands jeux de données.
import dash
import dash_table
import pandas as pd

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')
app = dash.Dash(__name__)
app.layout = dash_table.DataTable(
    id='table',
    columns=[{"name": i, "id": i} for i in df.columns],
    data=df.to_dict('records'),
)
if __name__ == '__main__':
    app.run_server(debug=True)
  • Dash Bootstrap Components : des composants Bootstrap pour Dash pour construire plus facilement la disposition de l’application.
pip install dash-bootstrap-components
import dash
import dash_bootstrap_components as dbc

app = dash.Dash(
    external_stylesheets=[dbc.themes.BOOTSTRAP]
)

app.layout = dbc.Alert(
    "Hello, Bootstrap!", className="m-5"
)

if __name__ == "__main__":
    app.run_server()
  • Dash Bio : un ensemble de composants bioinformatique qui permet de simplifier la visualisation et traitement des données dans ce domaine.
import dash_bio as dashbio
import six.moves.urllib.request as urlreq

data = urlreq.urlopen(
 "https://raw.githubusercontent.com/plotly/dash-bio-docs-files/master/" + 
 "alignment_viewer_p53.fasta"
).read().decode("utf-8")

component = dashbio.AlignmentChart(
  id='my-dashbio-alignmentchart',
  data=data
)
  • Dash Cytoscape : pour le traitement et visualisation de données de graphes.
pip install dash-cytoscape==0.2.0
import dash
import dash_cytoscape as cyto
import dash_html_components as html
app = dash.Dash(__name__)

app.layout = html.Div([
    cyto.Cytoscape(
        id='cytoscape-two-nodes',
        layout={'name': 'preset'},
        style={'width': '100%', 'height': '400px'},
        elements=[
            {'data': {'id': 'one', 'label': 'Node 1'}, 'position': {'x': 75, 'y': 75}},
            {'data': {'id': 'two', 'label': 'Node 2'}, 'position': {'x': 200, 'y': 200}},
            {'data': {'source': 'one', 'target': 'two'}}
        ]
    )
])

if __name__ == '__main__':
    app.run_server(debug=True)

Architecture pour Dash

L’architecture d’une application Dash est assez simple :

Dash_App/
|-- assets/
   |-- app.css
|--data
        |-- data.csv
|-- app.py
|-- .gitignore
|-- CHECKS
|-- Procfile
|-- requirements.txt
|-- runtime.txt

app.py : c’est là où se trouve l’application avec le code Dash. Il doit contenir une ligne pour définir le server (server = app.server).

data : dossier contenant fichier de données (csv, txt, …), que l’on peut utiliser de la manière suivante:

import pandas as pd

df = pd.read_csv('data/data.csv', index_col=0, parse_dates=True)
df.index = pd.to_datetime(df['Date'])
  • .gitignore : détermine quels fichiers et dossiers qui ne sont pas encore dans git (donc ignoré par le server au moment du déploiement).
  • assets : dossier facultatif qui peut contenir des fichiers css ou javascript pour compléter l’application
  • CHECKS : fichier optionnel qui peut être exécuté pour vérifier les performances de l’application.
  • Procfile : les commandes qui vont être exécutées par le conteneur de l’application.
  • requirements.txt : les installations nécessaires pour l’application
  • runtime.txt : fichier optionnel pour préciser la version de python à utiliser (par défaut pyhon 3.6.7 est installé)

Importation de données web service

Possibilité de se connecter et récupérer des données de web service à utiliser dans les applis web. Cf. code dans la version déposée sur GdT-PyR.

Comment écrire une app

L’architecture classique d’une application est la suivante :
On rentrera dans les détails au fur-et-à mesure.

.
├── app
│   ├── main.py
│   ├── static
│   └── templates
├── documentation_dev.md
├── Dockerfile
├── "environnement python"
│   └── pyvenv.cfg
├── README.md
└── requirements.txt

On va détailler le fichier main.py. C’est le coeur de l’application, il fonctionne de manière autonome. Le reste c’est pour rendre plus “joli”, plus efficace et d’autres fonctions.

Différentes pages

On définit des “routes”, ce sont les adresses URL. Lorsque l’on navigue vers ces adresses, la fonction se déclenche et retourne un résultat.

from flask import Flask
app = Flask(__name__)

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/hello')
def hello():
    return 'Flask says Hello'

HTTP Methods

Il y a deux principales interactions les “GET”, où on récupère des données depuis le serveur, et les POST où on envoie des données vers le serveur. Les GET sont principalement des affichages de pages et formulaires. Les POST sont les envois de ces dit formulaires, et la soumission de jeux de données.

from flask import request
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()

Templates

On veut quand même des pages un peu plus jolies avec de la vraie mise en page. Pour ça on utilise des templates HTML. Et on dit à Flask d’aller chercher ces pages et de nous les renvoyer. On peut aussi écrire du HTML directement dans le “return” de la fonction, mais c’est pas très propre. On détaille ici le rôle du dossier “templates” de l’architecture de l’appli. C’est ici que l’on place nos différents templates qui constituent différentes pages de notre application.

from flask import render_template, session

@app.route('/home')
@app.route('/')
def home():
    if 'logged_in' not in session:
        session['logged_in'] = False
    if 'username' in session:
        return render_template('home.html', username = session['username'], statut = session['logged_in'])
    else:
        session['username'] = ""
        return render_template('home.html', username = "", statut = session['logged_in'])

Je me suis permis aussi d’introduire l’objet “session”, qui sont les fameux cookies que les sites webs vous demandent sans arrêt. C’est un moyen de stocker et transférer de l’informations d’une session à la suivante. L’objet session va se différencier de l’objet cookies par la signature crytographique qui lui est apportée. Cela demande de définir une clé de cryptage pour l’application. (voir fichier “.env” pour le garder secret)

Accéder aux données

Une fois qu’on a soumis un formulaire, on veut que python puisse utiliser ces données. Pour cela on utilise l’objet “request”. C’est l’objet qui est transmis lors du “POST”.

from flask import render_template, Flask, session, request

@app.route('/login', methods = ['GET', 'POST'])
def login():
    if request.method == 'POST':
        user = User.query.filter_by(username = request.form['username']).first()
        if user is None or not user.check_password(request.form['password']):
            flash('Invalid username or password')
            return render_template("login.html", statut = session['logged_in'])
        session['username'] = request.form['username']
        session['logged_in'] = True
        return render_template('home.html', username = session['username'], statut = session['logged_in'])
    return render_template("login.html", statut = session['logged_in'])

Il faut donc faire le lien entre le formulaire HTML et la requête dans python. Ce lien se fait par le nom du formulaire et des composants “INPUT”, “SELECT” etc.

<form method="POST">
   <label for="username"> Username </label> <br>
   <input type=text name="username" value="test"> <br>
  
   <label for="password"> Password </label> <br>
   <input type="password" name="password" value="test"> <br>
  
   <button type=submit class="btn btn-primary"> Login </button>
</form>

Deploiement

Il y a un petit moteur de déploiement qui permet un déploiement de développement, c’est à dire pour voir ce qu’on fait pendant le développement de l’application. Ce moteur supporte mal le passage à l’échelle (voir un GDT-PyR prochain!).

$ export FLASK_APP=main.py
$ flask run
 * Running on http://127.0.0.1:5000/

En alternative, il suffit d’executer le fichier et de s’assurer que celui-ci se termine par la ligne suivante :


if __name__ == "__main__":
    app.run(host = '0.0.0.0', debug = True, threaded = True, port = 3838)

Déploiement par Docker… Objet de futures GDT_PyR 16 et 17

Je peux vous proposer un aperçu de mon travail https://github.com/JeanEudesH/uri-generator qui est une appli basée sur Flask.

Architecture générale d’une appli avancée

Exemple complet ici : https://hackersandslackers.com/plotly-dash-with-flask/
dash + flask. Attention c’est assez dur, il est conseillé de prendre l’approche “classique” pas à pas avant de se lancer là-dedans.

Découpé en fonctions - appli dans /app
- fichier de commandes lancement (.sh)
- déploiement wsgi
- fichier de config -> lecture du fichier .env (caché) + configuration packages-répertoires
- Documentation + packages utilisés
- On peut rajouter Dockerfile pour déploiement container

/plotlydash-flask-tutorial
├── /app
│   ├── __init__.py
│   ├── routes.py
│   ├── /static
│   ├── /templates
│   └── /plotlydash
│       └── dashboard.py
├── /data
├── README.md
├── config.py
├── requirements.txt
├── start.sh
└── wsgi.py