fastapi
fastapi copied to clipboard
Not rendering multi-select in API doc while using Pydantic model
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.
Commit to Help
- [X] I commit to help with one of those options 👆
Example Code
import typing
from fastapi import FastAPI, Query, Depends
from pydantic import BaseModel
from enum import Enum
app = FastAPI()
class Status(str, Enum):
SUCCESS = "SUCCESS"
REFUND = "REFUND"
FAIL = "FAIL"
CANCEL = "CANCEL"
@app.get("/working-example/")
async def root_with_normal_query_params(status_in: typing.List[Status] = Query(...)):
return {"status_inputs": status_in}
class StatusModel(BaseModel):
status_in: typing.List[Status]
@app.get("/not-working-example/")
async def root_with_pydantic(status_inputs: StatusModel = Depends()):
return {"status_inputs": status_inputs}
Description
The API docs are not generating the multi-select option while using the Pydantic model for the query/request parser.
without using Pydantic model
with using Pydantic model
Operating System
Linux
Operating System Details
No response
FastAPI Version
0.70.1
Python Version
Python 3.9.11
Additional Context
No response
Hmm that is interesting. I reproduced it but I didn't had the time to follow the logic of FastAPI when it resolves the dependencies and the connection of that logic to the buildup of the openapi.json
. I'll have another try tomorrow if I have the time, it shouldn't behave like that.
Any thoughts @JarroVGIT :)
@JarroVGIT @jerinpetergeorge Guys, I think you missed the point
Working example you provided is QUERY parameters
And non working example you provided is REQUEST BODY (not QUERY) and in docs it provides Example value
and Schema
If you click on Schema
you will see all your choices from Status
enum
Additional note: HTTP GET can not go with request body
I figured that as well, but unfortunately that does not seem to be the case. The docs are saying us that when defining a param with a default value of Query()
, it will be treated as a query list. The docs are also saying you can set this on a Pydantic model, and that is where the bug is. See the following example as illustration:
from re import S
import typing
from fastapi import FastAPI, Query, Depends, File
from pydantic import BaseModel, Field
from enum import Enum
app = FastAPI()
#----------------------------------------------
class Status(Enum):
SUCCESS = "SUCCESS"
REFUND = "REFUND"
FAIL = "FAIL"
CANCEL = "CANCEL"
#class with list of Enum:
class StatusModelWithListEnum(BaseModel):
status_in: list[Status] = Query()
#class with Enum:
class StatusModelNoList(BaseModel):
status_in: Status = Query(...)
#class with list of str:
class OtherModelWithListStr(BaseModel):
some_param: list[str] = Query(...)
#class with str:
class OtherModelNoList(BaseModel):
some_param: list[str] = Query(...)
#----------------------------------------------
#The following 4 endpoints do not show correctly in the docs,
#as they are referencing pydantic models.
@app.get("/statusmodel-list")
async def statusmodel_list(par: StatusModelWithListEnum = Depends()):
return {"status_inputs": par}
#shows as request body.
@app.get("/statusmodel-nolist")
async def statusmodel_nolist(par: StatusModelNoList = Depends()):
return {"status_inputs": par}
#shows as query
@app.get("/stringmodel-list")
async def stringmodel_list(par: OtherModelWithListStr = Depends()):
return {"status_inputs": par}
#shows as request body
@app.get("/stringmodel-nolist")
async def stringmodel_nolist(par: StatusModelNoList = Depends()):
return {"status_inputs": par}
#shows as query
@app.get("/no-model-string-list")
async def no_model_string_list(par: list[str] = Query()):
return {"status_inputs": par}
#as per docs, shows as query!
#----------------------------------------------
#The following two endpoint will show correctly.
@app.get("/status-list-direct-in-param")
async def status_list_in_param(par: list[Status] = Query()):
return {"status_inputs": par}
@app.get("/status-nolist")
async def stringmodel_nolist(par: Status = Query()):
return {"status_inputs": par}
#----------------------------------------------
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000, )
The issue is real, it shouldn't behave this way.
@JarroVGIT yeah, I get it now!
I think using Pydantic shows everything in body. I may be wrong.
Is there a fix in the works for this issue?
Not AFAIK
@jerinpetergeorge Seems like using a Dataclass instead can get around this issue if you're able to make the swap.
@jerinpetergeorge Seems like using a Dataclass instead can get around this issue if you're able to make the swap.
How? Can you give an example?
I tried these - it didn't seem to work
-
from dataclasses import dataclass @dataclass class StatusModel: status_in: typing.List[Status]
-
from dataclasses import dataclass @dataclass class StatusModel(BaseModel): status_in: typing.List[Status]
Moreover, the reason why I choose the Pydantic is that the superpower to parse the inputs - without the Pydantic model/feature I would be much disappointed :disappointed: @bshea5
You can use dataclass
from Pydantic.
import typing
from enum import Enum
from fastapi import FastAPI, Query, Depends
from pydantic.dataclasses import dataclass
app = FastAPI()
class Status(str, Enum):
SUCCESS = "SUCCESS"
REFUND = "REFUND"
FAIL = "FAIL"
CANCEL = "CANCEL"
@app.get("/working-example/")
async def root_with_normal_query_params(status_in: typing.List[Status] = Query(...)):
return {"status_inputs": status_in}
@dataclass
class StatusModel:
status_in: list[Status] = Query(...)
@app.get("/not-working-example/") # it now works
async def root_with_pydantic(status_inputs: StatusModel = Depends()):
return {"status_inputs": status_inputs}
You can use
dataclass
from Pydantic.import typing from enum import Enum from fastapi import FastAPI, Query, Depends from pydantic.dataclasses import dataclass app = FastAPI() class Status(str, Enum): SUCCESS = "SUCCESS" REFUND = "REFUND" FAIL = "FAIL" CANCEL = "CANCEL" @app.get("/working-example/") async def root_with_normal_query_params(status_in: typing.List[Status] = Query(...)): return {"status_inputs": status_in} @dataclass class StatusModel: status_in: list[Status] = Query(...) @app.get("/not-working-example/") # it now works async def root_with_pydantic(status_inputs: StatusModel = Depends()): return {"status_inputs": status_inputs}
This is nice!!!
Anyway, I'd love to see FastAPI supports the BaseModel as well (as per the issue described above)
@iudeen : Nice solution! Do you know, by any chance, why this is working while the 'normal' approach is not? What makes pydantic.dataclasses.dataclass
so different than pydantic.BaseModel
? I've seen numerous issues where the issue is founded in the difference between the two (and sometimes also between Python's own dataclasses.dataclass
and I am pretty confused on what the differences are and how they are handled differently by FastAPI. This is a prime example of that.
@JarroVGIT this discussion is about the difference between the two..
@jerinpetergeorge if you are happy with the solution, you can close this and open a ISSUE/BUG to discuss on why the "normal" approach is not working, and probably finding a better solution!
@jerinpetergeorge if you are happy with the solution, you can close this and open a ISSUE/BUG to discuss on why the "normal" approach is not working, and probably finding a better solution!
I'm kind of okay with the solution since we're still not sure "why" FastAPI doesn't support it - is that on purpose? Or a bug?
Also, I hope this current GH issue considers a bug - or I wrote like that in the first place, which makes me think that it is better not to close this GH issue at the moment.
I would be super happy if someone could change the label of this issue from https://github.com/tiangolo/fastapi/labels/question to https://github.com/tiangolo/fastapi/labels/bug