Using the FastAPI documentation, I'm attempting to send a POST
request to an endpoint that takes a JSON object with a list
as input:
{
"urls":[
"https://www.website.com/",
"https://www.anotherwebsite.com"
]
}
Simplified reproducible example app code:
from fastapi import FastAPI
app = FastAPI()
@app.post("/checkUrls")
def checkUrls(urls: list[str]):
return urls
#alternatively (given that I expect each url to be unique)
@app.post("/checkUrlsSet")
def checkUrlsSet(urls: set[str]):
return urls
When I test the endpoint using the Python requests library like this:
import requests
rListTest = requests.post("http://127.0.0.1:8000/checkUrls", json = {"urls" : ["https://www.website.com/", "https://www.anotherwebsite.com"]})
print(rListTest.status_code)
print(rListTest.json())
rSetTest = requests.post("http://127.0.0.1:8000/checkUrlsSet", json = {"urls" : ["https://www.website.com/", "https://www.anotherwebsite.com"]})
print(rSetTest.status_code)
print(rSetTest.json())
in both cases, I get the a 400 status code error and the response is:
{'detail': 'There was an error parsing the body'}
I'm using python 3.10 on a linux ubuntu 22.04 vm with an ampere ARM processor if it matters...
I've also tried the following app code:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UrlsList(BaseModel):
urls: list[str]
class UrlsSet(BaseModel):
urls: set[str]
@app.post("/checkUrls")
async def checkUrls(urls: UrlsList):
return urls
@app.post("/checkUrlsSet")
async def checkUrlsSet(urls: UrlsSet):
return urls
which returns a 422 Unprocessable Entity
error:
{'detail': [{'loc': ['body'], 'msg': 'value is not a valid list', 'type': 'type_error.list'}]}
{'detail': [{'loc': ['body'], 'msg': 'value is not a valid set', 'type': 'type_error.set'}]}
The reason of the error you get is explained here and here (see this answer as well). In brief, when defining a single body parameter like urls: list[str]
or urls: list[str] = Body()
, FastAPI will expect a request body like this:
[
"string1", "string2"
]
You can confirm the above by using the Swagger UI autodocs at /docs
. Hence, your Python requests client should look like this:
url = 'http://127.0.0.1:8000/checkUrls'
payload = ['https://www.website.com/', 'https://www.anotherwebsite.com']
r = requests.post(url, json=payload)
print(r.status_code)
print(r.json())
If you wanted to have the user adding the urls
key in the JSON string, then you would need to explicitly declare your parameter with Body()
and use the special Body parameter embed
(as described in the linked answers provided above). Example:
from fastapi import Body
@app.post('/checkUrls')
def checkUrls(urls: list[str] = Body(..., embed=True)):
return urls
or, use a Pydantic model (just like in the last code snippet of your question, and as demonstrated in the linked answers earlier):
class UrlsList(BaseModel):
urls: list[str]
@app.post('/checkUrls')
async def checkUrls(urls: UrlsList):
return urls
The API endpoint would then expect a body like this:
{
"urls":[
"string1",
"string2"
]
}
Hence, the client side should then look like this:
url = 'http://127.0.0.1:8000/checkUrls'
payload = {'urls': ['https://www.website.com/', 'https://www.anotherwebsite.com']}
r = requests.post(url, json=payload)
print(r.status_code)
print(r.json())
For posting files together with JSON list, please have a look here, as well as here and here.