POST request to FastAPI endpoint produces error 422: "value is not a valid dict"
The Problem
I have a Python script that sends a POST request to an API I’ve built using FastAPI. The POST request fails with error code 422 and the following details:
{
"detail": [
{
"loc": [ "body" ],
"msg": "value is not a valid dict",
"type": "type_error.dict"
}
]
}
My client script looks like this:
import requests, pandas, json
dataframe = pandas.DataFrame({
"Firstname": ["Joaquin"],
"Surname": ["Phoenix"],
"Age": [49]
})
df_json = dataframe.to_json(orient="records")
response = requests.post('http://localhost:8000/create-user/',
json=json.loads(df_json),
headers={"Content-Type": "application/json"})
print(response.json())
The relevant API endpoint and Pydantic model look like this:
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
class User(BaseModel):
firstname: str
surname: str
age: int
@app.post("/create-user/")
async def create_users(user: User):
# ... user creation operations ...
return {"Message": "User created."}
What’s going wrong? Is it a problem with my client script or my API code?
The Solution
The problem is in the client script. The requests.post function expects a dictionary to be passed to the json keyword argument, which it will automatically convert to JSON.
This script first converts a Pandas DataFrame to a JSON string (dataframe.to_json(orient="records")), which looks like this:
[{"Firstname":"Joaquin", "Surname": "Phoenix","Age": 49}]
The script then uses json.loads to convert it back into a Python object. Because of the argument orient="records" in the original conversion, the object created here will be a list rather than a dictionary, leading to the 422 error when it is submitted.
We can fix this problem by converting the DataFrame to a dictionary instead of a JSON string:
import requests, pandas
dataframe = pandas.DataFrame({
"Firstname": ["Joaquin"],
"Surname": ["Phoenix"],
"Age": [49]
})
userdata = dataframe.to_dict(orient="records")
# will be a list: [{"Firstname":"Joaquin", "Surname": "Phoenix","Age": 49}]
userdata = userdata[0] # take the first item from the list
# will be a dictionary: {"Firstname":"Joaquin", "Surname": "Phoenix","Age": 49}
prediction = requests.post('http://localhost:8000/create-users/',
json=userdata,
headers={"Content-Type": "application/json"})
This altered code should produce the expected results.
Considered "not bad" by 4 million developers and more than 150,000 organizations worldwide, Sentry provides code-level observability to many of the world's best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.