Serveur Web en Python avec `flask`

Serveur Web en Python avec flask #

Il existe de nombreux framework Web pour Python : Django, Cherrypy, Pyramid, Flask, Bottle… Nous allons nous concentrer ici sur flask en raison de sa relative simplicité d’utilisation, et de sa bonne diffusion.

Site officiel de flask : https://flask.palletsprojects.com/en/3.0.x/

La version de flask utilisée lors de la rédaction de ce document est la 2.3.X

Testez si le module est présent avec import flask. Si ce n’est pas le cas, un simple pip install flask devrait fonctionner.

Premiers tests, servir l’heure #

#!/bin/env python3
import flask
import datetime

app = flask.Flask(__name__)

@app.route('/time')
def index():
    formatstr = "Nous sommes le %d/%m/%Y, il est %H:%M:%S"
    heure = datetime.datetime.now().strftime(formatstr)
    stri = "<h1>Horloge</h1>"+heure
    return stri

# ============================================================
if __name__ == '__main__':
    app.config['DEBUG'] = True
    app.run(host='0.0.0.0', port=5000)

flask contient un serveur Web de test (pour une application importante, l’application Flask est généralement placée derrière un serveur capable de supporter une charge élevée (Apache ou Nginx par exemple)). Ce serveur Web est lancé par la dernière ligne :

app.run(host='0.0.0.0', port=5000)

Il sera accessible sur le port 5000, depuis n’importe quelle IP (host='0.0.0.0').

L’utilisation de flask consiste à écrire des fonctions, et à mapper ces fonctions sur des URLs.

Dans l’exemple qui précède, nous avons écrit la fonction index() et l’avons associé à l’URL /time.

Après lancement de l’application, par exemple en local, on peut donc consulter l’URL : http://localhost:5000/time. Flask exécutera alors la fonction index et renverra au client ce que renvoie la fonction. La partie HTTP est entièrement gérée par Flask. Nous avons juste à écrire le texte (généralement une page HTML) qui sera renvoyé.

Et c’est tout ! Ce principe permet déjà de répondre à la plupart des besoins.

Flask permet de faire beaucoup plus :

  • Système de templates (Jinja2), pour ne pas noyer son code Python dans du Html et avoir une maintenance plus simple
  • Gestion des cookies
  • Gestion des requêtes GET et POST, des formulaires
  • Passage de paramètres aux fonctions par le biais de l’URL

Nous allons voir certaines de ces fonctionnalités dans la suite.

Utilisation des templates #

Un template permet d’isoler les parties les plus statiques de la page et d’injecter dans cette page le contenu dynamique. Toutes les pages Web ayant une structure commune, chaque site ayant des réglages communs, un thème etc… ces informations doivent idéalement figurer à un seul endroit du code.

L’utilisation d’un template se fait par le biais de la fonction render_template qu’il est commode de renommer : TPL = flask.render_template

TPL = flask.render_template

...

@app.route('/time')
def index():
    formatstr = "Nous sommes le %d/%m/%Y, il est %H:%M:%S"
    heure = datetime.datetime.now().strftime(formatstr)
    return TPL("page.html", title="Horloge", data=heure) 

Le fichier page.html peut être placé par exemple dans le répertoire templates (cet endroit est configuré lors de la création de l’application Flask) :

<!doctype html>
<!-- page.html -->
<HTML lang="fr">
  <HEAD>
     <TITLE>{{title}}</TITLE>
     <meta charset="UTF-8">
  </HEAD>

  <body>
    <h1>{{title}}</h1>
    {{data | safe}}
    <hr/>
    <font size="-1"><i>Page réalisée avec Flask</i></font>
  </body>
</html>

La fonction index renvoie maintenant le résultat de render_templates, qui elle même prend en argument un template, ainsi que des arguments nommés qui seront injectés dans le template (ici title et data).

L’utilisation de {{data | safe}} ou lieu de {{data}} indique que le contenu de la variable data ….XXXXXX

