2012-10-18 8 views
8

Derzeit bin ich nur Dateien wie diese dienen:Wie kann ich dienen temporäre Dateien von 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 

Das Problem dabei ist, dass ich nicht in der Nähe oder, noch besser, löschen Sie die Datei nach Die Antwort wurde zurückgegeben. Die Datei wird verwaist. Ich kann mir ein paar hacky Wege vorstellen, aber ich hoffe, dass es irgendwo einen Standard gibt. Jede Hilfe wäre großartig.

Antwort

9

Sie möchten keinen Dateizeiger als festlegen. Dies führt dazu, dass der WSGI-Server Zeile für Zeile die Datei liest (wie for line in file), was normalerweise nicht der effizienteste Weg ist, um einen Datei-Upload zu steuern (stellen Sie sich ein Zeichen pro Zeile vor). Pyramid unterstützt Dateien über die Datei pyramid.response.FileResponse. Sie können eines davon erstellen, indem Sie ein Dateiobjekt übergeben.

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

Eine andere Möglichkeit ist es, einen Dateizeiger auf app_iter passieren, aber es in der pyramid.response.FileIter Objekt wickeln, die eine vernünftige Blockgröße verwenden wird nur zu vermeiden, indem Sie die Datei Zeile für Zeile lesbar.

Die WSGI-Spezifikation hat strenge Anforderungen, dass Antwort-Iteratoren, die eine close Methode enthalten, am Ende der Antwort aufgerufen werden. Die Einstellung response.app_iter = open(...) sollte daher keine Speicherlecks verursachen. Sowohl FileResponse als auch FileIter unterstützen auch eine close Methode und werden daher wie erwartet bereinigt.

Als kleines Update zu dieser Antwort dachte ich, ich würde erklären, warum FileResponse einen Dateipfad und keinen Dateizeiger nimmt.Das WSGI-Protokoll bietet Servern eine optionale Möglichkeit, einen optimierten Mechanismus zum Bereitstellen statischer Dateien über environ['wsgi.file_wrapper'] bereitzustellen. FileResponse wird automatisch dies behandeln, wenn Ihr WSGI-Server diese Unterstützung bereitgestellt hat. In diesem Sinne ist es ein Gewinn, Ihre Daten in einer tmpfile auf einer Ramdisk zu speichern und FileResponse mit dem vollständigen Pfad zu versehen, anstatt zu versuchen, einen Dateizeiger an FileIter zu übergeben.

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

+1

+1 Ich habe nicht bemerkt, dass es ein 'FileResponse'-Objekt gibt. Löscht es auch die Datei? Wenn nicht, würde es funktionieren, wenn der Pfad zur 'NamedTemporary' Datei an' FileResponse' übergeben wird, so dass die Datei nach dem Schließen gelöscht wird? –

+2

'FileResponse' bietet keine Unterstützung für das Löschen von Dateien. Wenn 'NamedTemporaryFile' das Löschen der zugrunde liegenden Datei über die 'close'-Methode unterstützt, können Sie diese in einen 'FileIter' einbinden. –

+1

Cool, ja standardmäßig 'NamedTemporaryFile' wird gelöscht, sobald es geschlossen ist :) –

8

Update:

Bitte sehen Michael Merickel Antwort für eine bessere Lösung und Erklärung.

Wenn Sie die Datei einmal response gelöscht haben wollen zurückgegeben wird, können Sie versuchen, die folgenden:

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 

Sie betrachten können NamedTemporaryFile mit:

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

Einstellung delete=True so dass Die Datei wird gelöscht, sobald sie geschlossen wird.

nun mit Hilfe von with können Sie immer die Garantie, dass die Datei geschlossen wird, und daher gestrichen:

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 
+0

Danke. Ich habe viel von Ihrer Antwort gelernt und werde die "NamedTemporaryFile" in Verbindung mit Mikes Antwort verwenden. Prost! – MFB

+0

@MFB Sie sind willkommen :) Froh, zu helfen! –

+0

BTW, es funktioniert wie ein Charme. Ich habe einfach 'NamedTemporaryFile' nach deiner Antwort erstellt und den' Name' an Mikes 'FileResponse' übergeben. Die Datei wird dann gelöscht. Yessssss! – MFB

0

Da Ihr Objekt Antwort ist eine Datei-Handle für die Datei halten ' /temp/XML_Export_%.xml '. Verwenden Sie del Anweisung zum Löschen von Handle 'response.app_iter'.

del response.app_iter 
1

Es gibt auch repoze.filesafe, die dafür sorgt, wird für Dich eine temporäre Datei zu erzeugen, und löschen Sie es am Ende. Ich verwende es zum Speichern von Dateien auf meinem Server hochgeladen. Vielleicht kann es dir auch nützlich sein.

2

Die Kombination von Michael und Kay Antwort funktioniert gut unter Linux/Mac wird aber unter Windows (für Auto-Löschung) nicht funktionieren. Windows mag es nicht, dass FileResponse versucht, die bereits geöffnete Datei zu öffnen (siehe Beschreibung von NamedTemporaryFile).

Ich arbeitete um dies durch Erstellen einer FileDecriptorResponse-Klasse, die im Wesentlichen eine Kopie von FileResponse ist, aber den Dateideskriptor der geöffneten NamedTemporaryFile. Ersetze einfach das open mit einem seek (0) und alle pfadbasierten Aufrufe (last_modified, content_length) mit ihren fstat-Entsprechungen.

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 

Hoffe, das ist hilfreich.

0

sowohl Michael Merickel und Kay Zhu sind in Ordnung. Ich habe herausgefunden, dass ich auch die Dateiposition im Begninnign der NamedTemporaryFile zurücksetzen muss, bevor ich sie an die Response übergebe, da die Antwort von der tatsächlichen Position in der Datei und nicht von Anfang an beginnt (Es ist in Ordnung, Sie brauchen nur zu jetzt es). Mit NamedTemporaryFile mit Löschsatz, können Sie es nicht schließen und wieder öffnen, weil sie es löschen würde (und Sie es trotzdem nicht wieder öffnen können), so dass Sie so etwas wie diese verwenden müssen:

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

hoffen, es hilft ;)

Verwandte Themen