Tutoriel sur Flask
1. Objectif
Nous allons ici implémenter un micro-service Movie
avec Flask
.
Important
|
Merci de vous référer à la page sur la préparation et les installations nécessaires à cette UE avant de continuer. |
Cloner (ou téléchargez) le contenu du repository git
suivant : https://github.com/IMTA-FIL/UE-AD-A1-REST
Le contenu de ce repository sera votre espace de travail pour ce tutoriel et votre TP sur REST. Il contient un répertoire par service à implémenter dans le TP, dont un répertoire pour le service Movie
qui nous intéresse ici. Chaque répertoire de service contient les données json
qui lui sont associées.
Le repository, comme indiqué dans le guide d’installation, contient également un fichier requirements.txt
racine utile pour installer les dépendances nécessaires (à savoir ici les bibliothèques Flask
et requests
). Il contient enfin les fichiers nécessaires à la construction de l’environnement Docker qui sera abordée plus tard dans cette UE (à savoir un fichier docker-compose.yaml
et dans chaque répertoire un fichier Dockerfile
).
2. Les bases de notre service Movie
2.1. Création et lecture des données JSON
Le code ci-dessous permet la lecture et l’écriture du fichier JSON databases/movie.json
. L’objet movies
récupéré est obtenu en lisant la clé "movies" et est donc un array
. La fonction d’écriture prend en entrée un array
de films à écrire dans le fichier json
. Pour conserver la structure initiale, on place cette liste de film dans un dictionnaire avec une unique clé "movies".
with open('{}/databases/movies.json'.format("."), "r") as jsf:
movies = json.load(jsf)["movies"]
def write(movies):
with open('{}/databases/movies.json'.format("."), 'w') as f:
full = {}
full['movies']=movies
json.dump(full, f)
2.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 de la requête HTTP /
. Le point d’entrée 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.
app = Flask(__name__)
PORT = 3200
HOST = '0.0.0.0'
@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(host=HOST, port=PORT)
Exécuter ce code (placez vous dans le bon répertoire movie
) avec au choix python movie.py
(attention de bien utiliser python3
si vous avez plusieurs versions) ou pymon movie.py
(pour éviter de devoir arrêter et relancer le service à chaque modification). Accéder à la page indiquée dans la sortie de l’exécution sur votre navigateur (http://127.0.0.1:3200
).
3. Tester les points d’entrée
On peut utiliser le navigateur web pour tester les points d’entrée de type GET, cela marche très bien. Dans la pratique toutefois et pour tester facilement des points d’entrée de tout type il est préférable d’utiliser un outil de tests d’API comme par exemple Insomnia, Postman, Bruno, etc.
Pour cette UE je vous conseille d’installer Insomnia ou Postman qui sont compatibles avec les 3 types d’API que nous allons développer (REST, GraphQL, gRPC).
4. Points d’entrée pour obtenir des données
Pour chacun de ces points d’entrée testez le résultat à chaque fois !
4.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).
@app.route("/json", methods=['GET'])
def get_json():
res = make_response(jsonify(movies), 200)
return res
4.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
courant 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"}),500)
Vous voyez ici que l’ID est indiqué dans l’adresse directement. C’est la méthode la plus classique en REST pour donner une information à la requête. A noter que l’on peut complexifier l’adresse comme on le souhaite, par exemple si on souhaite donner plus d’éléments pour la requête /entry_point/<val1>/<val2>/<val3>
.
4.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.
À noter que request.args
retourne un werkzeug.MultiDict
de Flask formé de
cette façon [(key,value),(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"}),500)
else:
res = make_response(jsonify(json),200)
return res
Vous voyez ici que le titre ne fait plus partie de l’adresse d’accès du point d’entrée. Le titre sera donné comme un argument de la requête HTML. C’est une autre façon de procéder qui utilise plus les mécanismes HTTP mais moins la logique REST.
Pour faire une requête et tester ce point d’entrée il faut donc passer un paramètre. Cela est facile à faire avec Postman
(ou ses équivalents). Si vous souhaitez tester le point d’entrée à la main dans votre navigateur il faudra adopter la notation suivante : URL/moviesbytitle&title=TITRE
.
5. Points d’entrée pour modifier, ajouter et supprimer des données
5.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 add_movie(movieid):
req = request.get_json()
for movie in movies:
if str(movie["id"]) == str(movieid):
print(movie["id"])
print(movieid)
return make_response(jsonify({"error":"movie ID already exists"}),500)
movies.append(req)
write(movies)
res = make_response(jsonify({"message":"movie added"}),200)
return res
Pour tester une requête de type POST avec un body de requête, nous ne pouvons plus utiliser le navigateur web. Il est de toute façon préférable maintenant de passer complètement sur un outil comme Postman
.
Warning
|
Attention de bien utiliser le bon type de requêtes dans Postman vous aurez sinon des erreurs !
|
Tip
|
Le fichier json écrit est mal formaté. Les IDE permettent un formattage automatique. Par exemple sur VSCode c’est CTRL+k CTRL+f (Shift+option+F sous macos).
|
5.2. PUT modifier la note d’un film
Ici nous ajoutons 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"] = rate
res = make_response(jsonify(movie),200)
write(movies)
return res
res = make_response(jsonify({"error":"movie ID not found"}),500)
return res
On voit ici que le même point d’entrée peut être utilisé pour différents types de requêtes ! Ici on réutilise le point d’entrée movies
.
5.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)
write(movies)
return make_response(jsonify(movie),200)
res = make_response(jsonify({"error":"movie ID not found"}),500)
return res