today we were trying to do a multipart/form-data
POST to one of our webservices using the usually rather usable python requests. the spec of the webservice required that multiple files were to be POSTed as multiparts with the name files
and individual filename attributes — a task that python request according to its doc did not really seem to support:
>>> url = 'http://httpbin.org/post' >>> files = {'file': ('report.xls', open('report.xls', 'rb'))} >>> r = requests.post(url, files=files) >>> r.text { ... "files": { "file": "<censored...binary...data>" }, ... }
since the files
parameter is a dict, it’s not really possible to add the files
keyword twice. several searches and attempts (tried listing tuples as value of the key, didn’t really work) later we resorted to tracing into the requests.post()
call to see what was going on down in the engine room of requests. lo and behold, we did come across the following inspiring piece of code in requests’s _encode_files
method:
for (k, v) in files: # support for explicit filename ft = None fh = None if isinstance(v, (tuple, list)): if len(v) == 2: fn, fp = v elif len(v) == 3: fn, fp, ft = v else: fn, fp, ft, fh = v else: fn = guess_filename(v) or k fp = v if isinstance(fp, str): fp = StringIO(fp) if isinstance(fp, bytes): fp = BytesIO(fp) rf = RequestField(name=k, data=fp.read(), filename=fn, headers=fh) rf.make_multipart(content_type=ft) new_fields.append(rf)
the inspiring bit is the for (k, v) in files:
part — this is iterating over a dict (as illustrated by the doc), but that should also work with a list of tuples of (name, file object)
!
sure enough: this piece of code:
import requests r = requests.post('http://127.0.0.1:8008/service/recorddata/write', files=[('files', open('/tmp/hurz.txt')), ('files', open('/tmp/wuff.txt'))])
yields the desired result:
POST /service/recorddata/write HTTP/1.1 Host: 127.0.0.1:8008 Content-Length: 286 Content-Type: multipart/form-data; boundary=c282f7a56d3941c1bcfc2c43cf991f67 Accept-Encoding: gzip, deflate, compress Accept: */*User-Agent: python-requests/2.2.0 CPython/2.7.3 Linux/3.2.0-58-generic-pae Connection: keep-alive --c282f7a56d3941c1bcfc2c43cf991f67Content-Disposition: form-data; name="files"; filename="hurz.txt" HurzHurzHurzHurz --c282f7a56d3941c1bcfc2c43cf991f67 Content-Disposition: form-data; name="files"; filename="wuff.txt" WuffWuffWuffWuff --c282f7a56d3941c1bcfc2c43cf991f67-
Voila!