Send file using POST from a Python script
Asked Answered
G

10

174

Is there a way to send a file using POST from a Python script?

Goldoni answered 16/9, 2008 at 1:16 Comment(0)
O
251

From: https://requests.readthedocs.io/en/latest/user/quickstart/#post-a-multipart-encoded-file

Requests makes it very simple to upload Multipart-encoded files:

with open('report.xls', 'rb') as f:
    r = requests.post('http://httpbin.org/post', files={'report.xls': f})

That's it. I'm not joking - this is one line of code. The file was sent. Let's check:

>>> r.text
{
  "origin": "179.13.100.4",
  "files": {
    "report.xls": "<censored...binary...data>"
  },
  "form": {},
  "url": "http://httpbin.org/post",
  "args": {},
  "headers": {
    "Content-Length": "3196",
    "Accept-Encoding": "identity, deflate, compress, gzip",
    "Accept": "*/*",
    "User-Agent": "python-requests/0.8.0",
    "Host": "httpbin.org:80",
    "Content-Type": "multipart/form-data; boundary=127.0.0.1.502.21746.1321131593.786.1"
  },
  "data": ""
}
Omission answered 19/4, 2012 at 18:40 Comment(7)
I'm trying the same thing & its working fine if file size is less than ~1.5 MB. else its throwing an error.. please have look at here.Peplos
what am trying to do is login to some site using request which i have done successfully but now i want to upload a video after logging in and the form has a different fields to be filled before submission. So how should I pass those values like videos description,videos title etcAhner
You'd probably want to do with open('report.xls', 'rb') as f: r = requests.post('http://httpbin.org/post', files={'report.xls': f}) instead, so it closes the file again after opening.Dermatologist
This answer should be updated to include Hjulle's suggestion of using the context manager to ensure file is closed.Nootka
this is not working for me, it says '405 method not allowed.' with open(file_path, 'rb') as f: response = requests.post(url=url, data=f, auth=HTTPBasicAuth(username=id, password=password) )Enchiridion
I have a raw .jpg format file stored as ndarray variable, how can I upload this to server?Indeterminable
Can we send pdf's using this? Because isn't this only for text files?Gelatinate
D
32

Yes. You'd use the urllib2 module, and encode using the multipart/form-data content type. Here is some sample code to get you started -- it's a bit more than just file uploading, but you should be able to read through it and see how it works:

user_agent = "image uploader"
default_message = "Image $current of $total"

import logging
import os
from os.path import abspath, isabs, isdir, isfile, join
import random
import string
import sys
import mimetypes
import urllib2
import httplib
import time
import re

def random_string (length):
    return ''.join (random.choice (string.letters) for ii in range (length + 1))

def encode_multipart_data (data, files):
    boundary = random_string (30)

    def get_content_type (filename):
        return mimetypes.guess_type (filename)[0] or 'application/octet-stream'

    def encode_field (field_name):
        return ('--' + boundary,
                'Content-Disposition: form-data; name="%s"' % field_name,
                '', str (data [field_name]))

    def encode_file (field_name):
        filename = files [field_name]
        return ('--' + boundary,
                'Content-Disposition: form-data; name="%s"; filename="%s"' % (field_name, filename),
                'Content-Type: %s' % get_content_type(filename),
                '', open (filename, 'rb').read ())

    lines = []
    for name in data:
        lines.extend (encode_field (name))
    for name in files:
        lines.extend (encode_file (name))
    lines.extend (('--%s--' % boundary, ''))
    body = '\r\n'.join (lines)

    headers = {'content-type': 'multipart/form-data; boundary=' + boundary,
               'content-length': str (len (body))}

    return body, headers

def send_post (url, data, files):
    req = urllib2.Request (url)
    connection = httplib.HTTPConnection (req.get_host ())
    connection.request ('POST', req.get_selector (),
                        *encode_multipart_data (data, files))
    response = connection.getresponse ()
    logging.debug ('response = %s', response.read ())
    logging.debug ('Code: %s %s', response.status, response.reason)

