10

目前,我只是提供这样的文件:

# 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

这样做的问题是我无法关闭,或者更好的是,在返回响应后删除文件。该文件被孤立。我可以想到一些解决这个问题的方法,但我希望在某个地方有一个标准的方法。任何帮助都是极好的。

4

6 回答 6

11

更新:

请参阅 Michael Merickel 的答案以获得更好的解决方案和解释。

如果您希望文件被删除一次response返回,您可以尝试以下操作:

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

您可以考虑使用NamedTemporaryFile

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

设置delete=True为文件一关闭就被删除。

现在,在您的帮助下,with您始终可以保证文件将被关闭并因此被删除:

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
于 2012-10-18T08:50:17.133 回答
11

您不想将文件指针设置为app_iter. 这将导致 WSGI 服务器逐行读取文件(与 相同for line in file),这通常不是控制文件上传的最有效方式(假设每行一个字符)。Pyramid 支持的文件服务方式是 via pyramid.response.FileResponse。您可以通过传递文件对象来创建其中之一。

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

另一种选择是将文件指针传递给app_iter但将其包装在pyramid.response.FileIter对象中,这将使用合理的块大小来避免仅逐行读取文件。

WSGI 规范有严格的要求,即包含close方法的响应迭代器在响应结束时被调用。因此设置response.app_iter = open(...)不应导致任何内存泄漏。两者都FileResponse支持FileIter一种close方法,因此将按预期进行清​​理。

作为对这个答案的一个小更新,我想我会解释为什么FileResponse采用文件路径而不是文件指针。WSGI 协议为服务器提供了一种可选的能力,以提供一种优化的机制来通过environ['wsgi.file_wrapper']. FileResponse如果您的 WSGI 服务器提供了该支持,它将自动处理此问题。考虑到这一点,您发现将数据保存到 ramdisk 上的 tmpfile 并提供FileResponse完整路径是一种胜利,而不是尝试将文件指针传递给FileIter.

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

于 2012-10-18T15:56:42.207 回答
2

Michael 和 Kay 的响应组合在 Linux/Mac 下效果很好,但在 Windows 下不起作用(用于自动删除)。Windows 不喜欢 FileResponse 试图打开已经打开的文件这一事实(参见 NamedTemporaryFile 的描述)。

我通过创建一个 FileDecriptorResponse 类来解决这个问题,该类本质上是 FileResponse 的副本,但采用打开的 NamedTemporaryFile 的文件描述符。只需将 open 替换为 seek(0) 并将所有基于路径的调用 (last_modified, content_length) 替换为其 fstat 等效项。

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

希望这会有所帮助。

于 2012-12-04T01:46:22.863 回答
1

还有repoze.filesafe会为你生成一个临时文件,并在最后删除它。我用它来保存上传到我的服务器的文件。也许它对你也有用。

于 2012-10-18T14:35:44.867 回答
0

因为您的对象响应持有文件“/temp/XML_Export_%s.xml”的文件句柄。使用del语句删除句柄 'response.app_iter'。

del response.app_iter 
于 2012-10-18T09:02:07.847 回答
0

Michael Merickel 和 Kay Zhu 都很好。我发现我还需要在 NamedTemporaryFile 的开始处重置文件位置,然后再将其传递给响应,因为似乎响应从文件中的实际位置开始,而不是从开头(没关系,你只需要现在它)。使用带有删除集的 NamedTemporaryFile,您无法关闭并重新打开它,因为它会删除它(并且无论如何您都无法重新打开它),因此您需要使用以下内容:

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

希望能帮助到你 ;)

于 2014-11-27T15:52:05.667 回答