Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: FastAPI error 422 with post request

I'm building a simple API to test a database. When I use get request everything works fine, but if I change to post I get "unprocessable entity" error:

Here is the FastAPI code:

from fastapi import FastAPI

app = FastAPI()

@app.post("/")
def main(user):
    return user

Then, my request using javascript

let axios = require('axios')

data = { 
    user: 'smith' 
}

axios.post('http://localhost:8000', data)
    .then(response => (console.log(response.url)))

Also using Python

import requests

url = 'http://127.0.0.1:8000'
data = {'user': 'Smith'}

response = requests.post(url, json=data)
print(response.text)

I also try to parse as json, enconding using utf-8, and change the headers. Nothing has worked for me.

like image 377
Smith Avatar asked Jan 27 '20 10:01

Smith


People also ask

How do I fix Error 422?

Broadly speaking, if you see an HTTP 422 error it means the server understands your request, but it can't fulfill it due to a problem on your end. If you fix that problem, you should be able to reload the page and the error will go away.

What does status code 422 mean?

The HyperText Transfer Protocol (HTTP) 422 Unprocessable Entity response status code indicates that the server understands the content type of the request entity, and the syntax of the request entity is correct, but it was unable to process the contained instructions.

When to use 422?

In an ideal world, 422 is preferred and generally acceptable to send as response if the server understands the content type of the request entity and the syntax of the request entity is correct but was unable to process the data because its semantically erroneous.


4 Answers

Straight from the documentation:

The function parameters will be recognized as follows:

  • If the parameter is also declared in the path, it will be used as a path parameter.
  • If the parameter is of a singular type (like int, float, str, bool, etc) it will be interpreted as a query parameter.
  • If the parameter is declared to be of the type of a Pydantic model, it will be interpreted as a request body."

So to create a POST endpoint that receives a body with a user field you would do something like:

from fastapi import FastAPI from pydantic import BaseModel   app = FastAPI()   class Data(BaseModel):     user: str   @app.post("/") def main(data: Data):     return data 
like image 81
michaeloliver Avatar answered Sep 20 '22 12:09

michaeloliver


In my case, I was calling the python API from different python project like this

queryResponse = requests.post(URL, data= query) 

I was using the data property, I changed it to json, then it worked for me

queryResponse = requests.post(URL, json = query) 
like image 31
Sunil Garg Avatar answered Sep 16 '22 12:09

Sunil Garg


If you're using the fetch API and still getting the 422 Unprocessable Entity, ensure that you have set the Content-Type header:

fetch(someURL, {
  method: "POST",
  headers: {
    "Content-type": "application/json"
  },
  body
}).then(...)

This solved the issue in my case. On the server-side I'm using Pydantic models, so if you aren't using those, see the above answers.

like image 44
Alan Avatar answered Sep 19 '22 12:09

Alan


A response having status code 422 (unprocessable entity) will have a response body that specifies the error message, telling exactly which part of your request is missing or doesn’t match the expected format. The code snippet you povided shows that you are trying to post JSON data to an endpoint that is expecting user being a query parameter, rather than JSON payload. Hence, the 422 unprocessable entity error. Below are given four different options on how to define an endpoint to expect JSON data.

Option 1

As per the documentation, when you need to send JSON data from a client (let's say, a browser) to your API, you send it as a request body (through a POST request). To declare a request body, you can use Pydantic models.

from pydantic import BaseModel

class User(BaseModel):
    user: str

@app.post("/")
def main(user: User):
    return user

Option 2

If one doesn't want to use Pydantic models, they could also use Body parameters. If a single body parameter is used (as in your example), you can use the special Body parameter embed.

from fastapi import Body

@app.post("/")
def main(user: str = Body(..., embed=True)):
    return {"user": user}

Option 3

Another (less recommended) way would be to use a Dict type (or simply dict in Python 3.9+) to declare a key:value pair. However, in this way, you can't use custom validations for various attributes in your expected JSON, as you would do with Pydantic models or Body fields (e.g., check if an email address is valid, or if a string follows a specific pattern).

from typing import Dict, Any

@app.post("/")
def main(user: Dict[Any, Any]):  # or, user: dict
    return user

Option 4

If you are confident that the incoming data is a valid JSON, you can use Starlette's Request object directly to get the request body parsed as JSON, using await request.json(). However, with this approach, not only can't you use custom validations for your attributes, but you would also need to define your endpoint with async def, since request.json() is an async method and thus, one needs to await it (have a look at this answer for more details on def vs async def).

from fastapi import Request

@app.post("/")
async def main(request: Request): 
    return await request.json()

If you wish, you could also implement some checking of the Content-Type in the request's headers, before attempting to parse the data, similar to this answer. However, just because a request says application/json in the headers, doesn't always mean that this is true, or that the incoming data is a valid JSON (i.e., may be missing a curly bracket, have a key that does not have a value, etc). Hence, you could use a try-except block when you attempt to parse the data, letting you handle any JSONDecodeError, in case there is an issue with the way in which your JSON data is formatted.

from fastapi import Request

@app.post("/")
async def main(request: Request):
    content_type = request.headers.get('Content-Type')
    
    if content_type is None:
        return 'No Content-Type provided.'
    elif content_type == "application/json":
        try:
            json = await request.json()
            return json
        except JSONDecodeError:
            return "Invalid JSON data."
    else:
        return 'Content-Type not supported.'

Test all the above options using Python requests module

import requests

url = 'http://127.0.0.1:8000'
payload ={"user": "foo"}
resp = requests.post(url=url, json=payload)
print(resp.json())
like image 25
Chris Avatar answered Sep 17 '22 12:09

Chris