Débloquez la puissance de FastAPI pour des téléversements de fichiers de formulaires multipart efficaces. Ce guide complet couvre les meilleures pratiques et la gestion des erreurs.
Maîtriser les téléversements de fichiers avec FastAPI : un guide approfondi sur le traitement des formulaires multipart
Dans les applications web modernes, la capacité à gérer les téléversements de fichiers est une exigence fondamentale. Qu'il s'agisse d'utilisateurs soumettant des photos de profil, des documents à traiter ou des médias à partager, des mécanismes de téléversement de fichiers robustes et efficaces sont essentiels. FastAPI, un framework web Python haute performance, excelle dans ce domaine, offrant des moyens rationalisés de gérer les données de formulaires multipart, qui sont la norme pour l'envoi de fichiers via HTTP. Ce guide complet vous guidera à travers les subtilités des téléversements de fichiers FastAPI, de la mise en œuvre de base aux considérations avancées, vous assurant de pouvoir créer en toute confiance des API puissantes et évolutives pour un public mondial.
Comprendre les données de formulaires multipart
Avant de plonger dans l'implémentation de FastAPI, il est essentiel de comprendre ce que sont les données de formulaires multipart. Lorsqu'un navigateur web soumet un formulaire contenant des fichiers, il utilise généralement l'attribut enctype="multipart/form-data". Ce type d'encodage décompose la soumission du formulaire en plusieurs parties, chacune avec son propre type de contenu et ses informations de disposition. Cela permet la transmission de différents types de données dans une seule requête HTTP, y compris des champs de texte, des champs non textuels et des fichiers binaires.
Chaque partie d'une requête multipart est constituée de :
- En-tête Content-Disposition : Spécifie le nom du champ de formulaire (
name) et, pour les fichiers, le nom de fichier d'origine (filename). - En-tĂŞte Content-Type : Indique le type MIME de la partie (par exemple,
text/plain,image/jpeg). - Corps : Les données réelles pour cette partie.
L'approche de FastAPI concernant les téléversements de fichiers
FastAPI exploite la bibliothèque standard de Python et s'intègre de manière transparente à Pydantic pour la validation des données. Pour les téléversements de fichiers, il utilise le type UploadFile du module fastapi. Cette classe fournit une interface pratique et sûre pour accéder aux données de fichiers téléversés.
Implémentation de base du téléversement de fichiers
Commençons par un exemple simple de la façon de créer un point de terminaison dans FastAPI qui accepte un seul téléversement de fichier. Nous utiliserons la fonction File de fastapi pour déclarer le paramètre de fichier.
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: UploadFile):
return {"filename": file.filename, "content_type": file.content_type}
Dans cet exemple :
- Nous importons
FastAPI,FileetUploadFile. - Le point de terminaison
/files/est défini comme une requêtePOST. - Le paramètre
fileest annoté avecUploadFile, ce qui signifie qu'il attend un téléversement de fichier. - À l'intérieur de la fonction de point de terminaison, nous pouvons accéder aux propriétés du fichier téléversé telles que
filenameetcontent_type.
Lorsqu'un client envoie une requête POST à /files/ avec un fichier joint (généralement via un formulaire avec enctype="multipart/form-data"), FastAPI gérera automatiquement l'analyse et fournira un objet UploadFile. Vous pouvez ensuite interagir avec cet objet.
Enregistrement des fichiers téléversés
Souvent, vous devrez enregistrer le fichier téléversé sur le disque ou traiter son contenu. L'objet UploadFile fournit des méthodes pour cela :
read(): Lit tout le contenu du fichier en mémoire sous forme d'octets. Utilisez ceci pour les fichiers plus petits.write(content: bytes): Écrit des octets dans le fichier.seek(offset: int): Modifie la position actuelle du fichier.close(): Ferme le fichier.
Il est important de gérer les opérations sur les fichiers de manière asynchrone, en particulier lorsqu'il s'agit de fichiers volumineux ou de tâches liées aux E/S. UploadFile de FastAPI prend en charge les opérations asynchrones.
from fastapi import FastAPI, File, UploadFile
import shutil
app = FastAPI()
@app.post("/files/save/")
async def save_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"file '{file.filename}' saved at '{file_location}'"}
Dans cet exemple amélioré :
- Nous utilisons
File(...)pour indiquer que ce paramètre est requis. - Nous spécifions un chemin local où le fichier sera enregistré. Assurez-vous que le répertoire
uploadsexiste. - Nous ouvrons le fichier de destination en mode d'écriture binaire (`"wb+"`).
- Nous lisons de manière asynchrone le contenu du fichier téléversé à l'aide de
await file.read(), puis nous l'écrivons dans le fichier local.
Remarque : La lecture de l'intégralité du fichier en mémoire avec await file.read() peut être problématique pour les très gros fichiers. Pour de tels scénarios, envisagez de diffuser le contenu du fichier en continu.
Diffusion en continu du contenu du fichier
Pour les fichiers volumineux, la lecture de l'intégralité du contenu en mémoire peut entraîner une consommation excessive de mémoire et des erreurs potentielles de mémoire insuffisante. Une approche plus économe en mémoire consiste à diffuser le fichier en continu, bloc par bloc. La fonction shutil.copyfileobj est excellente pour cela, mais nous devons l'adapter aux opérations asynchrones.
from fastapi import FastAPI, File, UploadFile
import aiofiles # Install using: pip install aiofiles
app = FastAPI()
@app.post("/files/stream/")
async def stream_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
async with aiofiles.open(file_location, "wb") as out_file:
content = await file.read()
await out_file.write(content)
return {"info": f"file '{file.filename}' streamed and saved at '{file_location}'"}
Avec aiofiles, nous pouvons diffuser efficacement le contenu du fichier téléversé vers un fichier de destination sans charger l'intégralité du fichier en mémoire en une seule fois. Le await file.read() dans ce contexte lit toujours l'intégralité du fichier, mais aiofiles gère l'écriture plus efficacement. Pour une véritable diffusion en continu bloc par bloc avec UploadFile, vous devez généralement itérer sur await file.read(chunk_size), mais aiofiles.open et await out_file.write(content) est un modèle courant et performant pour l'enregistrement.
Une approche de diffusion en continu plus explicite utilisant le chunking :
from fastapi import FastAPI, File, UploadFile
import aiofiles
app = FastAPI()
CHUNK_SIZE = 1024 * 1024 # Taille du chunk de 1 Mo
@app.post("/files/chunked_stream/")
async def chunked_stream_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
async with aiofiles.open(file_location, "wb") as out_file:
while content := await file.read(CHUNK_SIZE):
await out_file.write(content)
return {"info": f"file '{file.filename}' chunked streamed and saved at '{file_location}'"}
Ce point de terminaison `chunked_stream_file` lit le fichier par blocs de 1 Mo et écrit chaque bloc dans le fichier de sortie. C'est la manière la plus économe en mémoire de gérer des fichiers potentiellement très volumineux.
Gestion des téléversements de fichiers multiples
Les applications web nécessitent souvent que les utilisateurs téléversent plusieurs fichiers simultanément. FastAPI rend cela simple.
Téléversement d'une liste de fichiers
Vous pouvez accepter une liste de fichiers en annotant votre paramètre avec une liste de UploadFile.
from fastapi import FastAPI, File, UploadFile, Form
from typing import List
app = FastAPI()
@app.post("/files/multiple/")
async def create_multiple_files(
files: List[UploadFile] = File(...)
):
results = []
for file in files:
# Traiter chaque fichier, par exemple, l'enregistrer
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
results.append({"filename": file.filename, "content_type": file.content_type, "saved_at": file_location})
return {"files_processed": results}
Dans ce scénario, le client doit envoyer plusieurs parties avec le même nom de champ de formulaire (par exemple, `files`). FastAPI les collectera dans une liste Python d'objets UploadFile.
Mélange de fichiers et d'autres données de formulaire
Il est courant d'avoir des formulaires qui contiennent à la fois des champs de fichier et des champs de texte standard. FastAPI gère cela en vous permettant de déclarer d'autres paramètres à l'aide d'annotations de type standard, ainsi que Form pour les champs de formulaire qui ne sont pas des fichiers.
from fastapi import FastAPI, File, UploadFile, Form
from typing import List
app = FastAPI()
@app.post("/files/mixed/")
async def upload_mixed_data(
description: str = Form(...),
files: List[UploadFile] = File(...) # Accepte plusieurs fichiers avec le nom 'files'
):
results = []
for file in files:
# Traiter chaque fichier
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
results.append({"filename": file.filename, "content_type": file.content_type, "saved_at": file_location})
return {
"description": description,
"files_processed": results
}
Lorsque vous utilisez des outils tels que Swagger UI ou Postman, vous spécifierez la description comme un champ de formulaire standard, puis vous ajouterez plusieurs parties pour le champ files, chacune avec son type de contenu défini sur le type d'image/document approprié.
Fonctionnalités avancées et meilleures pratiques
Au-delà de la gestion de base des fichiers, plusieurs fonctionnalités avancées et meilleures pratiques sont essentielles pour créer des API de téléversement de fichiers robustes.
Limites de taille de fichier
Autoriser les téléversements de fichiers illimités peut entraîner des attaques par déni de service ou une consommation excessive de ressources. Bien que FastAPI lui-même n'applique pas de limites strictes par défaut au niveau du framework, vous devez implémenter des vérifications :
- Au niveau de l'application : Vérifiez la taille du fichier après sa réception, mais avant de le traiter ou de l'enregistrer.
- Au niveau du serveur web/proxy : Configurez votre serveur web (par exemple, Nginx, Uvicorn avec des workers) pour rejeter les requêtes dépassant une certaine taille de charge utile.
Exemple de vérification de la taille au niveau de l'application :
from fastapi import FastAPI, File, UploadFile, HTTPException
app = FastAPI()
MAX_FILE_SIZE_MB = 10
MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024
@app.post("/files/limited_size/")
async def upload_with_size_limit(file: UploadFile = File(...)):
if len(await file.read()) > MAX_FILE_SIZE_BYTES:
raise HTTPException(status_code=400, detail=f"File is too large. Maximum size is {MAX_FILE_SIZE_MB}MB.")
# Réinitialiser le pointeur de fichier pour relire le contenu
await file.seek(0)
# Procéder à l'enregistrement ou au traitement du fichier
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"File '{file.filename}' uploaded successfully."}
Important : Après avoir lu le fichier pour vérifier sa taille, vous devez utiliser await file.seek(0) pour réinitialiser le pointeur de fichier au début si vous avez l'intention de relire son contenu (par exemple, pour l'enregistrer).
Types de fichiers autorisés (types MIME)
Restreindre les téléversements à des types de fichiers spécifiques améliore la sécurité et garantit l'intégrité des données. Vous pouvez vérifier l'attribut content_type de l'objet UploadFile.
from fastapi import FastAPI, File, UploadFile, HTTPException
app = FastAPI()
ALLOWED_FILE_TYPES = {"image/jpeg", "image/png", "application/pdf"}
@app.post("/files/restricted_types/")
async def upload_restricted_types(file: UploadFile = File(...)):
if file.content_type not in ALLOWED_FILE_TYPES:
raise HTTPException(status_code=400, detail=f"Unsupported file type: {file.content_type}. Allowed types are: {', '.join(ALLOWED_FILE_TYPES)}")
# Procéder à l'enregistrement ou au traitement du fichier
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"File '{file.filename}' uploaded successfully and is of an allowed type."}
Pour une vérification de type plus robuste, en particulier pour les images, vous pouvez envisager d'utiliser des bibliothèques comme Pillow pour inspecter le contenu réel du fichier, car les types MIME peuvent parfois être usurpés.
Gestion des erreurs et commentaires des utilisateurs
Fournissez des messages d'erreur clairs et exploitables à l'utilisateur. Utilisez HTTPException de FastAPI pour les réponses d'erreur HTTP standard.
- Fichier introuvable/manquant : Si un paramètre de fichier requis n'est pas envoyé.
- Taille de fichier dépassée : Comme indiqué dans l'exemple de limite de taille.
- Type de fichier non valide : Comme indiqué dans l'exemple de restriction de type.
- Erreurs de serveur : Pour les problèmes lors de l'enregistrement ou du traitement des fichiers (par exemple, disque plein, erreurs d'autorisation).
Considérations de sécurité
Les téléversements de fichiers introduisent des risques de sécurité :
- Fichiers malveillants : Téléversement de fichiers exécutables (
.exe,.sh) ou de scripts déguisés en d'autres types de fichiers. Validez toujours les types de fichiers et envisagez d'analyser les fichiers téléversés à la recherche de logiciels malveillants. - Traversal de chemin : Nettoyez les noms de fichiers pour empêcher les attaquants de téléverser des fichiers dans des répertoires non prévus (par exemple, en utilisant des noms de fichiers comme
../../etc/passwd).UploadFilede FastAPI gère la désinfection de base des noms de fichiers, mais une prudence supplémentaire est sage. - Déni de service : Implémentez des limites de taille de fichier et potentiellement une limitation du débit sur les points de terminaison de téléversement.
- Cross-Site Scripting (XSS) : Si vous affichez des noms de fichiers ou du contenu de fichier directement sur une page web, assurez-vous qu'ils sont correctement échappés pour éviter les attaques XSS.
Meilleure pratique : Stockez les fichiers téléversés en dehors de la racine du document de votre serveur web et servez-les via un point de terminaison dédié avec des contrôles d'accès appropriés, ou utilisez un réseau de diffusion de contenu (CDN).
Utilisation des modèles Pydantic avec les téléversements de fichiers
Bien que UploadFile soit le type principal pour les fichiers, vous pouvez intégrer les téléversements de fichiers dans les modèles Pydantic pour des structures de données plus complexes. Cependant, les champs de téléversement de fichiers directs dans les modèles Pydantic standard ne sont pas pris en charge nativement pour les formulaires multipart. Au lieu de cela, vous recevez généralement le fichier en tant que paramètre distinct, puis vous le traitez potentiellement dans un format qui peut être stocké ou validé par un modèle Pydantic.
Un modèle courant consiste à avoir un modèle Pydantic pour les métadonnées, puis à recevoir le fichier séparément :
from fastapi import FastAPI, File, UploadFile, Form
from pydantic import BaseModel
from typing import Optional
class UploadMetadata(BaseModel):
title: str
description: Optional[str] = None
app = FastAPI()
@app.post("/files/model_metadata/")
async def upload_with_metadata(
metadata: str = Form(...), # Recevoir les métadonnées sous forme de chaîne JSON
file: UploadFile = File(...)
):
import json
try:
metadata_obj = UploadMetadata(**json.loads(metadata))
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON format for metadata")
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error parsing metadata: {e}")
# Maintenant, vous avez metadata_obj et file
# Procéder à l'enregistrement du fichier et à l'utilisation des métadonnées
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {
"message": "File uploaded successfully with metadata",
"metadata": metadata_obj,
"filename": file.filename
}
Dans ce modèle, le client envoie les métadonnées sous forme de chaîne JSON dans un champ de formulaire (par exemple, metadata) et le fichier sous forme de partie multipart distincte. Le serveur analyse ensuite la chaîne JSON dans un objet Pydantic.
Téléversements de fichiers volumineux et chunking
Pour les fichiers très volumineux (par exemple, des gigaoctets), même la diffusion en continu peut atteindre les limites du serveur web ou du côté client. Une technique plus avancée est le téléversement en chunks, où le client divise le fichier en petits morceaux et les téléverse en séquence ou en parallèle. Le serveur réassemble ensuite ces chunks. Cela nécessite généralement une logique côté client personnalisée et un point de terminaison de serveur conçu pour gérer la gestion des chunks (par exemple, l'identification des chunks, le stockage temporaire et l'assemblage final).
Bien que FastAPI ne fournisse pas de prise en charge intégrée pour les téléversements en chunks initiés par le client, vous pouvez implémenter cette logique dans vos points de terminaison FastAPI. Cela implique la création de points de terminaison qui :
- Reçoivent des chunks de fichiers individuels.
- Stockent ces chunks temporairement, éventuellement avec des métadonnées indiquant leur ordre et le nombre total de chunks.
- Fournissent un point de terminaison ou un mécanisme pour signaler que tous les chunks ont été téléversés, ce qui déclenche le processus de réassemblage.
Il s'agit d'une tâche plus complexe qui implique souvent des bibliothèques JavaScript côté client.
Considérations relatives à l'internationalisation et à la mondialisation
Lors de la création d'API pour un public mondial, les téléversements de fichiers nécessitent une attention particulière :
- Noms de fichiers : Les utilisateurs du monde entier peuvent utiliser des caractères non ASCII dans les noms de fichiers (par exemple, des accents, des idéogrammes). Assurez-vous que votre système gère et stocke correctement ces noms de fichiers. L'encodage UTF-8 est généralement standard, mais une compatibilité approfondie peut nécessiter un encodage/décodage et une désinfection minutieux.
- Unités de taille de fichier : Bien que Mo et Go soient courants, soyez attentif à la façon dont les utilisateurs perçoivent les tailles de fichier. Il est important d'afficher les limites d'une manière conviviale.
- Types de contenu : Les utilisateurs peuvent téléverser des fichiers avec des types MIME moins courants. Assurez-vous que votre liste de types autorisés est suffisamment complète ou flexible pour votre cas d'utilisation.
- Réglementations régionales : Soyez conscient des lois et réglementations sur la résidence des données dans différents pays. Le stockage des fichiers téléversés peut nécessiter le respect de ces règles.
- Interface utilisateur : L'interface côté client pour le téléversement de fichiers doit être intuitive et prendre en charge la langue et les paramètres régionaux de l'utilisateur.
Outils et bibliothèques pour les tests
Le test des points de terminaison de téléversement de fichiers est crucial. Voici quelques outils courants :
- Swagger UI (Documentation API interactive) : FastAPI génère automatiquement la documentation Swagger UI. Vous pouvez tester directement les téléversements de fichiers à partir de l'interface du navigateur. Recherchez le champ de saisie de fichier et cliquez sur le bouton "Choisir un fichier".
- Postman : Un outil populaire de développement et de test d'API. Pour envoyer une requête de téléversement de fichier :
- Définissez la méthode de requête sur POST.
- Entrez l'URL de votre point de terminaison API.
- Accédez à l'onglet "Body".
- Sélectionnez "form-data" comme type.
- Dans les paires clé-valeur, entrez le nom de votre paramètre de fichier (par exemple,
file). - Modifiez le type de "Text" Ă "File".
- Cliquez sur "Choisir des fichiers" pour sélectionner un fichier à partir de votre système local.
- Si vous avez d'autres champs de formulaire, ajoutez-les de la même manière, en conservant leur type comme "Text".
- Envoyez la requĂŞte.
- cURL : Un outil en ligne de commande pour effectuer des requĂŞtes HTTP.
- Pour un seul fichier :
curl -X POST -F "file=@/path/to/your/local/file.txt" http://localhost:8000/files/ - Pour plusieurs fichiers :
curl -X POST -F "files=@/path/to/file1.txt" -F "files=@/path/to/file2.png" http://localhost:8000/files/multiple/ - Pour des données mixtes :
curl -X POST -F "description=My description" -F "files=@/path/to/file.txt" http://localhost:8000/files/mixed/ - Bibliothèque `requests` de Python : Pour les tests programmatiques.
import requests
url = "http://localhost:8000/files/save/"
files = {'file': open('/path/to/your/local/file.txt', 'rb')}
response = requests.post(url, files=files)
print(response.json())
# Pour plusieurs fichiers
url_multiple = "http://localhost:8000/files/multiple/"
files_multiple = {
'files': [('file1.txt', open('/path/to/file1.txt', 'rb')),
('image.png', open('/path/to/image.png', 'rb'))]
}
response_multiple = requests.post(url_multiple, files=files_multiple)
print(response_multiple.json())
# Pour des données mixtes
url_mixed = "http://localhost:8000/files/mixed/"
data = {'description': 'Test description'}
files_mixed = {'files': open('/path/to/another_file.txt', 'rb')}
response_mixed = requests.post(url_mixed, data=data, files=files_mixed)
print(response_mixed.json())
Conclusion
FastAPI fournit un moyen puissant, efficace et intuitif de gérer les téléversements de fichiers multipart. En tirant parti du type UploadFile et de la programmation asynchrone, les développeurs peuvent créer des API robustes qui intègrent de manière transparente les fonctionnalités de gestion des fichiers. N'oubliez pas de donner la priorité à la sécurité, d'implémenter une gestion des erreurs appropriée et de tenir compte des besoins d'une base d'utilisateurs mondiale en abordant des aspects tels que l'encodage des noms de fichiers et la conformité réglementaire.
Que vous construisiez un simple service de partage d'images ou une plateforme complexe de traitement de documents, la maîtrise des fonctionnalités de téléversement de fichiers de FastAPI sera un atout important. Continuez à explorer ses capacités, à implémenter les meilleures pratiques et à offrir des expériences utilisateur exceptionnelles à votre public international.