1

I was using the answer from this question and saw the comment:

   raw = data.file.read() # This is dangerous for big files

How do I upload the file without doing this? My code so far is:

@bottle.route('/uploadLO', method='POST')
def upload_lo():
    upload_dir = get_upload_dir_path()
    files = bottle.request.files
    print files, type(files)
    if(files is not None):
        file = files.file
        print file.filename, type(file)
        target_path = get_next_file_name(os.path.join(upload_dir, file.filename))
        print target_path
        shutil.copy2(file.read(), target_path)  #does not work. Tried it as a replacement for php's move_uploaded_file
    return None

which gives this output:

127.0.0.1 - - [03/Apr/2014 09:29:37] "POST /uploadLO HTTP/1.1" 500 1418
Traceback (most recent call last):
  File "C:\Python27\lib\site-packages\bottle.py", line 862, in _handle
    return route.call(**args)
  File "C:\Python27\lib\site-packages\bottle.py", line 1727, in wrapper
    rv = callback(*a, **ka)
  File "C:\dev\project\src\mappings.py", line 83, in upload_lo
    shutil.copy2(file.read(), target_path)
AttributeError: 'FileUpload' object has no attribute 'read'

I am using python bottle v.12, dropzone.min.js, and mongodb. I also am using this tutorial:

http://www.startutorial.com/articles/view/how-to-build-a-file-upload-form-using-dropzonejs-and-php

Community
  • 1
  • 1
Jeff
  • 4,285
  • 15
  • 63
  • 115

2 Answers2

3

This is called "file slurping":

raw = data.file.read() 

and you don't want to do it (at least in this case).

Here's a better way to read a binary file of unknown (possibly large) size:

data_blocks = []

buf = data.file.read(8192)
while buf:
    data_blocks.append(buf)
    buf = data.file.read(8192)

data = ''.join(data_blocks)

You may also want to stop iterating if the accumulated size exceeds some threshold.

Hope that helps!


PART 2

You asked about limiting the file size, so here's an amended version that does that:

MAX_SIZE = 10 * 1024 * 1024 # 10MB
BUF_SIZE = 8192

# code assumes that MAX_SIZE >= BUF_SIZE

data_blocks = []
byte_count = 0

buf = f.read(BUF_SIZE)
while buf:
    byte_count += len(buf)

    if byte_count > MAX_SIZE:
        # if you want to just truncate at (approximately) MAX_SIZE bytes:
        break
        # or, if you want to abort the call
        raise bottle.HTTPError(413, 'Request entity too large (max: {} bytes)'.format(MAX_SIZE))

    data_blocks.append(buf)
    buf = f.read(BUF_SIZE)

data = ''.join(data_blocks)

It's not perfect, but it's simple and IMO good enough.

ron rothman
  • 17,348
  • 7
  • 41
  • 43
  • Is 8192 a standard of some sort (still reading the document you linked)? Also, since the js I am using has some sort of limit on it, would I need to limit the size? – Jeff Apr 03 '14 at 20:31
  • Nah, 8Kb is just a convention since that's the size that disk blocks are (were?). You can use any (reasonable) value in your case. – ron rothman Apr 04 '14 at 00:34
  • Not sure what you mean by "js... has some sort of limit" but IMHO this code would be considered poorly designed if it did not protect itself from huge files. I'll edit my answer to show how you might do that. – ron rothman Apr 04 '14 at 00:35
  • Oh, I just meant that the js I am using looks like it limits the size of files to a few hundred mg, but I'll def implement what you suggested. Thanks! – Jeff Apr 04 '14 at 01:30
  • 1
    Bottle stores file uploads in `request.files` as [FileUpload](http://bottlepy.org/docs/dev/api.html#bottle.FileUpload) instances, along with some metadata about the upload. FileUpload has a very handy [save](http://bottlepy.org/docs/dev/api.html#bottle.FileUpload.save) method that perform the "file slurping" for you. It does not check the maximum file upload though, but IMHO it's better to have nginx in front to perform this kind of limitation check. – Le Hibou May 18 '15 at 12:56
1

To add to ron.rothman's excellent answer...to fix your error message try this

@bottle.route('/uploadLO', method='POST')
def upload_lo():
    upload_dir = get_upload_dir_path()
    files = bottle.request.files

    # add this line
    data = request.files.data

    print files, type(files)

    if(files is not None):
        file = files.file
        print file.filename, type(file)
        target_path = get_next_file_name(os.path.join(upload_dir, file.filename))
        print target_path

        # add Ron.Rothman's code
        data_blocks = []
        buf = data.file.read(8192)
        while buf:
            data_blocks.append(buf)
            buf = data.file.read(8192)

        my_file_data = ''.join(data_blocks)
        # do something with the file data, like write it to target
        file(target_path,'wb').write(my_file_data)

    return None
Genome
  • 1,106
  • 8
  • 10
  • Thanks! I'll see if this works tonight! At your `#do something` comment, Would the user have to wait for the something to complete. Would you typically spawn a thread to take care of the action? – Jeff Apr 03 '14 at 20:30
  • No, I just added that as a placeholder. My suggestion is the line below i.e. write it to your target path. Happy to help. – Genome Apr 03 '14 at 20:57