Maison >développement back-end >Tutoriel Python >Comment accepter à la fois les téléchargements JSON et de fichiers dans une requête FastAPI POST ?

Comment accepter à la fois les téléchargements JSON et de fichiers dans une requête FastAPI POST ?

Mary-Kate Olsen
Mary-Kate Olsenoriginal
2024-12-19 10:35:12504parcourir

How to accept both JSON and file uploads in a FastAPI POST request?

Comment ajouter à la fois un fichier et un corps JSON dans une requête FastAPI POST ?

Plus précisément, je souhaite que l'exemple ci-dessous fonctionne :

from typing import List
from pydantic import BaseModel
from fastapi import FastAPI, UploadFile, File


app = FastAPI()


class DataConfiguration(BaseModel):
    textColumnNames: List[str]
    idColumn: str


@app.post("/data")
async def data(dataConfiguration: DataConfiguration,
               csvFile: UploadFile = File(...)):
    pass
    # read requested id and text columns from csvFile

Si ce n'est pas la bonne méthode pour une requête POST, veuillez me faire savoir comment sélectionner les colonnes requises à partir d'un fichier CSV téléchargé dans FastAPI.

Selon la documentation FastAPI :

Vous pouvez déclarer plusieurs paramètres de formulaire dans une opération de chemin, mais vous ne pouvez pas également déclarer les champs Body que vous vous attendez à recevoir au format JSON, car le corps de la requête sera codé en utilisant application/x-www-form-urlencoded au lieu de application/json (lorsque le formulaire comprend des fichiers, il est codé comme multipart/form-data).

Ceci n'est pas une limitation de FastAPI, cela fait partie du protocole HTTP.

Notez que vous devez d'abord avoir python-multipart installé, si vous ne l'avez pas déjà fait, puisque les fichiers téléchargés sont envoyés sous forme de « données de formulaire ». Par exemple :

pip install python-multipart

Il convient également de noter que dans les exemples ci-dessous, les points de terminaison sont définis avec une définition normale, mais vous pouvez également utiliser une définition asynchrone (en fonction de vos besoins). Veuillez consulter cette réponse pour plus de détails sur def vs async def dans FastAPI.

Si vous cherchez comment télécharger les deux fichiers et une liste de dictionnaires/données JSON, veuillez jetez un œil à cette réponse, ainsi qu'à cette réponse et cette réponse pour des exemples de travail (qui sont principalement basés sur certaines des méthodes suivantes).

Méthode 1

Comme décrit ici, on peut définir des fichiers et des formulaires en même temps en utilisant File et Form. Vous trouverez ci-dessous un exemple fonctionnel. Si vous disposez d'un grand nombre de paramètres et souhaitez les définir séparément du point de terminaison, veuillez consulter cette réponse sur la façon de déclarer les champs de formulaire de décalcomanie, en utilisant plutôt une classe de dépendance ou un modèle Pydantic.

app.py

from fastapi import Form, File, UploadFile, Request, FastAPI
from typing import List
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")


@app.post("/submit")
def submit(
    name: str = Form(...),
    point: float = Form(...),
    is_accepted: bool = Form(...),
    files: List[UploadFile] = File(...),
):
    return {
        "JSON Payload": {
            "name": name,
            "point": point,
            "is_accepted": is_accepted,
        },
        "Filenames": [file.filename for file in files],
    }


