1

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'}]}
Chris
  • 18,724
  • 6
  • 46
  • 80
user1583016
  • 79
  • 1
  • 11

1 Answers1

1

Option 1

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'
data = ['https://www.website.com/', 'https://www.anotherwebsite.com']
r = requests.post(url, json=data)
print(r.status_code)
print(r.json())

Option 2

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'
data = {'urls': ['https://www.website.com/', 'https://www.anotherwebsite.com']}
r = requests.post(url, json=data)
print(r.status_code)
print(r.json())
Chris
  • 18,724
  • 6
  • 46
  • 80
  • Thanks for the detailed response, the Pydantic model is the most intuitive approach to me, any idea why I'm getting a 422 response when using both of your examples? The error message is {'detail': [{'loc': ['body'], 'msg': 'value is not a valid list', 'type': 'type_error.list'}]} – user1583016 Nov 08 '22 at 20:28
  • Revisited this today and was able to determine that my sentry.io implementation is the cause of the problem. Still need to file the bug with sentry. – user1583016 Nov 25 '22 at 22:53