def make_upload_file (server, thread, delay = 15, message = None,
                      username = None, email = None, password = None):

    delay = max (int (delay or '0'), 15)

    def upload_file (path, current, total):
        assert isabs (path)
        assert isfile (path)

        logging.debug ('Uploading %r to %r', path, server)
        message_template = string.Template (message or default_message)

        data = {'MAX_FILE_SIZE': '3145728',
                'sub': '',
                'mode': 'regist',
                'com': message_template.safe_substitute (current = current, total = total),
                'resto': thread,
                'name': username or '',
                'email': email or '',
                'pwd': password or random_string (20),}
        files = {'upfile': path}

        send_post (server, data, files)

        logging.info ('Uploaded %r', path)
        rand_delay = random.randint (delay, delay + 5)
        logging.debug ('Sleeping for %.2f seconds------------------------------\n\n', rand_delay)
        time.sleep (rand_delay)

    return upload_file

def upload_directory (path, upload_file):
    assert isabs (path)
    assert isdir (path)

    matching_filenames = []
    file_matcher = re.compile (r'\.(?:jpe?g|gif|png)$', re.IGNORECASE)

    for dirpath, dirnames, filenames in os.walk (path):
        for name in filenames:
            file_path = join (dirpath, name)
            logging.debug ('Testing file_path %r', file_path)
            if file_matcher.search (file_path):
                matching_filenames.append (file_path)
            else:
                logging.info ('Ignoring non-image file %r', path)

    total_count = len (matching_filenames)
    for index, file_path in enumerate (matching_filenames):
        upload_file (file_path, index + 1, total_count)

def run_upload (options, paths):
    upload_file = make_upload_file (**options)

    for arg in paths:
        path = abspath (arg)
        if isdir (path):
            upload_directory (path, upload_file)
        elif isfile (path):
            upload_file (path)
        else:
            logging.error ('No such path: %r' % path)

    logging.info ('Done!')
Defecate answered 16/9, 2008 at 1:21 Comment(2)
On python 2.6.6 I was getting an error in Multipart boundary parsing while using this code on Windows. I had to change from string.letters to string.ascii_letters as discussed at #2823816 for this to work. The requirement on boundary is discussed here: #147951Mightily
calling run_upload ({'server':'', 'thread':''}, paths=['/path/to/file.txt']) causes error in this line: upload_file (path) because "upload file" requires 3 parameters so I replaces it with this line upload_file (path, 1, 1)Fujimoto
D
6

Looks like python requests does not handle extremely large multi-part files.

The documentation recommends you look into requests-toolbelt.

Here's the pertinent page from their documentation.

Dumortierite answered 8/7, 2015 at 22:51 Comment(0)
L
4

The only thing that stops you from using urlopen directly on a file object is the fact that the builtin file object lacks a len definition. A simple way is to create a subclass, which provides urlopen with the correct file. I have also modified the Content-Type header in the file below.

import os
import urllib2
class EnhancedFile(file):
    def __init__(self, *args, **keyws):
        file.__init__(self, *args, **keyws)

    def __len__(self):
        return int(os.fstat(self.fileno())[6])

theFile = EnhancedFile('a.xml', 'r')
theUrl = "http://example.com/abcde"
theHeaders= {'Content-Type': 'text/xml'}

theRequest = urllib2.Request(theUrl, theFile, theHeaders)

response = urllib2.urlopen(theRequest)

theFile.close()


for line in response:
    print line
Leonerd answered 1/11, 2011 at 16:43 Comment(2)
@robert I test your code in Python2.7 but it doesn't work. urlopen(Request(theUrl, theFile, ...)) merely encodes the content of file as if a normal post but can not specify the correct form field. I even try variant urlopen(theUrl, urlencode({'serverside_field_name': EnhancedFile('my_file.txt')})), it uploads a file but (of course!) with incorrect content as <open file 'my_file.txt', mode 'r' at 0x00D6B718>. Did I miss something?Cephalad
Thanks for the answer . By using the above code I had transferred 2.2 GB raw image file by using PUT request into the webserver.Slut
R
2

Chris Atlee's poster library works really well for this (particularly the convenience function poster.encode.multipart_encode()). As a bonus, it supports streaming of large files without loading an entire file into memory. See also Python issue 3244.

Resnick answered 8/2, 2009 at 5:20 Comment(0)
H
2

I am trying to test django rest api and its working for me:

def test_upload_file(self):
        filename = "/Users/Ranvijay/tests/test_price_matrix.csv"
        data = {'file': open(filename, 'rb')}
        client = APIClient()
        # client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
        response = client.post(reverse('price-matrix-csv'), data, format='multipart')

        print response
        self.assertEqual(response.status_code, status.HTTP_200_OK)
Hellenhellene answered 10/5, 2016 at 15:23 Comment(1)
this code provides to a memory leak - you forgot to close() a file.Bandore
K
0

You may also want to have a look at httplib2, with examples. I find using httplib2 is more concise than using the built-in HTTP modules.

Kit answered 16/9, 2008 at 18:3 Comment(3)
There are no examples that show how to deal with file uploads.Jeffreyjeffreys
Link is outdated + no inlined example.Egress
It has since moved to github.com/httplib2/httplib2. On the other hand, nowadays I would probably recommend requests instead.Kit
E
0
def visit_v2(device_code, camera_code):
    image1 = MultipartParam.from_file("files", "/home/yuzx/1.txt")
    image2 = MultipartParam.from_file("files", "/home/yuzx/2.txt")
    datagen, headers = multipart_encode([('device_code', device_code), ('position', 3), ('person_data', person_data), image1, image2])
    print "".join(datagen)
    if server_port == 80:
        port_str = ""
    else:
        port_str = ":%s" % (server_port,)
    url_str = "http://" + server_ip + port_str + "/adopen/device/visit_v2"
    headers['nothing'] = 'nothing'
    request = urllib2.Request(url_str, datagen, headers)
    try:
        response = urllib2.urlopen(request)
        resp = response.read()
        print "http_status =", response.code
        result = json.loads(resp)
        print resp
        return result
    except urllib2.HTTPError, e:
        print "http_status =", e.code
        print e.read()
Epifaniaepifano answered 18/3, 2016 at 6:59 Comment(0)
T
0

I tried some of the options here, but I had some issue with the headers ('files' field was empty).

A simple mock to explain how I did the post using requests and fixing the issues:

import requests

url = 'http://127.0.0.1:54321/upload'
file_to_send = '25893538.pdf'

files = {'file': (file_to_send,
                  open(file_to_send, 'rb'),
                  'application/pdf',
                  {'Expires': '0'})}

reply = requests.post(url=url, files=files)
print(reply.text)

More at https://requests.readthedocs.io/en/latest/user/quickstart/

To test this code, you could use a simple dummy server as this one (thought to run in a GNU/Linux or similar):

import os
from flask import Flask, request, render_template

rx_file_listener = Flask(__name__)

files_store = "/tmp"
@rx_file_listener.route("/upload", methods=['POST'])
def upload_file():
    storage = os.path.join(files_store, "uploaded/")
    print(storage)
    
    if not os.path.isdir(storage):
        os.mkdir(storage)

    try:
        for file_rx in request.files.getlist("file"):
            name = file_rx.filename
            destination = "/".join([storage, name])
            file_rx.save(destination)
        
        return "200"
    except Exception:
        return "500"

if __name__ == "__main__":
    rx_file_listener.run(port=54321, debug=True)
Thedrick answered 20/10, 2022 at 15:35 Comment(0)
S
0

python 3.11.3

Client:

import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder


        session = requests.Session()
        with open(local_file_path, 'rb') as file_obj:
            multipart_data = MultipartEncoder(
                fields={
                    'file': (os.path.basename(local_file_path), file_obj, your_file_content_type)}
            )
            request_headers["Content-Type"] = multipart_data.content_type
            response = session.post(url=upload_server_url, headers=request_headers, data=multipart_data)
        response.raise_for_status()

The 'file' is the form field name, which is the parameter name(@RequestParam("file")) of the server side. data=multipart_data will put file data into the request body.

Server Side, Java, Springboot:

@Slf4j
@Controller
@CrossOrigin
@RequestMapping("/myapi")
public class MultiPartController {

    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public String upload(HttpServletRequest httpServletRequest, @RequestParam("file")MultipartFile f) throws IOException {
        log.info(":::::  {}, {}, {}, {}", f.getName(), f.getContentType(), f.getSize(), f.getOriginalFilename());
        
        return "ok";
    }
}

Ref: https://github.com/requests/toolbelt/tree/1.0.0?tab=readme-ov-file#multipartform-data-encoder

How to upload file with python requests?

Shortwave answered 29/2 at 2:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.