【问题标题】:How can I serve temporary files from Python Pyramid如何从 Python Pyramid 提供临时文件
【发布时间】:2014-11-27 23:52:05
【问题描述】:

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

# 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

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

【问题讨论】:

    标签: python pyramid


    【解决方案1】:

    您不想将文件指针设置为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 上的 tmpfile 并为FileResponse 提供完整路径是一种胜利,而不是尝试将文件指针传递给FileIter

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

    【讨论】:

    • +1 我没有意识到有一个FileResponse 对象。它是否也会删除文件?如果没有,如果将NamedTemporary文件的路径传递给FileResponse,这样文件一旦关闭就会被删除,是否可行?
    • FileResponse 不提供删除文件的任何支持。如果NamedTemporaryFile 支持通过close 方法删除底层文件,那么您可以将其包装在FileIter 中。
    • 酷,是的,默认情况下NamedTemporaryFile 关闭后将被删除:)
    • 谢谢@MichaelMerickel。总是很高兴得到你的回答。
    【解决方案2】:

    更新:

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

    【讨论】:

    • 谢谢。我从您的回答中学到了很多东西,我将使用 NamedTemporaryFile 和 Mike 的回答。干杯!
    • 顺便说一句,它就像一个魅力。我只是根据您的回答创建了NamedTemporaryFile,并将name 传递给了迈克的FileResponse。文件已送达​​,然后被删除。是的!
    • @MFB 很高兴知道! (因为我没有测试我发布的内容:D)
    • 我认为这个答案更好 - 这是一个完整的例子,并展示了如何摆脱临时文件。最佳答案显示了如何返回普通文件而不是临时文件。谢谢!
    • @SamWatkins 谢谢山姆!很高兴听到您发现它很有用!
    【解决方案3】:

    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
    

    希望对您有所帮助。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        del response.app_iter 
        

        【讨论】:

          【解决方案6】:

          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'
              )
          

          希望对你有所帮助;)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-12-11
            • 1970-01-01
            • 1970-01-01
            • 2019-05-17
            相关资源
            最近更新 更多