fastapi: FastAPI 0.65.2 POST request fails with "value is not a valid dict" when using the Requests library; 0.65.1 works (with a caveat)

First check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn’t find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google “How to X in FastAPI” and didn’t find any information.
  • I already read and followed all the tutorial in the docs and didn’t find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.
  • After submitting this, I commit to one of:
    • Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
    • I already hit the “watch” button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
    • Implement a Pull Request for a confirmed bug.

First off, thank you for creating this marvellous piece of software. It is a real life-changer.

I hit a very odd bug while implementing some unit tests. Using FastAPI 0.65.2, a POST request via the requests module (requests.post) consistently returns the following error:

{'detail': [{'loc': ['body'], 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}]}

I created a reproducible example:

from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn

app = FastAPI()

class Data(BaseModel):
    field: str


@app.post("/test")
def test_data(data: Data):
    return "Polo"

if __name__=='__main__':
    uvicorn.run(app)

And the requests counterpart:

import requests as rq

def test():
    result = rq.post('http://127.0.0.1:8000/test', data={"field": "Marco"})
    print(f"==[ result: {result.json()}")

if __name__=="__main__":
    test()

The result is

==[ result: {'detail': [{'loc': ['body'], 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}]}

If I downgrade FastAPI to 0.65.1, I still get an error, but a different one:

==[ result: {'detail': [{'loc': ['body', 0], 'msg': 'Expecting value: line 1 column 1 (char 0)', 'type': 'value_error.jsondecode', 'ctx': {'msg': 'Expecting value', 'doc': 'field=heya', 'pos': 0, 'lineno': 1, 'colno': 1}}]}

This can be solved by JSONifying the dictionary:

import requests as rq
import json as js

def test():
    result = rq.post('http://127.0.0.1:8000/test', data = js.dumps({"field": "Marco"}))
    print(f"==[ result: {result.json()}")

if __name__=="__main__":
    test()

Running the above prints

==[ result: Polo

I am slightly perplexed because I am not sure if it is the requests library that is to blame or FastAPI. POSTing via ReDoc works without hiccups with both versions. I am at a loss as to why the second parameter to requests.post is accepted as JSON when it should actually be a dictionary.

Please let me know if I’m missing something obvious or whether I should redirect this to the maintainers of requests. Thank you again for making the lives of developers so much easier.

Cheers!

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 26 (8 by maintainers)

Commits related to this issue

Most upvoted comments

You probably meant to use the json parameter, not the data parameter. Data is for form data

Can confirm, this still happens! We solved it by adding a -H "Content-Type: application/json" to the curl

I think this issue is critical, I saw this on 0.70.0 too, make a POST request from httpx with json=msg, headers={“Content-Type”: “application/json; charset=utf-8”} does not resolve this issue.

my pydantic model just simple like this:

class history_model(BaseModel):
    process_order: int
    process_name: str
    request_dttm: str
    request_body: Optional[Dict] = None
    response_body: Optional[Dict]  = None


class worker_model(BaseModel):
    msg_no : str
    topic_name : str
    next_stage : str
    entity_info : Dict
    status_history : List[history_model]
    track_change : Optional[List[Dict]] = None

when I test the json string in /docs, it work, but post it via httpx/requests it will return error: {‘detail’: [{‘loc’: [‘body’], ‘msg’: ‘value is not a valid dict’, ‘type’: ‘type_error.dict’}]}

New update: found the bug, when setup with pydantic model, fastapi expect request_body to be received as dict, not string, if you send request and feed the json string into json param like this:

requests.post(url=url, json=msg_json_string)                                 

it will generate error {‘detail’: [{‘loc’: [‘body’], ‘msg’: ‘value is not a valid dict’, ‘type’: ‘type_error.dict’}]} my FastAPI version is 0.70.0. if you setup the path operation with pydantic model, remember to send request as dict, not json, like this:

requests.post(url=url, json=json.loads(msg_json_string))                                 

@tiangolo is this normal behavior ?

I got this error too. My use case was sending a base64 encoded image.

After trying a mix of the solutions above, I ended up with the below.

json_response = requests.post(
    "http://host:port/abc",
    json=data,
    headers={"Content-Type": "application/json; charset=utf-8"},
)  

Weirdly enough, Postman works despite not setting the charset.

I just tried v0.67.0 and I’m still seeing the issue. Taking the initial example, with the following request:

curl -X POST --header "Content-Type: text/plain" --data "{\"field\": \"Marco\"}" http://127.0.0.1:8000/test

This doesn’t work (shows INFO: 127.0.0.1:35400 - "POST /test HTTP/1.1" 422 Unprocessable Entity):

from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn

app = FastAPI()

class Data(BaseModel):
    field: str

@app.post("/test")
async def test_data(data: Data):
    print(data)
    return "Polo"

if __name__=='__main__':
    uvicorn.run(app)

And this works fine (prints {'field': 'Marco'}):

from fastapi import FastAPI
import uvicorn
from starlette.requests import Request

app = FastAPI()

@app.post("/test")
async def test_data(request: Request):
    print(await request.json())
    return "Polo"

if __name__=='__main__':
    uvicorn.run(app)

The reason the downgrade fixed it was due to the json post handling in the latest version - it was changed so it only tried to parse bodies with a valid Content-Type header as json, which the data parameter wouldn’t set

AWS SNS HTTP/HTTPS notification JSON format messages specify content-type of text/plain; charset=UTF-8. This causes the current version of FastAPI (0.68.1) to fail with a 422 exception discussed above, which appears to be a value is not a valid dict (type=type_error.dict) 400 exception when overriding the RequestValidationError and logging the exc as a string. Downgrading to 0.65.1 resolved the issue.

Example AWS SNS JSON message from https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-notification-json

POST / HTTP/1.1
x-amz-sns-message-type: SubscriptionConfirmation
x-amz-sns-message-id: 165545c9-2a5c-472c-8df2-7ff2be2b3b1b
x-amz-sns-topic-arn: arn:aws:sns:us-west-2:123456789012:MyTopic
Content-Length: 1336
Content-Type: text/plain; charset=UTF-8
Host: myhost.example.com
Connection: Keep-Alive
User-Agent: Amazon Simple Notification Service Agent

{
  "Type" : "SubscriptionConfirmation",
  "MessageId" : "165545c9-2a5c-472c-8df2-7ff2be2b3b1b",
  "Token" : "2336412f37...",
  "TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
  "Message" : "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:123456789012:MyTopic.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
  "SubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37...",
  "Timestamp" : "2012-04-26T20:45:04.751Z",
  "SignatureVersion" : "1",
  "Signature" : "EXAMPLEpH+DcEwjAPg8O9mY8dReBSwksfg2S7WKQcikcNKWLQjwu6A4VbeS0QHVCkhRS7fUQvi2egU3N858fiTDN6bkkOxYDVrY0Ad8L10Hs3zH81mtnPk5uvvolIC1CXGu43obcgFxeL3khZl8IKvO61GWB6jI9b5+gLPoBc1Q=",
  "SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"
}

@Mause

Thank you for the quick reply. What you suggested indeed fixed the problem, so I will mark this as resolved, but I am still perplexed. I never knew that the data parameter was for form data - I have always used data until I encountered this issue. This is from the requests documentation:

Requests’ simple API means that all forms of HTTP request are as obvious. For example, this is how you make an HTTP POST request: >>> r = requests.post('https://httpbin.org/post', data = {'key':'value'})

Also, I’m not sure why downgrading FastAPI works (subject to the JSONification caveat above).

Still, all is well that ends well. 😃 Thank you for your help!

Also, using requests.post("/create-user", json={"some": "data"}) requires the value passed to json to be the Python objects because Requests serializes that to JSON bytes (a string). If you serialize it before and pass it a string, then Requests will send it as a JSON string data type, not the pure data serialized as JSON.

It’s not related to FastAPI, FastAPI expects standard JSON. It’s related to how Requests expects the data in the parameter and how it serializes automatically for you.

The reason the downgrade fixed it was due to the json post handling in the latest version - it was changed so it only tried to parse bodies with a valid Content-Type header as json, which the data parameter wouldn’t set

This is a rather major change of behaviour!

It broke plenty of existing integrations at $work 😦

I just updated from 0.63 to 0.78 and I had the error too.

I fixed it by changing the frontend from

const response = await fetch(url, {
  method: 'POST',
  body: JSON.stringify(body),
})

to

const response = await fetch(url, {
  method: 'POST',
  body: JSON.stringify(body),
  headers: { 'Content-Type': 'application/json' },
})