目前,我只是提供这样的文件:
# 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方法,但我希望有一个标准的方法在那里。任何帮助都会很棒。
答案 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(...)
不应导致任何内存泄漏。 FileResponse
和FileIter
都支持close
方法,因此会按预期进行清理。
作为对此答案的一个小更新,我想我会解释为什么FileResponse
采用文件路径而不是文件指针。 WSGI协议为服务器提供了一种可选功能,可通过environ['wsgi.file_wrapper']
提供优化的机制来提供静态文件。如果您的WSGI服务器提供了该支持,FileResponse
将自动处理此问题。考虑到这一点,您会发现将数据保存到ramdisk上的tmp文件并为FileResponse
提供完整路径,而不是尝试将文件指针传递给FileIter
是一种胜利。
答案 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)
f = tempfile.NamedTemporaryFile()
#fill your file here
f.seek(0, 0)
response = FileResponse(
f,
request=request,
content_type='application/csv'
)
希望它有所帮助;)