POST request with nested JSON parameters malformed in request.POST - python

Inside integration tests, I try to make a POST request with a nested JSON parameter (this is just one of the approaches I tried):
test_function(admin_client):
admin_client.post(some_url,
json.dumps(some_nested_json),
content_type='application/json',
headers={'Content-type': 'application/json; charset=utf-8'})
I've also tried all sorts of different combinations for the parameters etc., everything I could find on the web, but I can't get the proper JSON output in the received request.JSON.
I get one of these three cases:
request.POST contains the first level of JSON plus the arrays of second level JSON keys
request.POST contains the first level of JSON plus an empty second level JSON
request.POST is empty, but request.body contains the whole nested JSON, in expected format
What am I missing here? I'm using Python 2.7.

This is expected behavior in django. Json requests are not in in request.POST but in request.body. You need to manually do json.loads(request.body) in your view.

Related

What is a good way to parse JSON response with varied data types?

I am trying to write API calls to retrieve the last package status using UPS Tracking API. However, it seems that the JSON strings returned by the API vary in the data type returned - for example, ["TrackResponse"]["Shipment"]["Package"] returns either a JSON string or a list of JSON strings.
I am currently managing these using the below try / except statements. If a list of JSONs is returned instead of a singular JSON string, I specify that I am looking for the first JSON string in the list.
response = requests.request("POST", url, data=payload, headers=headers)
response_json=response.json()
try:
status = response_json["TrackResponse"]["Shipment"]["Package"]["Activity"][0]["Status"]["Type"]
code = response_json["TrackResponse"]["Shipment"]["Package"]["Activity"][0]["Status"]["Code"]
location = response_json["TrackResponse"]["Shipment"]["Package"]["Activity"][0]["ActivityLocation"]["Address"]["City"]
except:
status = response_json["TrackResponse"]["Shipment"]["Package"][0]["Activity"][0]["Status"]["Type"]
code = response_json["TrackResponse"]["Shipment"]["Package"][0]["Activity"][0]["Status"]["Code"]
location = response_json["TrackResponse"]["Shipment"]["Package"][0]["Activity"][0]["ActivityLocation"]["Address"]["City"]
Unfortunately, this seems like it isn't going to work well as there are multiple parts of the API response where this issue is occurring. Eg."Activity" also returns a JSON if only one activity was found, or a list of JSONs if multiple activities are returned.
Is there a more elegant way of resolving these issues, besides writing multiple nested if/else or try/except statements to test the datatype returned at each point of the JSON response?
Thanks!

Proper way of streaming JSON with Django

i have a webservice which gets user requests and produces (multiple) solution(s) to this request.
I want to return a solution as soon as possible, and send the remaining solutions when they are ready.
In order to do this, I thought about using Django's Http stream response. Unfortunately, I am not sure if this is the most adequate way of doing so, because of the problem I will describe below.
I have a Django view, which receives a query and answers with a stream response. This stream returns the data returned by a generator, which is always a python dictionary.
The problem is that upon the second return action of the stream, the Json content breaks.
If the python dictionary, which serves as a response, is something like {key: val}, after the second yield the returned response is {key: val} {key: val}, which is not valid Json.
Any suggestions on how to return multiple Json objects at different moments in time?
Try decoding with something like
for example
import json
json.dumps( {key: val} {key: val}, separators=('}', ':')) #check it
The solution is on the client side (eg: browser). You need to interpret the JSON result when it comes. By definition of a stream you never know when it finished, so everytime you receive some data on the client side, you need to interpret it as a self contain JSON message.

Does Python Requests POST need numerical data to be a string rather than a float?

