POST request to FastAPI endpoint produces error 422: "value is not a valid dict"

David Y.

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.

Loved by over 4 million developers and more than 90,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.

Share on Twitter
Bookmark this page
Ask a questionJoin the discussion

Related Answers

A better experience for your users. An easier life for your developers.

    TwitterGitHubDribbbleLinkedinDiscord
© 2024 • Sentry is a registered Trademark
of Functional Software, Inc.