Serveur Web en Python avec `bottle`

Serveur Web en Python avec bottle #

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

Site officiel de bottle : http://bottlepy.org/docs/dev/index.html

La version de bottle utilisée lors de la rédaction de ce document est la 0.12.

l’installation se résume à récupérer le fichier bottle.py et à le déposer dans le PATH de Python (le mieux est de passer par le système d’installation standard de votre machine).

Premiers tests, servir l’heure #

import bottle
import datetime

@bottle.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

bottle.run(bottle.app(), host='0.0.0.0', port=8080, debug= True, reloader=True)

bottle contient un petit serveur Web (pour une application importante, l’application Bottle est 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 :

bottle.run(bottle.app(), host='0.0.0.0', port=8080, debug= True, reloader=True)

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

L’utilisation de bottle 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:8080/time. Bottle 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 bottle. 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.

bottle permet de faire beaucoup plus :

  • Système de templates intégré, 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 l’utilisation du décorateur bottle.view :

@bottle.route("/time")
@bottle.view("page.tpl")  
def index() :
    formatstr = "Nous sommes le %d/%m/%Y, il est %H:%M:%S"
    heure = datetime.datetime.now().strftime(formatstr)
    return {"title":"Horloge", "body" : heure}   

Le fichier page.tpl peut être placé au même endroit que le fichier Python ou bien dans le répertoire views (cet endroit peut de plus être configuré) :

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

  <body>
    <h1>{{title}}</h1>
    {{!body}}
    <hr/>
    <font size="-1"><i>Page réalisée avec Bottle</i></font>
  </body>
</html>

La fonction index ne renvoie plus une chaîne, mais un dictionnaire, contenant des clés (ici title et body) qui seront utilisées dans le fichier template. Finalement bottle servira le contenu du fichier template après avoir remplacé le contenu de {{title}} par la valeur associée à la clé title et le contenu de {{!body}} par la valeur associée à la clé body. La présence du ! permet d’indiquer à bottle de ne pas échapper les caractères de balisage HTML dans la chaîne body. Elle pourra donc contenir des balises.

Un formulaire avec bottle #

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

@bottle.route("/qui")
@bottle.view("page.tpl")
def qui() :
    stri = """
    <form method='post' action='bonjour'>
    <input type='text' name='nom' placeholder='Votre nom ?'/>
    <input type='submit' value='Bonjour bottle !'/>
    </form>
    """
    return {"title":"Présentez-vous", "body" : stri}

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

@bottle.route("/bonjour", method='POST')
@bottle.view("page.tpl")
def bonjour() :
    nom = bottle.request.forms.get('nom')
    stri = "Bonjour mon(a) che(è)r(e) {}".format(nom)
    return {"title":"Bonjour", "body" : stri}

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

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

nom = bottle.request.forms.get('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 :

@bottle.route("/menu")
@bottle.view("page.tpl")
def menu() :
    stri = """<ul>
    <li><a href='/action/temp'>Température</a></li>
    <li><a href='/action/pluie'>Pluie</a></li>
    </ul>
    """
    return {"title":"Site Météo", "body" : stri}


@bottle.route("/action/<nom_action>")
@bottle.view("page.tpl")
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 {"title":"Site Météo", "body" : 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 #

(Une autre version, avec un template par page est proposée dans la section suivante)

import bottle
import datetime

@bottle.route("/time")
@bottle.view("page.tpl")
def index() :
    heure = datetime.datetime.now().strftime("<p>Nous sommes le %d/%m/%Y, il est %H:%M:%S</p>")
    return {"title":"Horloge", "body" : heure}

@bottle.route("/qui")
@bottle.view("page.tpl")
def qui() :
    stri = """
    <form method='post' action='bonjour'>
    <input type='text' name='nom' placeholder='Votre nom ?'/>
    <input type='submit' value='Bonjour bottle !'/>
    </form>
    """
    return {"title":"Présentez-vous", "body" : stri}


@bottle.route("/bonjour", method='POST')
@bottle.view("page.tpl")
def bonjour() :
    nom = bottle.request.forms.get('nom')
    stri = "Bonjour mon(a) che(è)r(e) {}".format(nom)
    return {"title":"Bonjour", "body" : stri}

@bottle.route("/menu")
@bottle.view("page.tpl")
def menu() :
    stri = """<ul>
    <li><a href='/action/temp'>Température</a></li>
    <li><a href='/action/pluie'>Pluie</a></li>
    </ul>
    """
    return {"title":"Site Météo", "body" : stri}


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

bottle.run(bottle.app(), host='0.0.0.0', port=8080, debug= True, reloader=True)

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

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 fonction rebase qui permet de base un template sur un autre, de manière à ne donner qu’une seule fois les parties communes à toutes les pages.

Une telle version est disponible dans l’archive ci-jointe. C’est probablement une bonne base pour démarrer un travail.

Servir de pages statiques #

Si votre site contient des images ou des feuilles de style, elles peuvent aussi être servies par bottle. Dans ce cas, il convient de les placer dans un répertoire nommé static, puis d’ajouter la route suivante :

@bottle.route('/static/<filename:path>')
def server_static(filename):
    " Fichiers statiques du site placés dans le rép /static/ "
    return bottle.static_file(filename, root='static')

On accède ensuite aux fichiers statiques par l’URL : /static/mafeuilledestyle.css.