Eric Wilson
Eric Wilson

Reputation: 59405

How to serve file with cherrypy and delete the file from server?

I need to create a file on the server, serve it to the client, and I would prefer to delete the file from the server afterwards.

Here's what I'm currently trying:

def myaction():
    fs = facts.generatefacts(int(low),int(high),int(amount),op)
    filename = 'test.txt'
    FILE = open(filename,'w')
    FILE.writelines('my\nstuff\nhere')
    FILE.close()
    RETURN_FILE = open(filename,'r')
    return serve_fileobj(RETURN_FILE,disposition='attachment',
                         content_type='.txt',name=filename)

myaction.exposed = True

There are several things about this that I don't like. I wouldn't think that I would have to open the file twice, for example. I would expect that there is a way to write content directly to the response object, without ever creating a file object, but that isn't my question today.

The above code accomplishes what I want, but leaves a file behind. If I delete the file before returning the response, then (of course) the file isn't found.

Is there a way to remove this file as soon as it has been served?

I'm coming from the Java world, so I'm a bit disoriented, and any other suggestions to improve the above are appreciated.

Upvotes: 1

Views: 3962

Answers (3)

user1938995
user1938995

Reputation: 11

This worked for me:

class Handler:

    ...

    def download_complete(self)
        os.unlink(cherrypy.request.zipFileName)

    def download(self, path)
        zipFileName = createZip(path)
        cherrypy.request.zipFileName = zipFileName
        cherrypy.request.hooks.attach('on_end_request', self.download_complete)
        return cherrypy.lib.static.serve_download(zipFileName)

Upvotes: 0

Rasjid Wilcox
Rasjid Wilcox

Reputation: 711

The solution below uses weak references to clean up temporary files (and in this case temporary directories) once the file is fully sent by cherrypy.

I've used temporary directories, since that allows for processes that may create several files before sending the final result (eg, returning a zipped up excel file for example), but for simple cases just a single temporary file would work fine.

import cherrypy
from cherrypy import expose

import zipfile
import weakref
import shutil
import os
import tempfile

PARENT_TEMP_DATA_DIR = '/tmp/cherrypy_data_files'

def single_file_zip(filepath):
    'Give a filepath, creates a zip archive in the same directory, with just the single file inside'
    filename = os.path.basename(filepath)
    zipname = '%s.zip' % os.path.splitext(filename)[0]
    if filename.lower() == zipname.lower():
        raise ValueError("Can't use .zip file as source")
    zippath = os.path.join(os.path.dirname(filepath), zipname)
    zf = zipfile.ZipFile(zippath, mode='w', compression=zipfile.ZIP_DEFLATED)
    zf.write(filepath, filename)
    zf.close()
    return zippath

class DataFiles(object):
    def __init__(self):
        self.weak_references = {}
    def cleanup(self, wr):
        if wr in self.weak_references:
            filepath = self.weak_references[wr]
            if os.path.isfile(filepath):
                try:
                    os.remove(filepath)
                except Exception:
                    pass
            if os.path.isdir(filepath):
                shutil.rmtree(filepath, ignore_errors=True)
            self.weak_references.pop(wr)
    @expose
    def index(self):
        tempdir = os.path.abspath(tempfile.mkdtemp(dir=PARENT_TEMP_DATA_DIR))
        txt_path = os.path.join(tempdir, 'my_data.txt')
        with open(txt_path, 'wb') as fh:
            fh.write('lots of data here\n')
        zip_path = single_file_zip(txt_path)
        os.remove(txt_path)  # not strictly needed, as the cleanup routine would remove this anyway
        result = cherrypy.lib.static.serve_download(zip_path)

        # the weak-reference allows automatic cleanup of the temp dir once the file is served
        wr = weakref.ref(result, self.cleanup)
        self.weak_references[wr] = tempdir

        return result

if __name__=='__main__':
    # on startup clean up any prior temporary data 
    tempdir_parent = PARENT_TEMP_DATA_DIR
    print 'Clearing %s' % tempdir_parent
    if not os.path.exists(tempdir_parent):
        os.mkdir(tempdir_parent)
    for filename in os.listdir(tempdir_parent):
        filepath = os.path.join(tempdir_parent, filename)
        if os.path.isfile(filepath):
            print 'Deleting file %s' % filepath
            os.remove(filepath)
        if os.path.isdir(filepath):
            print 'Removing directory %s and all contents' % filepath
            shutil.rmtree(filepath, ignore_errors=True)

    # start CherryPy
    cherrypy.quickstart(DataFiles())

Upvotes: 1

varela
varela

Reputation: 1331

1) You can move file to temporary folder and remove all files older 0.5 hours

2) You can try

result = serve_fileobj(RETURN_FILE,disposition='attachment',
                         content_type='.txt',name=filename)
os.unlink(filename)
return result

3) Try to use StringIO file object that can wrap string to look like a file.

Upvotes: 2

Related Questions