@app.get("/", response_class=HTMLResponse)
def main(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

Vous pouvez tester l'exemple ci-dessus en accédant au modèle ci-dessous à l'adresse http://127.0.0.1:8000. Si votre modèle n'inclut aucun code Jinja, vous pouvez également renvoyer une simple réponse HTML.

templates/index.html

<!DOCTYPE html>
<html>
   <body>
      <form method="post" action="http://127.0.0.1:8000/submit"  enctype="multipart/form-data">
         name : <input type="text" name="name" value="foo"><br>
         point : <input type="text" name="point" value=0.134><br>
         is_accepted : <input type="text" name="is_accepted" value=True><br>    
         <label for="files">Choose file(s) to upload</label>
         <input type="file">

Vous pouvez également tester cet exemple en utilisant les autodocs interactifs OpenAPI/Swagger UI dans /docs, par exemple : http://127.0.0.1:8000/docs, ou en utilisant des requêtes Python, comme indiqué ci-dessous :

test.py

from typing import List
from pydantic import BaseModel
from fastapi import FastAPI, UploadFile, File


app = FastAPI()


class DataConfiguration(BaseModel):
    textColumnNames: List[str]
    idColumn: str


@app.post("/data")
async def data(dataConfiguration: DataConfiguration,
               csvFile: UploadFile = File(...)):
    pass
    # read requested id and text columns from csvFile

Méthode 2

On pourrait également utiliser des modèles Pydantic, ainsi que des dépendances, pour informer le point de terminaison /submit (dans l'exemple ci-dessous) que la variable paramétrée base dépend de la classe Base. Veuillez noter que cette méthode attend les données de base sous forme de paramètres de requête (et non de corps), qui sont ensuite validés et convertis dans le modèle Pydantic (dans ce cas, c'est-à-dire le modèle de base). Veuillez également noter qu'il ne faut jamais transmettre de données sensibles via la chaîne de requête, car cela pose un risque de sécurité sérieux. Veuillez consulter cette réponse pour plus de détails sur ce sujet.

Lors du retour d'une instance de modèle Pydantic (dans ce cas, c'est la base) à partir d'un point de terminaison FastAPI (par exemple, le point de terminaison /submit ci-dessous), il serait automatiquement converti en chaîne JSON, en arrière-plan, à l'aide de jsonable_encoder, comme expliqué en détail dans cette réponse. Cependant, si vous souhaitez convertir vous-même le modèle en chaîne JSON dans le point de terminaison, vous pouvez utiliser model_dump_json() de Pydantic (dans Pydantic V2), par exemple base.model_dump_json(), et renvoyer directement une réponse personnalisée, comme expliqué dans la réponse liée plus tôt ; ainsi, évitant l'utilisation de jsonable_encoder. Sinon, afin de convertir vous-même le modèle en dict, vous pouvez utiliser model_dump() de Pydantic (dans Pydantic V2), par exemple base.model_dump(), ou simplement dict(base) (notez que renvoyer un objet dict depuis un point de terminaison, FastAPI utiliserait toujours le jsonable_encoder, en coulisses, comme expliqué dans la réponse liée ci-dessus). Vous pouvez également consulter cette réponse pour connaître les méthodes et la documentation Pydantic pertinentes.

En plus d'utiliser un modèle Pydantic pour les paramètres de requête, on peut également définir des paramètres de requête directement dans le point de terminaison, comme démontré dans cette réponse. , ainsi que cette réponse et cette réponse.

Outre les paramètres de requête de base, le point de terminaison /submit suivant attend également des fichiers codés en multipart/form-data dans la requête body.

app.py

pip install python-multipart

Encore une fois, vous pouvez le tester en utilisant le modèle ci-dessous, qui, cette fois, utilise JavaScript pour modifier l'attribut d'action de l'élément de formulaire, afin de transmettre les données du formulaire en tant que paramètres de requête à l'URL au lieu de données de formulaire.

templates/index.html

from fastapi import Form, File, UploadFile, Request, FastAPI
from typing import List
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")


@app.post("/submit")
def submit(
    name: str = Form(...),
    point: float = Form(...),
    is_accepted: bool = Form(...),
    files: List[UploadFile] = File(...),
):
    return {
        "JSON Payload": {
            "name": name,
            "point": point,
            "is_accepted": is_accepted,
        },
        "Filenames": [file.filename for file in files],
    }


@app.get("/", response_class=HTMLResponse)
def main(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn