14

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
  • 101,766
  • 154
  • 423
  • 727

5 Answers5

8

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,724
  • 20
  • 39
2

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
  • 21
  • 1
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
  • 897
  • 10
  • 14
0

removing the following helped me in my case:

'Content-Type': 'application/json'

then the data should be passed as dictionary

Nabat Farsi
  • 695
  • 7
  • 14
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
  • 61,497
  • 83
  • 253
  • 442