如何从Python Pyramid中提供临时文件

时间:2012-10-18 07:14:44

标签: python pyramid

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

# 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

这个问题是我无法关闭,甚至更好地在返回响应后删除文件。该文件被孤立。我可以想到一些关于这方面的hacky方法,但我希望有一个标准的方法在那里。任何帮助都会很棒。

6 个答案:

答案 0 :(得分:10)

您不希望将文件指针设置为app_iter。这将导致WSGI服务器逐行读取文件(与for line in file相同),这通常不是控制文件上载的最有效方法(想象每行一个字符)。 Pyramid支持的文件服务方式是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(...)不应导致任何内存泄漏。 FileResponseFileIter都支持close方法,因此会按预期进行清​​理。

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

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

答案 1 :(得分:9)

<强>更新

请参阅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

答案 2 :(得分:2)

Michael和Kay的反应组合在Linux / Mac下运行良好但在Windows下无效(用于自动删除)。 Windows不喜欢FileResponse尝试打开已经打开的文件这一事实(参见NamedTemporaryFile的说明)。

我通过创建一个FileDecriptorResponse类来解决这个问题,该类本质上是FileResponse的副本,但是接受了打开NamedTemporaryFile的文件描述符。只需用seek(0)和所有基于路径的调用(last_modified,content_length)替换open与其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

希望这有帮助。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

del response.app_iter 

答案 5 :(得分:0)

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

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

希望它有所帮助;)