Transmitir un archivo a la respuesta HTTP en Pylons

Tengo una acción de controlador Pylons que necesita devolver un archivo al cliente. (El archivo está fuera de la raíz web, por lo que no puedo vincularlo directamente a él). La forma más sencilla es, por supuesto, esta:

with open(filepath, 'rb') as f: response.write(f.read()) 

Eso funciona, pero obviamente es ineficiente para archivos grandes. ¿Cuál es la mejor manera de hacer esto? No he podido encontrar ningún método conveniente en los pilones para transmitir el contenido del archivo. ¿Realmente tengo que escribir el código para leer un trozo a la vez yo mismo desde cero?

La herramienta correcta a utilizar es shutil.copyfileobj, que copia de una a otra una porción a la vez.

Ejemplo de uso:

 import shutil with open(filepath, 'r') as f: shutil.copyfileobj(f, response) 

Esto no resultará en un uso de memoria muy grande, y no requiere la implementación del código usted mismo.

Se debe tener cuidado con las excepciones: si maneja señales (como SIGCHLD), tiene que manejar EINTR porque la escritura en la respuesta podría interrumpirse, e IOError / OSError puede ocurrir por varias razones al hacer I / O.

Finalmente conseguí que funcionara utilizando la clase FileApp , gracias a Chris AtLee y THC4k (de esta respuesta ). Este método también me permitió establecer el encabezado Content-Length, algo con lo que Pylons tiene muchos problemas , lo que permite al navegador mostrar una estimación del tiempo restante.

Aquí está el código completo:

 def _send_file_response(self, filepath): user_filename = '_'.join(filepath.split('/')[-2:]) file_size = os.path.getsize(filepath) headers = [('Content-Disposition', 'attachment; filename=\"' + user_filename + '\"'), ('Content-Type', 'text/plain'), ('Content-Length', str(file_size))] from paste.fileapp import FileApp fapp = FileApp(filepath, headers=headers) return fapp(request.environ, self.start_response) 

La clave aquí es que WSGI, y los pilones por extensión, funcionan con respuestas iterables. Por lo tanto, debería poder escribir algún código similar (advertencia, código no probado a continuación):

 def file_streamer(): with open(filepath, 'rb') as f: while True: block = f.read(4096) if not block: break yield block response.app_iter = file_streamer() 

Además, paste.fileapp.FileApp está diseñado para poder devolver datos de archivos, así que también puedes probar:

 return FileApp(filepath) 

en su método de control.