Un formulaire avec Flask #

Voici comment utiliser les informations d’un formulaire. Une première page présente le formulaire (rien de spécial à signaler)

@app.route("/qui")
def qui() :
    stri = """
    <form method='post' action='bonjour'>
    <input type='text' name='nom' placeholder='Votre nom ?'/>
    <input type='submit' value='Bonjour Flask !'/>
    </form>
    """
    return TPL("page.html", title = "Présentez-vous", data=stri)

L’URL appelée lors de la validation est /bonjour, avec la méthode POST :

@app.route("/bonjour", methods=['POST'])
def bonjour() :
    nom = flask.request.form['nom']
    stri = "Bonjour mon(a) che(è)r(e) {}".format(nom)
    return TPL("page.html", title="Bonjour", data=stri)

Il faut préciser methods=['POST'] car par défaut, les routes ne concernent que les requêtes de type GET. Une fonction peut aussi répondre aux deux types de requêtes en indiquant : methods=['POST', 'GET'].

Les valeurs entrées dans le formulaire sont récupérables simplement avec :

nom = flask.request.form['nom']

Passage de paramètres aux fonctions #

Une fonctionnalité intéressante permet de passer des portions de l’URL comme paramètres de la fonction :

@app.route("/menu")
def menu() :
    stri = """<ul>
    <li><a href='/action/temp'>Température</a></li>
    <li><a href='/action/pluie'>Pluie</a></li>
    </ul>
    """
    return TPL("page.html", title="Site Météo", data=stri)


@app.route("/action/<nom_action>")
def action(nom_action) :
    if nom_action == 'temp':
        stri = "Il ne fera pas très froid"
    elif nom_action == 'pluie':
        stri = "Il ne pleuvra pas trop. Ou l'inverse"
    else:
        stri = "Demande erronée"
    stri += "<hr/> <a href='../menu'>Retour menu</a>"
    return TPL("page.html", title="Site Météo", data=stri)

La route donnée pour la seconde fonction indique que la fonction action est associées aux URLs de type : /action/XXXXXXX est passé en paramètre nommé à la fonction.

Notons que l’utilisation de l’URL /action ne fonctionnera pas (mais on peut associer plusieurs URLs à une seule fonction, comme indiqué dans le manuel).

Code complet #

#!/bin/env python3
import flask
import datetime

TPL = flask.render_template
app = flask.Flask(__name__)

@app.route('/time')
def index():
    formatstr = "Nous sommes le %d/%m/%Y, il est %H:%M:%S"
    heure = datetime.datetime.now().strftime(formatstr)
    return TPL("page.html", title="Horloge", data=heure) 

@app.route("/qui")
def qui() :
    stri = """
    <form method='post' action='bonjour'>
    <input type='text' name='nom' placeholder='Votre nom ?'/>
    <input type='submit' value='Bonjour Flask !'/>
    </form>
    """
    return TPL("page.html", title = "Présentez-vous", data=stri)
    
@app.route("/bonjour", methods=['POST'])
def bonjour() :
    nom = flask.request.form['nom']
    stri = "Bonjour mon(a) che(è)r(e) {}".format(nom)
    return TPL("page.html", title="Bonjour", data=stri)

@app.route("/menu")
def menu() :
    stri = """<ul>
    <li><a href='/action/temp'>Température</a></li>
    <li><a href='/action/pluie'>Pluie</a></li>
    </ul>
    """
    return TPL("page.html", title="Site Météo", data=stri)

@app.route("/action/<nom_action>")
def action(nom_action) :
    if nom_action == 'temp':
        stri = "Il ne fera pas très froid"
    elif nom_action == 'pluie':
        stri = "Il ne pleuvra pas trop. Ou l'inverse"
    else:
        stri = "Demande erronée"
    stri += "<hr/> <a href='../menu'>Retour menu</a>"
    return TPL("page.html", title="Site Météo", data=stri)

    
