Reputation: 551
I'm trying to send a multipart/related message using requests in Python. The script seems simple enough, except that requests only seems to allow multipart/form-data messages to be sent, though their documentation does not clearly state this one way or another.
My use case is sending soap with attachments. I can provide a dictionary with the two files whose contents are a test soap-message, and a test document that I'm trying to send. The first contains the soap message with all the instructions, the second is the actual document.
However, if I don't specify a headers value, requests only seems to use multipart/form-data when using the files option. But if I specify headers in an attempt to specify a different multipart type, requests does not seem to add in the mime boundary information.
url = 'http://10.10.10.90:8020/foo'
headers = {'content-type': 'multipart/related'}
files = {'submission': open('submission_set.xml', 'rb'), 'document': open('document.txt', 'rb')}
response = requests.post(url, data=data, headers=headers)
print response.text
Is there a way to get this done using requests? Or is there another tool that I should be looking at?
Upvotes: 16
Views: 10337
Reputation: 463
I'm working with requests
and the Google Drive API "Multipart" upload.
The email.mime
solution did not work with Google's API, so I dug into the requests
source code to see how it implements multipart/form-data
bodies.
requests
uses the urllib3.filepost.encode_multipart_formdata()
helper, which can be wrapped to provide multipart/related
:
from urllib3.filepost import encode_multipart_formdata, choose_boundary
def encode_multipart_related(fields, boundary=None):
if boundary is None:
boundary = choose_boundary()
body, _ = encode_multipart_formdata(fields, boundary)
content_type = str('multipart/related; boundary=%s' % boundary)
return body, content_type
Now we can use encode_multipart_related()
to create a (body, content_type)
tuple that matches Google's requirements:
import json
from urllib3.fields import RequestField
def encode_media_related(metadata, media, media_content_type):
rf1 = RequestField(
name='placeholder',
data=json.dumps(metadata),
headers={'Content-Type': 'application/json; charset=UTF-8'},
)
rf2 = RequestField(
name='placeholder2',
data=media,
headers={'Content-Type': media_content_type},
)
return encode_multipart_related([rf1, rf2])
Here is a full example that uses our encode_media_related()
to upload a hello world file to Google Drive, using the google_auth
library.
from google.oauth2 import service_account
import google.auth.transport.requests
credentials = service_account.Credentials.from_service_account_file(
PATH_TO_SERVICE_FILE,
scopes=['https://www.googleapis.com/auth/drive.file'],
)
session = google.auth.transport.requests.AuthorizedSession(credentials)
metadata = {
'mimeType': 'application/vnd.google-apps.document',
'name': 'Test Upload',
}
body, content_type = encode_media_related(
metadata,
'<html><body><p>Hello World!</body></html>',
'text/html; charset=UTF-8',
)
resp = session.post(
'https://www.googleapis.com/upload/drive/v3/files',
data=body,
params={'uploadType': 'multipart'},
headers={'Content-Type': content_type},
)
print 'Uploaded to file with id: %s' % resp.json()['id']
Upvotes: 10
Reputation: 1124758
You'll have to create the MIME encoding yourself. You can do so with the email.mime
package:
import requests
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
related = MIMEMultipart('related')
submission = MIMEText('text', 'xml', 'utf8')
submission.set_payload(open('submission_set.xml', 'rb').read())
related.attach(submission)
document = MIMEText('text', 'plain')
document.set_payload(open('document.txt', 'rb').read())
related.attach(document)
body = related.as_string().split('\n\n', 1)[1]
headers = dict(related.items())
r = requests.post(url, data=body, headers=headers)
I presumed the XML file uses UTF-8, you probably want to set a character set for the document
entry as well.
requests
only knows how to create multipart/form-data
post bodies; the multipart/related
is not commonly used.
Upvotes: 28