Here is a small quote from this answer:
import requests
import json
data = {"data" : "24.3"}
data_json = json.dumps(data)
headers = {'Content-type': 'application/json'}
response = requests.post(url, data=data_json, headers=headers)
Does anyone know for sure whether it matters whether you have
data = {"data" : "24.3"}
or
data = {"data" : 24.3} ?
You are already giving a string to requests.post(), because you convert your dictionary to a JSON document with json.dumps(). It doesn't matter to requests what this string contains.
It only matters to whatever server you are sending this data; it is that server that will decode the JSON document and use your number or string.
Note that requests can do the JSON conversion for you. There is no need to use json.dumps() here, just pass your dictionary to the json keyword argumnet:
import requests
data = {"data" : "24.3"}
response = requests.post(url, json=data)
This also takes care of setting the Content-Type header to application/json.
There are two unrelated questions in your post.
The first is:
Does anyone know for sure whether it matters whether you have
data = {"data" : "24.3"}
or
data = {"data" : 24.3} ?
Yes, it does matter!
They are completely different things.
Treating them the same would make JSON format usage obsolete.
If server expects key "data" to be JSON data type number and you send it as a JSON data type string instead, a HTTP status code 400 should be responded.
If server does not report any error it means that this particular key is not being used in server and/or it is not being validated in server.
If server does treat them the same it is idiotic rather than redundant. That is why JSON format is being used in the first place.
The second is:
Does Python Requests POST need numerical data to be a string rather
than a float?
This question title is explained in Martijn Pieters's answer.

Python Requests - add text at the beginning of query string

When sending data through python-requests a GET request, I have a need to specifically add something at the beginning of the query string. I have tried passing the data in through dicts and json strings with no luck.
The request as it appears when produced by requests:
/apply/.../explain?%7B%22......
The request as it appears when produced by their interactive API documentation (Swagger):
/apply/.../explain?record=%7B%22....
Where the key-value pairs of my data follow the excerpt above.
Ultimately, I think the missing piece is the record= that gets produced by their documentation. It is the only piece that is different from what is produced by Requests.
At the moment I've got it set up something like this:
import requests
s = requests.Session()
s.auth = requests.auth.HTTPBasicAuth(username,password)
s.verify = certificate_path
# with data below being a dictionary of the values I need to pass.
r = s.get(url,data=data)
I am trying to include an image of the documentation below, but don't yet have enough reputation to do so:
apply/model/explain documentation
'GET' requests don't have data, that's for 'POST' and friends.
You can send the query string arguments using params kwarg instead:
>>> params = {'record': '{"'}
>>> response = requests.get('http://www.example.com/explain', params=params)
>>> response.request.url
'http://www.example.com/explain?record=%7B%22'
From the comments i felt the need to explain this.
http://example.com/sth?key=value&anotherkey=anothervalue
Let's assume you have a url like the above in order to call with python requests you only have to write
response = requests.get('http://example.com/sth', params={
'key':'value',
'anotherkey':'anothervalue'
})
Have in mind that if your value or your keys have any special character in them they will be escaped thats the reason for the %7B%2 part of url in your question.

django nested body request not being set

I'm trying to make a request to the box api using python and django. I'm getting a 400 Entity body should be a correctly nested resource attribute name\\/value pair error.
My requests looks like:
requests.options(headers.kwargs['url'], headers=headers.headers,
data={'parent': {'id': 'xxxx'}, 'name': 'name.pdf'})
When I inspect the 400 request.body it contains 'parent=id&name=name.pdf' which leads me to believe I'm not setting the body properly
A curl works with the body
-d '{"name": "name.pdf", "parent": {"id": "xxxxx"}}'
Explicitly encode the dictionary to prevent form-encoding. Otherwise, it will be form-encoded as the way similar to urllib.urlencode (or urllib.parse.urlencode in Python 3.x).
import json
...
requests.options(
headers.kwargs['url'], headers=headers.headers,
data=json.dumps({'parent': {'id': 'xxxx'}, 'name': 'name.pdf'}))
In other word, instead of passing a dictionary, pass a string.
According to More complicated POST requests - Request documentation:
...
There are many times that you want to send data that is not
form-encoded. If you pass in a string instead of a dict, that data
will be posted directly.

Categories