Python file upload

Not only am I using C++ at my new job, but i’m also doing some Perl and some Python. I actually have found that I rather like Python. It’s a great language for what we’re using it for. As an aside that has nothing to do with this blog post, this link shows the most popular programming languages. I am using 7 of the top 20 (as of July 2008) programming languages right now. :)

Anyway, back to the point of this post: I was very surprised to learn that Python 2.5x does not have support for uploading binary files over http (i.e. it can’t do file upload). What? That’s crazy. I happen to need this functionality for a task i’m working on. After some searching, i was able to pull together a bunch of pieces on the net and get a custom solution working. Many sites had pieces of the puzzle, but nobody had an entire working example all put together. Anyway, here is my solution:

import os, stat, mimetypes, httplib

def post_multipart(host, selector, fields, files):
Post fields and files to an http host as multipart/form-data.
@param host: the hostname of the server to connect to.  For example:
@param selector: where to go on the host.  For example: cgi-bin/ or blog/upload, etc..
@param fields: a sequence of (name, value) elements for regular form fields.  For example:
    [("vals", "16,18,19"), ("foo", "bar")]
@param files: a sequence of (name, file) elements for data to be uploaded as files.  For example:
    [ ("mugshot", open("/images/me.jpg", "rb")) ]
@return: the server's response page.

    content_type, body = _encode_multipart_formdata(fields, files)
    h = httplib.HTTPConnection(host)  
    headers = {
        'User-Agent': 'python_multipart_caller',
        'Content-Type': content_type
    h.request('POST', selector, body, headers)
    res = h.getresponse()

def _encode_multipart_formdata(fields, files):
@return: (content_type, body) ready for httplib.HTTP instance
    BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
    CRLF = '\r\n'
    L = []
    for (key, value) in fields:
        L.append('--' + BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"' % key)
    for (key, fd) in files:
        file_size = os.fstat(fd.fileno())[stat.ST_SIZE]
        filename ='/')[-1]
        contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
        L.append('--%s' % BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
        L.append('Content-Type: %s' % contenttype)
        L.append('\r\n' +
    L.append('--' + BOUNDARY + '--')
    body = CRLF.join(L)
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    return content_type, body

if __name__ == '__main__':
    post_multipart("", "/cgi-bin/", [("foo", "bar")], [("mugshot", open("/images/me.jpg", "rb"))])