fastapi
fastapi copied to clipboard
Required Body argument with None is not supported
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
from fastapi import FastAPI, Body
from typing import Optional
app = FastAPI()
@app.post("/")
def post_data(data: Union[str, None] = Body(...)):
pass
Description
According to the documentation, it is possible to handle a None
value for required argument provided by the user. First of all, the example in the documentation doesn't really make sense, since a Python None
(i.e. a JSON null
) can not be provided in the query a GET request, but has to be sent through the body of a POST/PUT request.
Nevertheless, this is my use case: I have a POST endpoint, with a required (without default) argument, but I want the user to be able to send a null
value.
There has been a issue (https://github.com/tiangolo/fastapi/issues/1661) for 2 years exactly about this point. It has been claimed, that this is solved in 0.85.1, but I claim that this is not. I report here below more or less the same MWE:
- store the above code in a file example.py
- start the fastapi server with
uvicorn example:app
- send a POST request with:
$ curl -X POST "http://localhost:8000/" -d 'null' -H "Content-Type: application/json"
{"detail":[{"loc":["body"],"msg":"field required","type":"value_error.missing"}]}
Expected would be:
$ curl -X POST "http://localhost:8000/" -d 'null' -H "Content-Type: application/json"
null
I tried with other (more recent) versions of fastapi without more success.
Operating System
Linux
Operating System Details
No response
FastAPI Version
0.85.1
Python Version
Python 3.9.2
Additional Context
No response
I think this might be a bug. Related function is request_body_to_args()
:
https://github.com/tiangolo/fastapi/blob/efc12c5cdbede6922a033a5234c70cb22c4d204a/fastapi/dependencies/utils.py#L624-L627
https://github.com/tiangolo/fastapi/blob/efc12c5cdbede6922a033a5234c70cb22c4d204a/fastapi/dependencies/utils.py#L645
https://github.com/tiangolo/fastapi/blob/efc12c5cdbede6922a033a5234c70cb22c4d204a/fastapi/dependencies/utils.py#L657-L658
https://github.com/tiangolo/fastapi/blob/efc12c5cdbede6922a033a5234c70cb22c4d204a/fastapi/dependencies/utils.py#L665-L667
With your MWE, there are two cases the body will be loaded as Python's None
:
- sending an empty body
- sending raw JSON
null
.
In both cases, request_body_to_args
got received_body = None
, hence the value
in code quoted above is None
. However, according to your declaration, field.required
is True
. So, an error is added by FastAPI before Pydantic validates the model, which happens later in
https://github.com/tiangolo/fastapi/blob/efc12c5cdbede6922a033a5234c70cb22c4d204a/fastapi/dependencies/utils.py#L696
So it is FastAPI that raise this problem but not Pydantic.
Now, the question is how to distinguish empty body and null
as body. The only way I can come up with is to pass the bytes of received_body
, instead of its loaded version to the function request_body_to_args()
quoted above. Or we should pass some extra data to distinguish nothing and null.
A workaround that slightly changes your API, is to use an Object as the requested body instead of the bare value. More precisely, use:
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, Field
class Payload(BaseModel):
data: Union[str, None] = Field(...)
app = FastAPI()
@app.post("/")
def post_data(q: Payload):
pass
Then compare (with httpie
):
http POST :8000 --raw='{"data": null}'
http POST :8000 --raw='{"data": "hello"}'
http POST :8000 --raw='{}'
http POST :8000 --raw=''
There is some imperfection on the Pydantic side, the JSON schema that Pydantic generates for the above Payload is
Payload.schema(): {
'title': 'Payload',
'type': 'object',
'properties': {
'data': {
'title': 'Data',
'type': 'string',
},
},
'required': ['data'],
} (dict) len=4
so the json
{
"data": null
}
does not pass the validation of that JSON schema.
@roynico how can I reproduce this?