whoopscheckmate
whoopscheckmate

Reputation: 876

Python requests PUT request with json parameter fails and data parameter succeeds

Problem

I've looked at some of the documentation about the json and data parameters and the differences between them. I think I understand the difference, best explained here, in my opinion.

However, I have a specific request that fails on PUT using json, but fails using data, and I'm not sure why. Can someone clarify why this is the case? Could it be that there is a list in the payload?

Context

I have requests==2.28.0 installed. Below is the code that submits the PUT requests to an API for PagerDuty, the incident management software, one using data (successful) and one using json (failing). Otherwise they are identical.

The weird thing is that their examples use the json parameter.

payload = f'{{"source_incidents": [{{"id": "{child_incident_id}", "type": "incident_reference"}}]}}'

headers = {
    'Content-Type': "application/json",
    'Accept': "application/vnd.pagerduty+json;version=2",
    'From': email,
    'Authorization': f"Token token={read_write_api_token}"
    }

response = requests.put(f'https://api.pagerduty.com/incidents/{parent_incident_id}/merge', data=payload, headers=headers)

print("response: ", response)

Result: response: <Response [200]>

payload = f'{{"source_incidents": [{{"id": "{child_incident_id}", "type": "incident_reference"}}]}}'

headers = {
    'Content-Type': "application/json",
    'Accept': "application/vnd.pagerduty+json;version=2",
    'From': email,
    'Authorization': f"Token token={read_write_api_token}"
    }

response = requests.put(f'https://api.pagerduty.com/incidents/{parent_incident_id}/merge', json=payload, headers=headers)

print("response: ", response)

Result: response: <Response [400]>

Upvotes: 2

Views: 2063

Answers (3)

Karen Petrosyan
Karen Petrosyan

Reputation: 372

That is what the requests library does with json data.

  • Converts your Python object to JSON using the json encoder.
  • Sets the "content-type" header to "application/json".

It is possible to implement it as shown here.

def put(uri, data=None, json=None):
    if json and data:
        raise Exception()
    if json:
        payload = json.dumps(json)
    else:
        payload = data
    ...

So the first request returns 200 because you passed valid JSON OBJECT through the "data" parameter.

And the second request fails because you passed STRING through the JSON that will be dumped with "json.dumps(obj)" for example. As a result, it would be nothing more than a string, which is also a valid JSON object but not a javascript object.

As shown here, if you pass a string through "json.dumps" and a dictionary, it returns two different values: a string and an object.

>>> json.dumps("{}")
'"{}"'
>>> json.dumps({})
'{}'
>>> 

Upvotes: 1

Val Lapidus
Val Lapidus

Reputation: 153

Alternatively you could use json.loads to parse your string:

import json
payload = f'{{"source_incidents": [{{"id": "{child_incident_id}", "type": "incident_reference"}}]}}'
headers = {
    'Content-Type': "application/json",
    'Accept': "application/vnd.pagerduty+json;version=2",
    'From': email,
    'Authorization': f"Token token={read_write_api_token}"
    }

response = requests.put(f'https://api.pagerduty.com/incidents/{parent_incident_id}/merge', data=json.loads(payload), headers=headers)

print("response: ", response)

Upvotes: 1

Selcuk
Selcuk

Reputation: 59425

Your payload is a string while json parameter takes a dictionary. That's the whole point of the json argument (you don't have to encode it yourself):

If you need that header set and you don’t want to encode the dict yourself, you can also pass it directly using the json parameter (added in version 2.4.2) and it will be encoded automatically:

You should pass a dictionary if you want to use the json parameter:

payload = {
    "source_incidents": [
        {
            "id": child_incident_id,
            "type": "incident_reference"
        }
    ]
}

which is more readable anyway.

Upvotes: 1

Related Questions