Here's a self-contained, minimal, reproducible, example with my use case:
from fastapi import FastAPI, File, Form
from pydantic import BaseModel
app = FastAPI()
class Address(BaseModel):
first_line: str
post_code: str
class User(BaseModel):
first_name: str
last_name: str
address: Address
@app.post("/users/")
async def create_user(avatar: bytes = File(...), user: User = Form(...)):
return {
"file_size": len(avatar),
"user": user,
}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=8000, debug=True)
/docs. Click try it out on the APIexecute422 Unprocessable Entity{
"detail": [
{
"loc": [
"body",
"user"
],
"msg": "value is not a valid dict",
"type": "type_error.dict"
}
]
.
pip
fastapi==0.61.2
python-multipart==0.0.5
requests==2.24.0
starlette==0.13.6
uvicorn==0.12.1
...
Python version: 3.8.6
I found #842 has similar issue, this one is more a mixed use of files and object in a form data.
Not sure if I messed something up, but thanks for any advice.
The problem is using a pydantic model with form.
I recommend what I call Mause Form Solution: https://github.com/tiangolo/fastapi/issues/1989#issuecomment-684170738
Thanks @Kludex for pointing out Mause's solution. That is a really nice solution. I will close this issue.
Hopping in this as I have a slightly different use (but essentially the same issue)
I've been using a solution similar to Mause's solution. I use an as_form decorator to add a method rather than converting the class. Both these workarounds fall short of working for nested dictionaries.
from typing import Dict
from fastapi import FastAPI, Depends, Form
from pydantic import BaseModel
app = FastAPI()
@as_form
class Item(BaseModel):
name: str
another: str
opts: Dict[str, int] = {}
@app.post('/test', response_model=Item)
def endpoint(item: Item = Depends(Item.as_form)):
return item
Both styles of docs show the opts field as a dict, with the redoc style even showing that property name is an integer
I don't believe my method would work for nested fields I'm afraid.
Perhaps you could provide a sample of the form body you are posting and the source of your as_form function in a new issue?
This works for me, here is a full as is example: Surprisingly it works with example as well. Worth mentioning that I have a slightly different signature of the view function.
from typing import List
from fastapi import FastAPI, File, Form
from pydantic import BaseModel
app = FastAPI()
def as_form(cls):
cls.__signature__ = cls.__signature__.replace(
parameters=[arg.replace(default=Form(...)) for arg in cls.__signature__.parameters.values()]
)
return cls
class Address(BaseModel):
first_line: str
postcode: str
@as_form
class User(BaseModel):
first_name: str
last_name: str
address: List[Address]
class Config:
schema_extra = {
'example': {
'first_name': 'John',
'last_name': 'Lewis',
'address': [
{'first_line': '5', 'postcode': '123456'},
{'first_line': '6', 'postcode': '123456'},
],
}
}
@app.post("/users/")
async def create_user(avatar: bytes = File(None), user: User = Form(...)):
return {
"file_size": avatar and len(avatar),
"user": user,
}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=8001, debug=True)