How to POST JSON data with Python Requests?
Asked Answered
U

10

1091

I need to POST a JSON from a client to a server. I'm using Python 2.7.1 and simplejson. The client is using Requests. The server is CherryPy. I can GET a hard-coded JSON from the server (code not shown), but when I try to POST a JSON to the server, I get "400 Bad Request".

Here is my client code:

data = {'sender':   'Alice',
    'receiver': 'Bob',
    'message':  'We did it!'}
data_json = simplejson.dumps(data)
payload = {'json_payload': data_json}
r = requests.post("http://localhost:8080", data=payload)

Here is the server code.

class Root(object):

    def __init__(self, content):
        self.content = content
        print self.content  # this works

    exposed = True

    def GET(self):
        cherrypy.response.headers['Content-Type'] = 'application/json'
        return simplejson.dumps(self.content)

    def POST(self):
        self.content = simplejson.loads(cherrypy.request.body.read())

Any ideas?

Upcountry answered 16/3, 2012 at 7:46 Comment(6)
I was using a stripped down version of an example straight out of the documentation.Upcountry
My comment still stands - CherryPy does not call class __init__ methods with a content argument (and does not claim to in the link you supply). In the detailed example they have, the user supplies the code that calls __init__ and provides the arguments, which we have not seen here so I have no idea what state your object is in when your # this works comment is relevant.Homeopathist
Are you asking to see the line where the instance is created?Upcountry
yeah, I was trying to start up your example in order to test it, and I wasn't sure how you were instantiating it.Homeopathist
The code has changed. I'm now creating it without the extra argument. cherrypy.quickstart(Root(), '/', conf).Upcountry
Alternatively, If you use a tool like Postman to test your API calls, you can generate code snippet. Like snippets in Python that uses the requests package. Postman documentationSabrina
B
1765

Starting with Requests version 2.4.2, you can use the json= parameter (which takes a dictionary) instead of data= (which takes a string) in the call:

>>> import requests
>>> r = requests.post('http://httpbin.org/post', json={"key": "value"})
>>> r.status_code
200
>>> r.json()
{'args': {},
 'data': '{"key": "value"}',
 'files': {},
 'form': {},
 'headers': {'Accept': '*/*',
             'Accept-Encoding': 'gzip, deflate',
             'Connection': 'close',
             'Content-Length': '16',
             'Content-Type': 'application/json',
             'Host': 'httpbin.org',
             'User-Agent': 'python-requests/2.4.3 CPython/3.4.0',
             'X-Request-Id': 'xx-xx-xx'},
 'json': {'key': 'value'},
 'origin': 'x.x.x.x',
 'url': 'http://httpbin.org/post'}
Bridwell answered 13/10, 2014 at 16:8 Comment(9)
Setting this to the accepted answer since this is more idiomatic as of 2.4.2. Keep in mind, for crazy unicode, this may not work.Upcountry
I saw an example of this that took the dict object and performed json.dumps(object) before sending. Don't do this...it messes up your JSON. The above is perfect..you can pass it a python object and it turns into perfect json.Dorettadorette
@Dorettadorette looking at the source code, Requests sets the content type then does json.dumps(your_json) and converts the result from a str to bytes.Eared
I logged into my account especially to give you +1 for this. Thanks. I was stuck for a couple of hours until I saw you could specify json=dict It' a script for Linode DNS API that I am working on. Thank you very much!Oedema
Also should note, that in order to add authentication, you can easily add the auth=HTTPBasicAuth(user, pass) to the params.Scheel
Before I found this answer I had also to use data=json.dumps(...) but without Content-type: application/json in the header it wouldn't work. This json=... thing works perfect.Richardricharda
Server sends me absolutely different results with using data= and json=. Thanks!Cappello
In my the latest requests version the logic is to favor data over json in case of both value are provided. It is not a warning!!! github.com/psf/requests/blob/… and github.com/psf/requests/blob/… and here you see it doesn't automatically print body based on content-type (which I thought).Castle
Using parameter json= we don't need to set the header Content-Type: Application/JsonAdductor
U
546

It turns out I was missing the header information. The following works:

import requests

