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 サーバーはファイルを 1 行ずつ ( と同じfor line in file) 読み取ることになりますが、これは通常、ファイルのアップロードを制御する最も効率的な方法ではありません (1 行に 1 文字を想像してください)。Pyramid がサポートするファイルの提供方法は、pyramid.response.FileResponse. これらのいずれかを作成するには、ファイル オブジェクトを渡します。

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

もう 1 つのオプションは、ファイル ポインターを渡しapp_iterてオブジェクトにラップすることですpyramid.response.FileIter。これにより、適切なブロック サイズを使用して、ファイルを 1 行ずつ読み取らないようにします。

closeWSGI 仕様には、メソッド含む応答反復子が応答の最後に呼び出されるという厳格な要件があります。したがって、設定response.app_iter = open(...)によってメモリリークが発生することはありません。FileResponseとはどちらFileIterもメソッドをサポートしているためclose、期待どおりにクリーンアップされます。

FileResponseこの回答のマイナーな更新として、ファイル ポインターではなくファイル パスを使用する理由を説明したいと思います。WSGI プロトコルは、 を介して静的ファイルを提供するための最適化されたメカニズムを提供するオプション機能をサーバーに提供しますenviron['wsgi.file_wrapper']FileResponseWSGI サーバーがそのサポートを提供している場合、これは自動的に処理されます。これを念頭に置いて、データを RAM ディスク上の 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 の説明を参照)。

私は、基本的に FileResponse のコピーである FileDecriptorResponse クラスを作成することで、これを回避しましたが、開いている 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

Object応答がファイル '/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 に答える