I have a small_file.txt
file that contains:
1asdfaksdjfhlaksjdhflkjashdflkjhasldkjfhlaksdfhasdf:
2asdfaksdjfhlaksjdhflkjashdflkjhasldkjfhlaksdfhasdf:
3asdfaksdjfhlaksjdhflkjashdflkjhasldkjfhlaksdfhasdf
4asdfaksdjfhlaksjdhflkjashdflkjhasldkjfhlaksdfhasdf:
Notice the colons at the end, they are just regular strings.
When I try to send it using python requests
it doesn't work. For some reason, it waits for the first line with a colon and then sends all the lines starting from there. So for example, in the file above, it will POST
only:
3asdfaksdjfhlaksjdhflkjashdflkjhasldkjfhlaksdfhasdf
4asdfaksdjfhlaksjdhflkjashdflkjhasldkjfhlaksdfhasdf:
How can I fix this issue? I'm not sure what is going on.
Here is a simple version of my code:
import requests
import sys
import json
import os
token = 'nVQowAng0c'
url = "https://api.hipchat.com/v2/room/test_room/share/file"
headers = {'Content-type': 'multipart/related; boundary=boundary123456'}
headers['Authorization'] = "Bearer " + token
filepath = 'small_file.csv'
data = open(filepath, 'rb').read()
payload = """\
--boundary123456
Content-Type: application/json; charset=UTF-8
Content-Disposition: attachment; name="metadata"
--boundary123456
Content-Disposition: attachment; name="file"; filename="{0}"
{1}
--boundary123456--\
""".format(os.path.basename(filepath), data)
r = requests.post(url, headers=headers, data=payload)
r.raise_for_status()
When I try to send something like a .csv
file with a timestamp on every row, nothing will get sent because each row has a colon.
Your immediate error is that you misencoded the MIME multipart elements. Each part has two sections, headers and contents, with a double newline between. Yours is missing the second newline, add it in:
payload = """\
--boundary123456
Content-Type: application/json; charset=UTF-8
Content-Disposition: attachment; name="metadata"
--boundary123456
Content-Disposition: attachment; name="file"; filename="{0}"
{1}
--boundary123456--\
""".format(os.path.basename(filepath), data)
I'd not manually build the contents, but re-purpose the requests-toolbelt
project to let you upload your data in a streaming fashion:
from requests_toolbelt import MultipartEncoder
class MultipartRelatedEncoder(MultipartEncoder):
"""A multipart/related encoder"""
@property
def content_type(self):
return str(
'multipart/related; boundary={0}'.format(self.boundary_value)
)
def _iter_fields(self):
# change content-disposition from form-data to attachment
for field in super(MultipartRelatedEncoder, self)._iter_fields():
content_type = field.headers['Content-Type']
field.make_multipart(
content_disposition='attachment',
content_type=content_type)
yield field
m = MultipartRelatedEncoder(
fields={
'metadata': (None, '', 'application/json; charset=UTF-8'),
'file': (os.path.basename(filepath), open(filepath, 'rb'), 'text/csv'),
}
)
headers['Content-type'] = m.content_type
r = requests.post(url, data=m, headers=headers)
I've adapted the requests_toolbelt.MultipartEncoder
class to emit a multipart/related
data stream rather than a multipart/form-data
message.
Note that I pass in the open file object, and not the file data itself; this because the MultipartEncoder
lets you stream the data to the remote server, the file doesn't have to be read into memory in one.
You probably want to pass in actual JSON data in the metadata
part; replace the empty string in the (None, '', 'application/json; charset=UTF-8'
tuple with a valid JSON document.