how to POST multipart list of JSON/xml files using python requests

后端 未结 1 463
无人共我
无人共我 2020-12-18 16:04

In python2.7 I use requests to communicate with a REST endpoint. I can upload single JSON and xml objects to it. To speed things up I want to upload multiple js

相关标签:
1条回答
  • 2020-12-18 16:34

    Do not use a dictionary, use a list of (key, value) tuples for your query parameters:

    params = [('title', 'file1'), ('title', 'file2'), ('title', 'file3')]
    

    otherwise you'll end up with just the one key.

    You should not set the Content-Type header; requests will set that correctly for you when you use the files parameter; that way the correct boundary will also be included. You should never set the boundary directly yourself, really:

    params = [('title', 'file1'), ('title', 'file2'), ('title', 'file3')]
    r = requests.post('http://example.com', 
                      files=files, headers=headers, params=params)
    

    You can set headers per file part by adding a 4th element to the per-file tuple for extra headers, but in your case, you should not try to set the Content-Disposition header yourself; it'll be overwritten anyway.

    Introspecting a prepared request object then gives you:

    >>> import requests
    >>> from pprint import pprint
    >>> files = {'file1': ('foo.txt', 'foo\ncontents\n','text/plain'), 
    ...           'file2': ('bar.txt', 'bar contents', 'text/plain'),
    ...           'file3': ('baz.txt', 'baz contents', 'text/plain')}
    >>> headers = {'Content-Disposition': 'attachment'}
    >>> params = [('title', 'file1'), ('title', 'file2'), ('title', 'file3')]
    >>> r = requests.Request('POST', 'http://example.com',
    ...                      files=files, headers=headers, params=params)
    >>> prepared = r.prepare()
    >>> prepared.url
    'http://example.com/?title=file1&title=file2&title=file3'
    >>> pprint(dict(prepared.headers))
    {'Content-Disposition': 'attachment',
     'Content-Length': '471',
     'Content-Type': 'multipart/form-data; boundary=7312ccd96db94419bf1d97f2c54bbad1'}
    >>> print prepared.body
    --7312ccd96db94419bf1d97f2c54bbad1
    Content-Disposition: form-data; name="file3"; filename="baz.txt"
    Content-Type: text/plain
    
    baz contents
    --7312ccd96db94419bf1d97f2c54bbad1
    Content-Disposition: form-data; name="file2"; filename="bar.txt"
    Content-Type: text/plain
    
    bar contents
    --7312ccd96db94419bf1d97f2c54bbad1
    Content-Disposition: form-data; name="file1"; filename="foo.txt"
    Content-Type: text/plain
    
    foo
    contents
    
    --7312ccd96db94419bf1d97f2c54bbad1--
    

    If you absolutely must have multipart/mixed and not multipart/form-data, you'll have to build the POST body yourself and set the headers from that. The included urllib3 tools should be able to do this for you:

    from requests.packages.urllib3.fields import RequestField
    from requests.packages.urllib3.filepost import encode_multipart_formdata
    
    fields = []    
    for name, (filename, contents, mimetype) in files.items():
        rf = RequestField(name=name, data=contents,
                          filename=filename)
        rf.make_multipart(content_disposition='attachment', content_type=mimetype)
        fields.append(rf)
    
    post_body, content_type = encode_multipart_formdata(fields)
    content_type = ''.join(('multipart/mixed',) + content_type.partition(';')[1:])
    
    headers = {'Content-Type': content_type}
    requests.post('http://example.com', data=post_body, headers=headers, params=params)
    

    or you could use the email package to do the same:

    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    
    body = MIMEMultipart()
    for name, (filename, contents, mimetype) in files.items():
        part = MIMEText(contents, _subtype=mimetype.partition('/')[-1], _charset='utf8')
        part.add_header('Content-Disposition', 'attachment', filename=filename)
        body.attach(part)
    
    post_body = body.as_string().partition('\n\n')[-1]
    content_type = body['content-type']
    
    headers = {'Content-Type': content_type}
    requests.post('http://example.com', data=post_body, headers=headers, params=params)
    

    but take into account that this method expects you to set a character set (I assumed UTF-8 for JSON and XML) and that it'll more than likely use Base64 encoding for the contents:

    >>> body = MIMEMultipart()
    >>> for name, (filename, contents, mimetype) in files.items():
    ...     part = MIMEText(contents, _subtype=mimetype.partition('/')[-1], _charset='utf8')
    ...     part.add_header('Content-Disposition', 'attachment', filename=filename)
    ...     body.attach(part)
    ... 
    >>> post_body = body.as_string().partition('\n\n')[-1]
    >>> content_type = body['content-type']
    >>> print post_body
    --===============1364782689914852112==
    MIME-Version: 1.0
    Content-Type: text/plain; charset="utf-8"
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment; filename="baz.txt"
    
    YmF6IGNvbnRlbnRz
    
    --===============1364782689914852112==
    MIME-Version: 1.0
    Content-Type: text/plain; charset="utf-8"
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment; filename="bar.txt"
    
    YmFyIGNvbnRlbnRz
    
    --===============1364782689914852112==
    MIME-Version: 1.0
    Content-Type: text/plain; charset="utf-8"
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment; filename="foo.txt"
    
    Zm9vCmNvbnRlbnRzCg==
    
    --===============1364782689914852112==--
    
    >>> print content_type
    multipart/mixed; boundary="===============1364782689914852112=="
    
    0 讨论(0)
提交回复
热议问题