Issue
I am trying to upload an image but FastAPI is coming back with an error I can’t figure out.
If I leave out the "file: UploadFile = File(...)
" from the function definition, it works correctly. But when I add the file
to the function definition, then it throws the error.
Here is the complete code.
@router.post('/', response_model=schemas.PostItem, status_code=status.HTTP_201_CREATED)
def create(request: schemas.Item, file: UploadFile = File(...), db: Session = Depends(get_db)):
new_item = models.Item(
name=request.name,
price=request.price,
user_id=1,
)
print(file.filename)
db.add(new_item)
db.commit()
db.refresh(new_item)
return new_item
The Item
Pydantic model is just
class Item(BaseModel):
name: str
price: float
The error is:
Code 422 Error: Unprocessable Entity
{
"detail": [
{
"loc": [
"body",
"request",
"name"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"request",
"price"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
Solution
The problem is that your route is expecting 2 types of request body:
-
request: schemas.Item
- This is expecting POSTing an
application/json
body - See the Request Body section of the FastAPI docs: "Read the body of the request as JSON"
- This is expecting POSTing an
-
file: UploadFile = File(...)
- This is expecting POSTing a
multipart/form-data
- See the Request Files section of the FastAPI docs: "FastAPI will make sure to read that data from the right place instead of JSON. …when the form includes files, it is encoded as
multipart/form-data
"
- This is expecting POSTing a
That will not work as that breaks not just FastAPI, but general HTTP protocols. FastAPI mentions this in a warning when using File
:
You can declare multiple
File
andForm
parameters in a path operation, but you can’t also declareBody
fields that you expect to receive as JSON, as the request will have the body encoded usingmultipart/form-data
instead ofapplication/json
.This is not a limitation of FastAPI, it’s part of the HTTP protocol.
The common solutions, as discussed in Posting a File and Associated Data to a RESTful WebService preferably as JSON, is to either:
- Break the API into 2 POST requests: 1 for the file, 1 for the metadata
- Send it all in 1
multipart/form-data
Fortunately, FastAPI supports solution 2, combining both your Item
model and uploading a file into 1 multipart/form-data
. See the section on Request Forms and Files:
Use
File
andForm
together when you need to receive data and files in the same request.
Here’s your modified route (I removed db
as that’s irrelevant to the problem):
class Item(BaseModel):
name: str
price: float
class PostItem(BaseModel):
name: str
@router.post('/', response_model=PostItem, status_code=status.HTTP_201_CREATED)
def create(
# Here we expect parameters for each field of the model
name: str = Form(...),
price: float = Form(...),
# Here we expect an uploaded file
file: UploadFile = File(...),
):
new_item = Item(name=name, price=price)
print(new_item)
print(file.filename)
return new_item
The Swagger docs present it as 1 form
…and you should be able now to send both Item
params and the file in one request.
If you don’t like splitting your Item
model into separate parameters (it would indeed be annoying for models with many fields), see this Q&A on fastapi form data with pydantic model.
Here’s the modified code where Item
is changed to ItemForm
to support accepting its fields as Form
values instead of JSON:
class ItemForm(BaseModel):
name: str
price: float
@classmethod
def as_form(cls, name: str = Form(...), price: float = Form(...)) -> 'ItemForm':
return cls(name=name, price=price)
class PostItem(BaseModel):
name: str
@router.post('/', response_model=PostItem, status_code=status.HTTP_201_CREATED)
def create(
item: ItemForm = Depends(ItemForm.as_form),
file: UploadFile = File(...),
):
new_item = Item(name=item.name, price=item.price)
print(new_item)
print(file.filename)
return new_item
The Swagger UI should still be the same (all the Item
fields and the file upload all in one form).
For this:
If I leave out the "
file: UploadFile = File(...)
" from the function definition, it works correctly
It’s not important to focus on this, but it worked because removing File
turned the expected request body back to an application/json
type, so the JSON body would work.
Finally, as a side note, I strongly suggest NOT using request
as a parameter name for your route. Aside from being vague (everything is a request), it could conflict with FastAPI’s request: Request
parameter when using the Request object directly.
Answered By – Gino Mempin
This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0