Fastapi: Using UploadFile and Pydantic model in one request

Created on 27 Oct 2020  ·  17Comments  ·  Source: tiangolo/fastapi

First check

  • [x] I added a very descriptive title to this issue.
  • [x] I used the GitHub search to find a similar issue and didn't find it.
  • [x] I searched the FastAPI documentation, with the integrated search.
  • [x] I already searched in Google "How to X in FastAPI" and didn't find any information.
  • [x] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [x] I already checked if it is not related to FastAPI but to Pydantic.
  • [x] I already checked if it is not related to FastAPI but to Swagger UI.
  • [x] I already checked if it is not related to FastAPI but to ReDoc.
  • [x] After submitting this, I commit to one of:

    • Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.

    • I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.

    • Implement a Pull Request for a confirmed bug.

Example

Here's a self-contained, minimal, reproducible, example with my use case:

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

app = FastAPI()


class Properties(BaseModel):
    language: str = None
    author: str = None


@app.post("/uploadfile/", status_code=201)
async def create_upload_file(properties: Properties, file: UploadFile = File(...)):
    return {"filename": file.filename, 'properties': properties}

Description

  • Open the browser /docs and call the endpoint /uploadfile.
  • It returns error 422 Validation Error.
  • But I expected it to return code 201.

Environment

  • OS: Linux (Fedora)
  • FastAPI Version: 0.61.1
  • Python version: 3.8.6

Additional context

Immediately I apologize, maybe I missed something and / or did not understand and should not have bothered you. I need this function in order to unload a poster for it along with information in the form of a Pydantic model. I issued a patch, but everyone ignores it

question

All 17 comments

based on docs

from fastapi import FastAPI, File, UploadFile,Form
from pydantic import BaseModel

app = FastAPI()


class Properties(BaseModel):
    language: str = None
    author: str = None


@app.post("/uploadfile/", status_code=201)
async def create_upload_file(language: str = Form(...),author: str = Form(...),file: UploadFile = File(...)):
    return {"filename": file.filename, 'properties': Properties(language=language,author=author)}

based on docs

from fastapi import FastAPI, File, UploadFile,Form
from pydantic import BaseModel

app = FastAPI()


class Properties(BaseModel):
    language: str = None
    author: str = None


@app.post("/uploadfile/", status_code=201)
async def create_upload_file(language: str = Form(...),author: str = Form(...),file: UploadFile = File(...)):
    return {"filename": file.filename, 'properties': Properties(language=language,author=author)}

Hello. This option does not suit me, since the model in my project has many fields and I do not want crutches in my project, because of this I released a patch so that you can transfer the Pydantic Model to FormData. But thanks for trying to help

Then you can try to use dependency injection. Just make class with required fields (better use dataclass).
But imo it's bad practice to upload file and body fields simultaneously

Then you can try to use dependency injection. Just make class with required fields (better use dataclass).

Again, this will be a crutch, I already have a ready-made Pydantic model and a handler for it. Now I just want to add a picture to it. FastAPI does not support the Pydantic model through FormData. Why can't you just add support for this? I already solved this problem with 4 lines of code but they are ignored. But thanks for trying to help

Again, this will be a crutch, I already have a ready-made Pydantic model and a handler for it. Now I just want to add a picture to it. FastAPI does not support the Pydantic model through FormData. Why can't you just add support for this? I already solved this problem with 4 lines of code but they are ignored. But thanks for trying to help

In fact, it's working with Pydantic model, I had such code snippet in my pet project:

@api.post('/')
def add_review(
    body: Model = Body(...),
    image_files: Optional[List[UploadFile]] = File(None, media_type='image/jpeg'),
):
    ...