url = "http://localhost:8080"
data = {'sender': 'Alice', 'receiver': 'Bob', 'message': 'We did it!'}
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
r = requests.post(url, data=json.dumps(data), headers=headers)
Upcountry answered 31/3, 2012 at 3:26 Comment(9)
Good catch - I saw your application/json in GET and somehow missed that you hadn't provided it on the request. You may also need to make sure that you return something from POST or you might get a 500.Homeopathist
Doesn't seem to be necessary. When I print r, I get <Response [200]>.Upcountry
How do I retrieve this json at the server side ?Estancia
r = requests.get('localhost:8080') c = r.content result = simplejson.loads(c)Upcountry
Little heads up before using json.dumps here. The data parameter of requests works fine with dictionaries. No need for converting to a string.Ers
This was the best answer for me as requests' built in json support uses Python's built in json support which could not handle my object types. So I needed to use jsonpickle and manually set the media type to jsonSecretary
yeah, json.dumps(data) is a very efficient and amazing way to do it.Bouzoun
Is headers=headers necessary?Fourdimensional
@lolololol ol It's necessary. You are setting an argument for the function/method you're calling. In this case the function/method is named post(). The left side of the assignment operator = is the parameter name some dev defined (e.g. def post(some_param0, headers, ...):). The righthand side of the assignment operator (i.e. assignment value) is the variable you defined (e.g. headers = { ... }) that also just happens to be called headers in this example. You could rename the right side of the assignment (e.g. the assignment value) to whatever you like!Camara
G
99

From requests 2.4.2 (https://pypi.python.org/pypi/requests), the "json" parameter is supported. No need to specify "Content-Type". So the shorter version:

requests.post('http://httpbin.org/post', json={'test': 'cheers'})
Grosmark answered 10/12, 2014 at 10:8 Comment(0)
E
80

Which parameter between data / json / files you need to use depends on a request header named Content-Type (you can check this through the developer tools of your browser).

When the Content-Type is application/x-www-form-urlencoded, use data=:

requests.post(url, data=json_obj)

When the Content-Type is application/json, you can either just use json= or use data= and set the Content-Type yourself:

requests.post(url, json=json_obj)
requests.post(url, data=jsonstr, headers={"Content-Type":"application/json"})

When the Content-Type is multipart/form-data, it's used to upload files, so use files=:

requests.post(url, files=xxxx)
Ewald answered 12/4, 2020 at 7:53 Comment(2)
+1. If you're using curlify to see the request made from the response, the content type header will not be set unless you follow these instructions. print(curlify.to_curl(project.request))Huonghupeh
The key realization is that every parameter will perform automagic serialization without regard for the current value of the Content-Type header. Consequently, data=json_obj is basically always incorrect; the value must be stringified first, either explicitly by the caller or implicitly via the json parameter.Geocentric
D
50

The better way is:

url = "http://xxx.xxxx.xx"
data = {
    "cardno": "6248889874650987",
    "systemIdentify": "s08",
    "sourceChannel": 12
}
resp = requests.post(url, json=data)
Detruncate answered 4/5, 2017 at 7:26 Comment(0)
O
12
headers = {"charset": "utf-8", "Content-Type": "application/json"}
url = 'http://localhost:PORT_NUM/FILE.php'

r = requests.post(url, json=YOUR_JSON_DATA, headers=headers)
print(r.text)
Oke answered 5/1, 2022 at 14:9 Comment(0)
S
9

Works perfectly with python 3.5+

client:

import requests
data = {'sender':   'Alice',
    'receiver': 'Bob',
    'message':  'We did it!'}
r = requests.post("http://localhost:8080", json={'json_payload': data})

server:

class Root(object):

    def __init__(self, content):
        self.content = content
        print self.content  # this works

    exposed = True

    def GET(self):
        cherrypy.response.headers['Content-Type'] = 'application/json'
        return simplejson.dumps(self.content)

    @cherrypy.tools.json_in()
    @cherrypy.tools.json_out()
    def POST(self):
        self.content = cherrypy.request.json
        return {'status': 'success', 'message': 'updated'}
Sprinkle answered 20/1, 2017 at 21:10 Comment(1)
Note/reminder: the json arg automatically adds the content-type header for POST in newer versions of Python.Philter
M
8

With current requests you can pass in any data structure that dumps to valid JSON , with the json parameter, not just dictionaries (as falsely claimed by the answer by Zeyang Lin).

import requests
r = requests.post('http://httpbin.org/post', json=[1, 2, {"a": 3}])

this is particularly useful if you need to order elements in the response.

Mimimimic answered 23/5, 2022 at 7:53 Comment(0)
S
0

I solved it this way:

from flask import Flask, request
from flask_restful import Resource, Api


req = request.json
if not req :
    req = request.form
req['value']
Silvie answered 15/12, 2021 at 22:5 Comment(0)
V
-1

It always recommended that we need to have the ability to read the JSON file and parse an object as a request body. We are not going to parse the raw data in the request so the following method will help you to resolve it.

def POST_request():
    with open("FILE PATH", "r") as data:
        JSON_Body = data.read()
    response = requests.post(url="URL", data=JSON_Body)
    assert response.status_code == 200
Vaulted answered 19/3, 2021 at 14:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.