# ============================================================
if __name__ == '__main__':
    app.config['DEBUG'] = True
    app.run(host='0.0.0.0', port=5000)

Pensez à récupérer aussi le fichier template : page.html.

Code complet / un template par page #

Il peut être préférable d’avoir un template par page. On peut dans ce cas utiliser la directive {% extends "XXX.html" %} dans le template, pour baser chaque template particulier sur un template plus général. (Voir la documentation)

Servir de pages statiques #

Si votre site contient des images ou des feuilles de style, elles peuvent aussi être servies par Flask. Il suffit de les déposer dans le dossier nommé static à la racine du site.

Autre exemple complet Flask #

Example flask contenant :

  • redirection
  • code d’erreur (ici 403) avec abort
  • récupération des paramètres GET
  • récupération des paramètres POST
  • réalisation d’un formulaire
  • utilisation d’un template Jinja2
  • utilisation de url_for
  • renvoi de données au format Json (pour Api Rest)

Enregistrer les deux fichiers dans le même répertoire :

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

<!-- ======================= head ========================== -->
<title>Titre : {{ title }}</title>
</head>

<body>
<div>
{{ data | safe }}
</div>
<div id="footer">
<hr/>
<a href="{{ url_for('info') }}">home</a>
 - <a href="{{ url_for('paramurl', number=42) }}">paramurl</a>
 - <a href="{{ url_for('formulaire') }}">formulaire</a>
 - <a href="{{ url_for('paramget') }}?login=toto">paramget</a>
 - <a href="{{ url_for('data_json') }}">data_json</a>
 - <a href="{{ url_for('redirect_me') }}">redirect_me</a>
 - <a href="{{ url_for('forbidden') }}">forbidden</a>
</div>
</body>
</html>
#!/bin/env python3

import flask
TPL = flask.render_template # Pour éviter de toujours taper flask.render_template...

app = flask.Flask(__name__, template_folder='.')

@app.route('/')
def info():
    data = """\
    Bonjour Visiteur
    """
    return TPL("default.html", title='Home', data=data)

@app.route('/paramurl/<int:number>')
def paramurl(number):
    data = """\
    Vous avez mis  {} dans l'URL.
    """.format(number)
    return TPL("default.html", title='ParamUrl', data=data)

@app.route('/paramget')
def paramget():
    login = flask.request.args['login']
    data = """\
    Le login entré est {}.
    """.format(login)
    return TPL("default.html", title='Paramget', data=data)

@app.route('/formulaire')
def formulaire():
    data = """\
    <form action="validate" method="post">
    <input type="text" name="login"/><br/>
    <input type="submit"/>
    </form>
    """
    return TPL("default.html", title='Formulaire', data=data)

# Récupération des données d'un formulaire, en POST uniquement
# Si les données sont postées en json et non en
# application/x-www-form-urlencoded ou multipart/form-data
# utiliser flask.request.get_json()  au lieu
# de flask.request.form
@app.route('/validate', methods=["POST"])
def validate():
    login = flask.request.form['login']
    data = """\
    Le login entré est {}.
    """.format(login)
    return TPL("default.html", title='Validate', data=data)


# Renvoie du json
@app.route('/data_json')
def data_json():
    liste = [1, 2, 3, "toto"]
    dico = {"val1": liste, "val2": "Salut"}
    return flask.jsonify(dico) # Renvoie la chaîne json en gérant correctement l'entête HTTP

# Code de retour :
# https://fr.wikipedia.org/wiki/Liste_des_codes_HTTP
@app.route('/forbidden')
def forbidden():
    flask.abort(403)

@app.route('/redirect_me')
def redirect_me():
    return flask.redirect(flask.url_for('info'))

print("PATH =====>", app.instance_path)
if __name__ == '__main__':
    app.config['DEBUG'] = True
    app.secret_key = 'chooseaverysecretkeyhere'
    app.run(host='0.0.0.0', port=5000)