Tutoriel sur Flask

imt

1. Objectif

Nous allons ici implémenter un micro-service Movie. Pour cela créer un répertoire spécifique. Nous allons ensuite créer un nouvel environnement virtuel Python pour ce service dans lequel nous installerons Flask.

Important
Je considère Python dans sa version 3.x déjà installé sur vos machines. Il peut également être intéressant d’installer Postman (https://www.postman.com/). Si ce n’est pas le cas merci de le faire. Vous pouvez vérifier votre version de Python avec la commande python --version. Attention il est possible que vous ayez plusieurs versions de Python sur votre machine, utilisez bien Python3 pour cette UE.

2. Installation de Flask

La meilleure façon de faire des installations pour un projet python sans polluer tout l’environnement Python est de créer un environnement virtuel.

pip3 install virtualenv
python3 -m venv /path/to/new/virtual/environment

Activez ensuite votre environnement, comme par exemple sous linux avec :

source <venv>/bin/activate
Note
voir ici pour plus de détails https://docs.python.org/3/library/venv.html

Nous pouvons maintenant installer Flask dans l’environnement virtuel.

pip3 install Flask

3. Vérifier l’installation

Créer un helloworld.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Hello world !"

if __name__ == "__main__":
    app.run()

Exécuter la commande (dans l’environnement virtuel contenant Flask)

python3 helloworld.py

Vous devriez observer une sortie comme celle-ci et pouvoir accéder à l’adresse indiquée dans un navigateur.

* Serving Flask app "helloworld" (lazy loading)
* Environment: production
  WARNING: This is a development server. Do not use it in a production deployment.
  Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

4. Les bases de notre service Movie

4.1. Création et lecture des données JSON

Récupérer le fichier movie.json sur Moodle et le placer dans un répertoire databases.

Le code ci-dessous permet la lecture d’un fichier JSON. L’objet movies récupéré est obtenu en lisant la clé "movies" et est donc un array.

from flask import Flask
import json

with open('{}/databases/movies.json'.format("."), "r") as jsf:
   movies = json.load(jsf)["movies"]

4.2. Création d’un point d’entrée

Nous allons créer un point d’entrée pour notre service. Ce point d’entrée se situe à la racine /, reçoit des requêtes HTTP de type GET et construit une réponse (au moyen de la méthode make_response) contenant une balise HTML.

from flask import Flask, make_response
import json

app = Flask(__name__)

with open('{}/databases/movies.json'.format("."), "r") as jsf:
   movies = json.load(jsf)["movies"]

@app.route('/', methods=['GET'])
def index():
    return make_response("<h1 style='color:blue'>Welcome to the Movie service!</h1>",200)

if __name__ == "__main__":
    app.run()

Exécuter ce code et accéder à la page.

4.3. Utilisation des templates dans Flask

Dans Flask il est possible d’utiliser des templates Jinja

L’idée de base est de créer un répertoire templates dans lequel seront placés des fichiers template. Ici nous allons créer un fichier templates/index.html avec ce contenu :

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Flask tutorial</title>
    </head>
    <body>
        <h1>{{ body_text }}</h1>
    </body>
</html>

Tout partie de texte présente entre {{ }} est une expression qui sera remplacée dans le document final. Notez qu’il existe aussi la balise {% %} qui permet d’ajouter des conditions et des boucles au template.

Créons donc un nouveau point d’entrée pour exploiter ce template au travers de la méthode render_template.

from flask import Flask, render_template, make_response
import json

...

@app.route("/template", methods=['GET'])
def template():
    return make_response(render_template('index.html', body_text='This is my HTML template for Movie service'),200)

Lancer le programme et accéder à URL/template pour observer le résultat.

5. Points d’entrée pour obtenir des données

5.1. GET JSON en entier

Créons un point d’entrée retournant le fichier JSON entièrement. Pour cela nous utilisons la méthode jsonify qui permet de créer une réponse HTTP à partir d’un format JSON (see this link).

from flask import Flask, render_template, jsonify, make_response
import json

...

@app.route("/json", methods=['GET'])
def get_json():
    res = make_response(jsonify(movies), 200)
    return res

5.2. GET information d’un film à partir de son ID

Rappelons que l’objet movies est un array, on peut donc itérer sur ses éléments que nous appelons ici movie. Chaque objet movie est un dictionnaire. Nous vérifions pour chaque élément si la valeur associée à la clé "id" est l’ID recherché. Si tel est le cas le dictionnaire movie courrant est retourné.

@app.route("/movies/<movieid>", methods=['GET'])
def get_movie_byid(movieid):
    for movie in movies:
        if str(movie["id"]) == str(movieid):
            res = make_response(jsonify(movie),200)
            return res
    return make_response(jsonify({"error":"Movie ID not found"}),400)

5.3. GET information à partir du titre avec un argument dans la requête

Dans ce point d’entrée nous allons voir comment utiliser des arguments dans une requête. Ici l’argument est une clé-valeur avec pour clé "title" et pour valeur le titre du film à chercher.

A noter que request.args retourne un werkzeug.MultiDict de Flask formé de cette façon [(key,vlue),(key,value),…​]. Toutes les informations sont ici

@app.route("/moviesbytitle", methods=['GET'])
def get_movie_bytitle():
    json = ""
    if request.args:
        req = request.args
        for movie in movies:
            if str(movie["title"]) == str(req["title"]):
                json = movie

    if not json:
        res = make_response(jsonify({"error":"movie title not found"}),400)
    else:
        res = make_response(jsonify(json),200)
    return res

6. Points d’entrée pour modifier, ajouter, et supprimer des données

6.1. POST ajouter un nouveau film

Nous créons ici un point d’entrée de l’API permettant l’ajout d’un nouveau film en base. Ce point d’entrée reçoit des requêtes de type POST. Nous avons ici aussi besoin d’utiliser request pour récupérer le JSON donné dans le corps de la requête. Ce JSON est de la forme :

{
  "title": "Test",
  "rating": 1.2,
  "director": "Someone",
  "id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
}
@app.route("/movies/<movieid>", methods=["POST"])
def create_movie(movieid):
    req = request.get_json()

    for movie in movies:
        if str(movie["id"]) == str(movieid):
            return make_response(jsonify({"error":"movie ID already exists"}),409)

    movies.append(req)
    res = make_response(jsonify({"message":"movie added"}),200)
    return res

6.2. PUT modifier la note d’un film

Ici nous ajouter un point d’entrée pour une requête de type PUT permettant de modifier la note d’un film.

@app.route("/movies/<movieid>/<rate>", methods=["PUT"])
def update_movie_rating(movieid, rate):
    for movie in movies:
        if str(movie["id"]) == str(movieid):
            movie["rating"] = int(rate)
            res = make_response(jsonify(movie),200)
            return res

    res = make_response(jsonify({"error":"movie ID not found"}),201)
    return res

6.3. DELETE un film

Ici nous supprimons un film.

@app.route("/movies/<movieid>", methods=["DELETE"])
def del_movie(movieid):
    for movie in movies:
        if str(movie["id"]) == str(movieid):
            movies.remove(movie)
            return make_response(jsonify(movie),200)

    res = make_response(jsonify({"error":"movie ID not found"}),400)
    return res