BUT! when I tried to send such data I had to insert body json as string inside form-data (that's RFC limitations: https://tools.ietf.org/html/rfc1867)

In fact, it's working with Pydantic model, I had such code snippet in my pet project:

@api.post('/')
def add_review(
    body: Model = Body(...),
    image_files: Optional[List[UploadFile]] = File(None, media_type='image/jpeg'),
):
    ...

BUT! when I tried to send such data I had to insert body json as string inside form-data (that's RFC limitations: https://tools.ietf.org/html/rfc1867)

The code you provided doesn't work ("Model" in your example inherits the Pydantic model right?) (At least with Swagger). Error 422 (Swagger still submits the Pydantic model via form data and this cannot be handled by FastAPI). And if this does not work in Swagger, then I cannot make tests for my project (pytest)

@app.post("/uploadfile", status_code=201)
async def create_upload_file(properties: Properties = Form(...), file: UploadFile = File(...)):
    return {"filename": file.filename, 'properties': properties}

We left on the wrong topic. I want to discuss why I cannot use FormData

Oups, sorry, I forgot I made custom validator to transform str to json for Model:

class Model(BaseModel):
    foo: int
    bar: str

    @classmethod
    def __get_validators__(cls):
        yield cls.validate_to_json

    @classmethod
    def validate_to_json(cls, value):
        if isinstance(value, str):
            return cls(**json.loads(value))
        return value

Oups, sorry, I forgot I made custom validator to transform str to json for Model:

class Model(BaseModel):
    foo: int
    bar: str

    @classmethod
    def __get_validators__(cls):
        yield cls.validate_to_json

    @classmethod
    def validate_to_json(cls, value):
        if isinstance(value, str):
            return cls(**json.loads(value))
        return value

I've seen a similar solution, but you don't think it is a workaround. Maybe it's time to really fix the problem? You can try using my fork with the problem fixed

We left on the wrong topic. I want to discuss why I cannot use FormData

I can't give you answer, but I think it's because Form data is string and cannot be parsed easily. For example, you cannot use Pydantic model in Query too, only for Body params

Мы ушли не по той теме. Я хочу обсудить, почему я не могу использовать FormData

Я не могу дать вам ответ, но думаю, это потому, что данные формы являются строковыми и не могут быть легко проанализированы. Например, вы не можете использовать модель Pydantic в Query, только для параметров Body.

Open it

Imo it's still workaround and even dirtier than custom validator. Form Data used for text and files commonly, not for json data and making it as default behavior could be misleading and make more bugs.
As I said, look at Form like at Query, they share identical behavior - some small atomic data
Still, it could be my stubbornness and I'd like to hear another one opinion

Imo it's still workaround and even dirtier than custom validator. Form Data used for text and files commonly, not for json data and making it as default behavior could be misleading and make more bugs.
As I said, look at Form like at Query, they share identical behavior - some small atomic data
Still, it could be my stubbornness and I'd like to hear another one opinion

Perhaps you are right, we will wait for opinions on this from other participants.

A small digression. I am currently switching from Django there it uses Form to pass new record data. Why the same cannot be done in FastAPI. It's just a question

Hi,

if I understand you well you can use UploadFile and Pydantic model in one request using Dependencies.
More detailed information here: https://fastapi.tiangolo.com/tutorial/dependencies/
Sample code:

from fastapi import File, UploadFile, Depends

class User(BaseModel):

    username: str
    password: str

@app.post('/user')
async def upload(user: User = Depends(), file: UploadFile = File(...)):

  data_db = user.dict()
  print(data_db)

Hi,

if I understand you well you can use UploadFile and Pydantic model in one request using Dependencies.
More detailed information here: https://fastapi.tiangolo.com/tutorial/dependencies/
Sample code:

from fastapi import File, UploadFile, Depends

class User(BaseModel):

    username: str
    password: str

@app.post('/user')
async def upload(user: User = Depends(), file: UploadFile = File(...)):

  data_db = user.dict()
  print(data_db)

Hello. Thanks for trying to help, but that's not what I want. My pidantic model is quite large and it's not very good to use GET parameters for it. I think it is better to use custom validator in pydantic model and pass data through FormData. I think it's time to close this topic

Oups, sorry, I forgot I made custom validator to transform str to json for Model:

class Model(BaseModel):
    foo: int
    bar: str

    @classmethod
    def __get_validators__(cls):
        yield cls.validate_to_json

    @classmethod
    def validate_to_json(cls, value):
        if isinstance(value, str):
            return cls(**json.loads(value))
        return value

This is the solution to the problem. But it would be better if you can use the pydantic model through FormData without additional code. There is a ready-made patch for this problem

Was this page helpful?
0 / 5 - 0 ratings