15

I am trying to do the following with requests:

data = {'hello', 'goodbye'}
json_data = json.dumps(data)
headers = {
        'Access-Key': self.api_key,
        'Access-Signature': signature,
        'Access-Nonce': nonce,
        'Content-Type': 'application/json',
        'Accept': 'text/plain'
    }
r = requests.post(url, headers=headers, data=json_data, 
                 files={'file': open('/Users/david/Desktop/a.png', 'rb')})

However, I get the following error:

ValueError: Data must not be a string.

Note that if I remove the files parameter, it works as needed. Why won't requests allow me to send a json-encoded string for data if files is included?

Note that if I change data to be just the normal python dictionary (and not a json-encoded string), the above works. So it seems that the issue is that if files is not json-encoded, then data cannot be json-encoded. However, I need to have my data encoded to match a hash signature that's being created by the API.

David542
  • 104,438
  • 178
  • 489
  • 842
  • Have you tried `data=data`? (no json encoding) – keyser Dec 18 '14 at 18:16
  • 1
    Yes, but then it screws up the API signature (which every other method depends on). – David542 Dec 18 '14 at 18:16
  • 2
    You need to specify the header so that the module knows it's a JSON string. See [this question](http://stackoverflow.com/questions/9733638/post-json-using-python-request) for details. – tyteen4a03 Dec 18 '14 at 18:18
  • @tyteen4a03 -- no, that's not it. I already have those headers. It has to do with the data being passed is a string but the files being passed is not. – David542 Dec 18 '14 at 18:21
  • @David542 What is your header right now? – tyteen4a03 Dec 18 '14 at 18:22
  • Just saw that you have specified a file; I don't think you can upload a file and use a JSON body at the same time. – tyteen4a03 Dec 18 '14 at 18:24
  • @tyteen4a03 -- ok, thanks for that insight. How would I upload this file then? Send its data as a string? – David542 Dec 18 '14 at 18:25
  • @David542 Encapsulate your JSON string in a normal POST key-value pair (i.e "json" => json.dumps(data)). – tyteen4a03 Dec 18 '14 at 18:27
  • Ok -- if I try putting that in my dict and doing `json` encoding I get a `UnicodeDecodeError` from the decoder though. It seems I can't encode this type of data in json? – David542 Dec 18 '14 at 18:30
  • Did you remove the `Content-Type` and `Accept` headers? – tyteen4a03 Dec 18 '14 at 18:31

5 Answers5

9

When you specify your body to a JSON string, you can no longer attach a file since file uploading requires the MIME type multipart/form-data.

You have two options:

  1. Encapsulate your JSON string as part as the form data (something like json => json.dumps(data))
  2. Encode your file in Base64 and transmit it in the JSON request body. This looks like a lot of work though.
Community
  • 1
  • 1
tyteen4a03
  • 1,812
  • 24
  • 45
4

1.Just remove the line json_data = json.dumps(data) and change in request as data=data.

2.Remove 'Content-Type': 'application/json' inside headers.

This worked for me.

Akash Raj
  • 41
  • 2
1

removing the following helped me in my case:

'Content-Type': 'application/json'

then the data should be passed as dictionary

Nabat Farsi
  • 840
  • 1
  • 9
  • 17
0

Alternative solution to this problem is to post data as file.

  1. You can post strings as files. Read more here: http://docs.python-requests.org/en/latest/user/quickstart/#post-a-multipart-encoded-file

  2. Here is explained how to post multiple files: http://docs.python-requests.org/en/latest/user/advanced/#post-multiple-multipart-encoded-files

daliusd
  • 1,025
  • 11
  • 15
0

If your files are small, you could simply convert the binary (image or anything) to base64 string and send that as JSON to the API. That is much simpler and more straight forward than the suggested solutions. The currently accepted answer claims that is a lot of work, but it's really simple.

Client:

with open('/Users/houmie/Downloads/log.zip','rb') as f:
   bytes = f.read()
   tb = b64encode(bytes)
   tb_str = tb.decode('utf-8')
   body = {'logfile': tb_str}
   r = requests.post('https://prod/feedback', data=json.dumps(body), headers=headers)  

API:

def create(event, context):
    data = json.loads(event["body"])
    if "logfile" in data:
        tb_back = data["logfile"].encode('utf-8')
        zip_data = base64.b64decode(tb_back)
Houman
  • 64,245
  • 87
  • 278
  • 460