Les différences entre les Frameworks Flask et FastAPI
Flask est aujourd’hui le choix historique et standard pour le développement d’API dans les projets développés en Python. Toutefois, le Framework FastAPI est une très bonne alternative qui apporte des éléments supplémentaires qui peuvent permettre d’améliorer ces API.
Par rapport à Flask, FastAPI apporte en plus la validation automatique des données, la génération automatique de documentation et de meilleures pratiques intégrées telles que les schémas pydantic et le typage python, ce qui en fait un choix judicieux pour de futurs projets.
Or, grâce à la proximité sémantique et fonctionnelle entre Flask et FastAPI, il est très facile de migrer un projet de l’un à l’autre, et cela, pour un effort minimum de quelques heures.
Dans cet article, je vous propose de découvrir les principales différences d’implémentations des cas d’utilisation courants dans Flask et FastAPI.
Installation
Flask et FastAPI sont disponibles sur PyPI.
Flask
pip install flask
FastAPI
pip install fastapi uvicorn
Hello World
Flask
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
app.run()
Exécution du serveur :
python app.py
Attention, Flask utilise par défaut le port 5000.
FastAPI
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
uvicorn.run(app)
Exécution :
python app.py
Attention, FastAPI utilise par défaut le port 8000.
À noter que FastAPI diffère la diffusion du service via un serveur prêt pour la production appelé uvicorn ce qui explique qu’il est généralement installé avec FastAPI.
Serveur de production
Flask
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
app.run()
Pour un serveur de production, gunicorn est un choix courant avec Flask.
gunicorn app:app
FastAPI
# app.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
uvicorn.run(app)
Pour rappel, FastAPI diffère la diffusion du service à uvicorn. Nous pouvons donc démarrer le serveur uvicorn comme ceci :
uvicorn app:app
Vous pouvez aussi le démarrer en mode “rechargement à chaud” (permet de recharger automatiquement le serveur à chaque modification de votre code) :
uvicorn app:app --reload
De plus, vous pouvez également modifier le port :
uvicorn app:app --port 5000
Et il est possible de contrôler le nombre de workers de la façon suivante :
uvicorn app:app --workers 2
Vous pouvez également utiliser gunicorn pour gérer uvicorn en utilisant la commande suivante :
gunicorn -k uvicorn.workers.UvicornWorker app:app
Enfin, tous les flags standards utilisés par gunicorn tels que le nombre de workers (-w) fonctionnent.
Méthodes HTTP
Flask
@app.route('/', methods=['POST'])
def example():
...
FastAPI
@app.post('/')
def example():
...
À la différence de Flask, FastAPI vous propose des décorateurs individuels pour chaque méthode HTTP :
@app.get('/')
@app.put('/')
@app.patch('/')
@app.delete('/')
Variables d'URL
Nous voulons obtenir l’ID d’un utilisateur à partir d’une URL, par exemple /users/1, puis renvoyer l’ID utilisateur à l’utilisateur.
Flask
@app.route('/users/')
def get_user_details(user_id):
return {'user_id': user_id}
FastAPI
Dans FastAPI, nous utilisons des indicateurs de type en Python pour spécifier tous les types de données. Par exemple, nous spécifions ici que user_id doit être un entier. La variable dans le chemin de l’URL est également spécifiée de la même manière que les f-strings.
@app.get('/users/{user_id}')
def get_user_details(user_id: int):
return {'user_id': user_id}
Chaînes de requête
Nous voulons permettre à l’utilisateur de spécifier un terme de recherche en utilisant une chaîne de requête ?q=abc dans l’URL.
Flask
from flask import request
@app.route('/search')
def search():
query = request.args.get('q')
return {'query': query}
FastAPI
@app.get('/search')
def search(q: str):
return {'query': q}
Demande JSON POST
Prenons comme exemple l’envoi d’un texte en majuscule que nous affecterons à une clé d’un JSON et dont la réponse de l’API sera le même JSON avec le texte en minuscule.
# Request
{"text": "HELLO"}
# Response
{"text": "hello"}
Flask
from flask import request
@app.route('/lowercase', methods=['POST'])
def lower_case():
text = request.json.get('text')
return {'text': text.lower()}
FastAPI
Si vous répliquez simplement la fonctionnalité de Flask, vous pouvez le faire de la façon suivante avec FastAPI :
@app.post('/lowercase')
def lower_case(json_data: dict):
text = json_data.get('text')
return {'text': text.lower()}
Mais c’est là que FastAPI introduit un nouveau concept de création de schéma pydantic qui correspond aux données JSON reçues. Nous pouvons factoriser l’exemple ci-dessus en utilisant pydantic de la façon suivante :
from pydantic import BaseModel
class Sentence(BaseModel):
text: str
@app.post('/lowercase')
def lower_case(sentence: Sentence):
return {'text': sentence.text.lower()}
Comme on le voit, au lieu d’obtenir un dictionnaire, les données JSON sont converties en un objet du schéma Sentence. En tant que tel, nous pouvons accéder aux données à l’aide d’attributs tels que sentence.text. Cela permet également une validation automatique des types de données. Si l’utilisateur tente d’envoyer des données autres qu’une chaîne, il recevra une erreur de validation générée automatiquement.
Exemple de demande non valide :
{"text": None}
Et sa réponse automatique :
{
"detail": [
{
"loc": [
"body",
"text"
],
"msg": "none is not an allowed value",
"type": "type_error.none.not_allowed"
}
]
}
Téléchargement de fichiers
Créons une API pour renvoyer le nom du fichier téléchargé. La clé utilisée lors du téléchargement du fichier sera file.
Flask
Flask permet d’accéder au fichier téléchargé via l’objet request.
from flask import Flask, request
app = Flask(__name__)
@app.route('/upload', methods=['POST'])
def upload_file():
file = request.files.get('file')
return {'name': file.filename}
FastAPI
FastAPI utilise le paramètre de la fonction pour spécifier la clé du fichier.
from fastapi import FastAPI, UploadFile, File
app = FastAPI()
@app.post('/upload')
def upload_file(file: UploadFile = File(...)):
return {'name': file.filename}
Soumission de formulaire
Nous voulons accéder à un champ de formulaire de texte défini comme indiqué ci-dessous et faire écho à la valeur.
<input name='city' type='text'>
Flask
L’accès aux champs du formulaire se fait via l’objet request.
from flask import Flask, request
app = Flask(__name__)
@app.route('/submit', methods=['POST'])
def echo():
city = request.form.get('city')
return {'city': city}
FastAPI
Nous utilisons le paramètre de fonction pour définir la clé et le type de données pour le champ de formulaire.
from fastapi import FastAPI, Form
app = FastAPI()
@app.post('/submit')
def echo(city: str = Form(...)):
return {'city': city}
Nous pouvons également rendre le champ de formulaire facultatif comme indiqué ci-dessous :
from typing import Optional
@app.post('/submit')
def echo(city: Optional[str] = Form(None)):
return {'city': city}
De même, nous pouvons définir une valeur par défaut pour le champ de formulaire comme indiqué ci-dessous :
@app.post('/submit')
def echo(city: Optional[str] = Form('Paris')):
return {'city': city}
Cookies
Nous voulons accéder à un cookie appelé name à partir de la demande.
Flask
Flask permet d’accéder aux cookies via l’objet request.
from flask import Flask, request
app = Flask(__name__)
@app.route('/profile')
def profile():
name = request.cookies.get('name')
return {'name': name}
FastAPI
Nous utilisons un paramètre pour définir la clé du cookie.
from fastapi import FastAPI, Cookie
app = FastAPI()
@app.get('/profile')
def profile(name = Cookie(None)):
return {'name': name}
Vues modulaires
Nous voulons décomposer les vues d’un seul fichier app.py en fichiers séparés :
- app.py
- views
- user.py
Flask
Dans Flask, nous utilisons un concept appelé blueprints pour gérer cela. Nous allons d’abord créer un blueprint pour la vue utilisateur comme suit :
# views/user.py
from flask import Blueprint
user_blueprint = Blueprint('user', __name__)
@user_blueprint.route('/users')
def list_users():
return {'users': ['a', 'b', 'c']}
Ensuite, cette vue est enregistrée dans le fichier principal app.py :
# app.py
from flask import Flask
from views.user import user_blueprint
app = Flask(__name__)
app.register_blueprint(user_blueprint)
FastAPI
Dans FastAPI, l’équivalent d’un blueprint est appelé un routeur. Tout d’abord, nous créons un routeur utilisateur comme :
# routers/user.py
from fastapi import APIRouter
router = APIRouter()
@router.get('/users')
def list_users():
return {'users': ['a', 'b', 'c']}
Ensuite, nous attachons ce routeur à l’objet principal de l’application :
# app.py
from fastapi import FastAPI
from routers import user
app = FastAPI()
app.include_router(user.router)
La validation des données
Flask
Flask ne fournit aucune fonctionnalité de validation des données d’entrée prête à l’emploi. Il est courant d’écrire une logique de validation personnalisée ou d’utiliser des bibliothèques telles que marshmallow, pydantic ou d’utiliser la librairie flask-pydantic pour étendre les fonctionnalités de Flask.
FastAPI
FastAPI intègre nativement pydantic et permet la validation des données en utilisant simplement une combinaison de schéma pydantic et d’indices de type Python.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
age: int
@app.post('/users')
def save_user(user: User):
return {'name': user.name,
'age': user.age}
Ce code effectuera une validation automatique pour s’assurer que name est une chaîne de caractères et qu’age est un entier. Si un autre type de données est envoyé, il génère automatiquement une erreur de validation avec un message pertinent.
Voici quelques exemples de schéma pydantic pour les cas d’utilisation courants :
Exemple 1, la paire clé-valeur :
{
"name": "Isaac",
"age": 60
}
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
Exemple 2, la collection d’objets :
{
"series": ["GOT", "Dark", "Mr. Robot"]
}
from pydantic import BaseModel
from typing import List
class Metadata(BaseModel):
series: List[str]
Exemple 3, les objets imbriqués :
{
"users": [
{
"name": "xyz",
"age": 25
},
{
"name": "abc",
"age": 30
}
],
"group": "Group A"
}
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
class UserGroup(BaseModel):
users: list[User]
group: str
Documentation automatique
Flask
Flask ne fournit aucune fonctionnalité intégrée pour la génération de documentation. Il existe des extensions telles que flask-swagger ou flask-restful pour combler cette lacune.
FastAPI
FastAPI génère automatiquement un point de terminaison pour la documentation Swagger UI sur /docs et une documentation de référence sur /redoc.
Par exemple, supposons que nous ayons une vue simple telle que donnée ci-dessous et qui fait écho à ce qu’un utilisateur rechercherait.
from fastapi import FastAPI
app = FastAPI()
@app.get('/search')
def search(q: str):
return {'query': q}
Si vous exécutez le serveur et accédez au point de terminaison http://127.0.0.1:8000/docs, vous accèderez à une documentation Swagger générée automatiquement.
Vous pouvez essayer de manière interactive l’API à partir du navigateur lui-même.
En plus de Swagger, si vous accédez au point de terminaison http://127.0.0.01:8000/redoc, vous obtiendrez une documentation de référence générée automatiquement. Il y a des informations sur les paramètres, le format de la demande, le format de la réponse et les codes d’état.
Partage de Ressources Inter-Origines (CORS)
Flask
Flask ne fournit pas de support CORS prêt à l’emploi. Nous devons utiliser une extension telle que flask-cors pour configurer CORS comme indiqué ci-dessous.
from flask import Flask
from flask_cors import CORS
app_ = Flask(__name__)
CORS(app_)
FastAPI
FastAPI fournit un middleware intégré pour gérer CORS. Nous montrons un exemple de CORS ci-dessous où nous autorisons toute origine à accéder à nos API.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Le mot de la fin
FastAPI est une excellente alternative à Flask pour créer des API robustes avec les meilleures pratiques intégrées.
Si vous ne souhaitez pas forcément migrer vers FastAPI, vous pouvez aussi élargir les fonctionnalités de Flask avec d’autres librairies tel que Flask-RESTPlus qui vous apportera la documentation auto-générée ou Flask-Pydantic qui vous permettra d’intégrer des fonctionnalités similaires à celles proposées nativement par FastAPI au sein de vos projets Flask.
Enfin, n’hésitez pas à vous référer à la documentation pour en savoir plus et si dans votre galère, vous voulez un peu de soutiens, n’hésitez pas à me contacter.