2012-10-18 14 views
8

Attualmente, sto solo servire i file in questo modo:Come posso servire i file temporanei da Python Piramide

# 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 

Il problema di questo è che non posso chiudere o, meglio ancora, eliminare il file dopo la risposta è stata restituita. Il file diventa orfano. Posso pensare ad alcuni modi hacky intorno a questo, ma spero che ci sia un modo standard là fuori da qualche parte. Qualsiasi aiuto sarebbe fantastico.

risposta

9

Non si desidera impostare un puntatore di file come app_iter. Ciò farà sì che il server WSGI legga il file riga per riga (come lo for line in file), che in genere non è il modo più efficiente per controllare il caricamento di un file (immagina un carattere per riga). Il modo supportato da Pyramid per servire i file è via pyramid.response.FileResponse. È possibile creare uno di questi passando un oggetto file.

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

Un'altra opzione è quella di passare un puntatore di file per app_iter ma avvolgerlo nell'oggetto pyramid.response.FileIter, che utilizzerà una dimensione di blocco sano per evitare proprio la lettura del file riga per riga.

La specifica WSGI ha requisiti rigorosi secondo cui gli iteratori di risposta che contengono un metodo verranno richiamati alla fine della risposta. Pertanto l'impostazione di response.app_iter = open(...) non dovrebbe causare perdite di memoria. Entrambi FileResponse e FileIter supportano anche un metodo close e verranno quindi ripuliti come previsto.

Come aggiornamento secondario a questa risposta ho pensato di spiegare perché FileResponse accetta un percorso di file e non un puntatore di file.Il protocollo WSGI offre ai server la possibilità opzionale di fornire un meccanismo ottimizzato per servire file statici tramite environ['wsgi.file_wrapper']. FileResponse gestirà automaticamente questo se il server WSGI ha fornito tale supporto. Con questo in mente, trovi che è una vittoria salvare i tuoi dati in un file tmp su un ramdisk e fornire il FileResponse con il percorso completo, invece di provare a passare un puntatore di file a FileIter.

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

+1

+1 Non mi sono reso conto che esiste un oggetto 'FileResponse'. Ma cancella anche il file? In caso contrario, potrebbe funzionare se il percorso del file 'NamedTemporary' viene passato a' FileResponse', in modo che il file venga eliminato una volta chiuso? –

+2

'FileResponse' non fornisce supporto per l'eliminazione di file. Se 'NamedTemporaryFile' supporta l'eliminazione del file sottostante tramite il metodo' close', allora puoi racchiuderlo in un 'FileIter'. –

+1

Fresco, sì per impostazione predefinita 'NamedTemporaryFile' verrà eliminato una volta chiuso :) –

8

Aggiornamento:

Si prega di vedere la risposta di Michael Merickel per una soluzione migliore e spiegazione.

Se si desidera avere il file eliminato una volta response viene restituito, si può provare il seguente:

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 

È possibile considerare l'utilizzo di NamedTemporaryFile:

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

Impostazione delete=True in modo che il file viene eliminato non appena viene chiuso.

Ora, con l'aiuto di with si può sempre avere la garanzia che il file verrà chiuso, e quindi cancellato:

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

Grazie. Ho imparato molto dalla tua risposta e userò il 'NamedTemporaryFile' in congiunzione con la risposta di Mike. Saluti! – MFB

+0

@MFB Siete i benvenuti :) Felice di aiutare! –

+0

BTW, funziona come un fascino. Ho semplicemente creato 'NamedTemporaryFile' come da tua risposta e ho passato il' name' a 'FileResponse' di Mike. File servito e poi cancellato. Yessssss! – MFB

0

Perché il vostro oggetto risposta sta tenendo un handle di file per il file ' /temp/XML_Export_%s.xml'. Utilizza l'istruzione del per eliminare l'handle 'response.app_iter'.

del response.app_iter 
1

C'è anche repoze.filesafe che si occuperà di generare un file temporaneo per voi, ed eliminarlo alla fine. Lo uso per salvare i file caricati sul mio server. Forse può essere utile anche a te.

2

La combinazione di risposta di Michael e Kay di grandi opere sotto Linux/Mac, ma non funziona sotto Windows (per auto-eliminazione). A Windows non piace il fatto che FileResponse tenti di aprire il file già aperto (vedere la descrizione di NamedTemporaryFile).

Ho risolto il problema creando una classe FileDecriptorResponse che è essenzialmente una copia di FileResponse, ma accetta il descrittore di file del NamedTemporaryFile aperto. Basta sostituire l'open con un seek (0) e tutte le chiamate basate sul percorso (last_modified, content_length) con i loro equivalenti 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 

Spero che sia utile.

0

sia Michael Merickel che Kay Zhu stanno bene. Ho scoperto che ho anche bisogno di ripristinare la posizione del file sul begninnign del NamedTemporaryFile prima di passarlo alla risposta, poiché sembra che la risposta inizi dalla posizione effettiva nel file e non dall'inizio (va bene, hai solo bisogno ad ora). Con NamedTemporaryFile con delezione set, non è possibile chiudere e riaprirlo, perché sarebbe eliminarlo (e non è possibile riaprirla comunque), quindi è necessario usare qualcosa di simile:

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

Speranza che aiuta ;)

Problemi correlati