How can I serve temporary files from Python Pyramid

岁酱吖の 提交于 2019-12-12 07:15:06

问题


Currently, I'm just serving files like this:

# view callable
def export(request):
    response = Response(content_type='application/csv')
    # use datetime in filename to avoid collisions
    f = open('/temp/XML_Export_%s.xml' % datetime.now(), 'r')
        # this is where I usually put stuff in the file
    response.app_iter = f
    response.headers['Content-Disposition'] = ("attachment; filename=Export.xml")
    return response

The problem with this is that I can't close or, even better, delete the file after the response has been returned. The file gets orphaned. I can think of some hacky ways around this, but I'm hoping there's a standard way out there somewhere. Any help would be awesome.


回答1:


You do not want to set a file pointer as the app_iter. This will cause the WSGI server to read the file line by line (same as for line in file), which is typically not the most efficient way to control a file upload (imagine one character per line). Pyramid's supported way of serving files is via pyramid.response.FileResponse. You can create one of these by passing a file object.

response = FileResponse('/some/path/to/a/file.txt')
response.headers['Content-Disposition'] = ...

Another option is to pass a file pointer to app_iter but wrap it in the pyramid.response.FileIter object, which will use a sane block size to avoid just reading the file line by line.

The WSGI specification has strict requirements that response iterators which contain a close method will be invoked at the end of the response. Thus setting response.app_iter = open(...) should not cause any memory leaks. Both FileResponse and FileIter also support a close method and will thus be cleaned up as expected.

As a minor update to this answer I thought I'd explain why FileResponse takes a file path and not a file pointer. The WSGI protocol provides servers an optional ability to provide an optimized mechanism for serving static files via environ['wsgi.file_wrapper']. FileResponse will automatically handle this if your WSGI server has provided that support. With this in mind, you find it to be a win to save your data to a tmpfile on a ramdisk and providing the FileResponse with the full path, instead of trying to pass a file pointer to FileIter.

http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/api/response.html#pyramid.response.FileResponse




回答2:


Update:

Please see Michael Merickel's answer for a better solution and explanation.

If you want to have the file deleted once response is returned, you can try the following:

import os
from datetime import datetime
from tempfile import NamedTemporaryFile

# view callable
def export(request):
    response = Response(content_type='application/csv')
    with NamedTemporaryFile(prefix='XML_Export_%s' % datetime.now(),
                            suffix='.xml', delete=True) as f:
        # this is where I usually put stuff in the file
        response = FileResponse(os.path.abspath(f.name))
        response.headers['Content-Disposition'] = ("attachment; filename=Export.xml")
        return response

You can consider using NamedTemporaryFile:

NamedTemporaryFile(prefix='XML_Export_%s' % datetime.now(), suffix='.xml', delete=True)

Setting delete=True so that the file is deleted as soon as it is closed.

Now, with the help of with you can always have the guarantee that the file will be closed, and hence deleted:

from tempfile import NamedTemporaryFile
from datetime import datetime

# view callable
def export(request):
    response = Response(content_type='application/csv')
    with NamedTemporaryFile(prefix='XML_Export_%s' % datetime.now(),
                            suffix='.xml', delete=True) as f:
        # this is where I usually put stuff in the file
        response.app_iter = f
        response.headers['Content-Disposition'] = ("attachment; filename=Export.xml")
        return response



回答3:


The combination of Michael and Kay's response works great under Linux/Mac but won't work under Windows (for auto-deletion). Windows doesn't like the fact that FileResponse tries to open the already open file (see description of NamedTemporaryFile).

I worked around this by creating a FileDecriptorResponse class which is essentially a copy of FileResponse, but takes the file descriptor of the open NamedTemporaryFile. Just replace the open with a seek(0) and all the path based calls (last_modified, content_length) with their fstat equivalents.

class FileDescriptorResponse(Response):
"""
A Response object that can be used to serve a static file from an open
file descriptor. This is essentially identical to Pyramid's FileResponse
but takes a file descriptor instead of a path as a workaround for auto-delete
not working with NamedTemporaryFile under Windows.

``file`` is a file descriptor for an open file.

``content_type``, if passed, is the content_type of the response.

``content_encoding``, if passed is the content_encoding of the response.
It's generally safe to leave this set to ``None`` if you're serving a
binary file.  This argument will be ignored if you don't also pass
``content-type``.
"""
def __init__(self, file, content_type=None, content_encoding=None):
    super(FileDescriptorResponse, self).__init__(conditional_response=True)
    self.last_modified = fstat(file.fileno()).st_mtime
    if content_type is None:
        content_type, content_encoding = mimetypes.guess_type(path,
                                                              strict=False)
    if content_type is None:
        content_type = 'application/octet-stream'
    self.content_type = content_type
    self.content_encoding = content_encoding
    content_length = fstat(file.fileno()).st_size
    file.seek(0)
    app_iter = FileIter(file, _BLOCK_SIZE)
    self.app_iter = app_iter
    # assignment of content_length must come after assignment of app_iter
    self.content_length = content_length

Hope that's helpful.




回答4:


There is also repoze.filesafe which will take care of generating a temporary file for you, and delete it at the end. I use it for saving files uploaded to my server. Perhaps it can be useful to you too.




回答5:


Because your Object response is holding a file handle for the file '/temp/XML_Export_%s.xml'. Use del statement to delete handle 'response.app_iter'.

del response.app_iter 



回答6:


both Michael Merickel and Kay Zhu are fine. I found out that I also need to reset file position at the begninnign of the NamedTemporaryFile before passing it to response, as it seems that the response starts from the actual position in the file and not from the beginning (It's fine, you just need to now it). With NamedTemporaryFile with deletion set, you can not close and reopen it, because it would delete it (and you can't reopen it anyway), so you need to use something like this:

f = tempfile.NamedTemporaryFile()
#fill your file here
f.seek(0, 0)
response = FileResponse(
    f,
    request=request,
    content_type='application/csv'
    )

hope it helps ;)



来源:https://stackoverflow.com/questions/12949077/how-can-i-serve-temporary-files-from-python-pyramid

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!