Progreso de las solicitudes de Python

Estoy cargando un archivo grande con el paquete de solicitudes de Python, y no puedo encontrar ninguna manera de devolver datos sobre el progreso de la carga. He visto varios medidores de progreso para descargar un archivo, pero estos no funcionarán para una carga de archivo.

La solución ideal sería algún tipo de método de callback como:

def progress(percent): print percent r = requests.post(URL, files={'f':hugeFileHandle}, callback=progress) 

Gracias de antemano por tu ayuda 🙂

requests no son compatibles con la transmisión de carga , por ejemplo:

 import os import sys import requests # pip install requests class upload_in_chunks(object): def __init__(self, filename, chunksize=1 << 13): self.filename = filename self.chunksize = chunksize self.totalsize = os.path.getsize(filename) self.readsofar = 0 def __iter__(self): with open(self.filename, 'rb') as file: while True: data = file.read(self.chunksize) if not data: sys.stderr.write("\n") break self.readsofar += len(data) percent = self.readsofar * 1e2 / self.totalsize sys.stderr.write("\r{percent:3.0f}%".format(percent=percent)) yield data def __len__(self): return self.totalsize # XXX fails r = requests.post("http://httpbin.org/post", data=upload_in_chunks(__file__, chunksize=10)) 

por cierto, si no es necesario informar el progreso; podría usar un archivo asignado en memoria para cargar archivos grandes .

Para solucionarlo, puede crear un adaptador de archivo similar al de la supervisión de progreso de la POST de urllib2 :

 class IterableToFileAdapter(object): def __init__(self, iterable): self.iterator = iter(iterable) self.length = len(iterable) def read(self, size=-1): # TBD: add buffer for `len(data) > size` case return next(self.iterator, b'') def __len__(self): return self.length 

Ejemplo

 it = upload_in_chunks(__file__, 10) r = requests.post("http://httpbin.org/post", data=IterableToFileAdapter(it)) # pretty print import json json.dump(r.json, sys.stdout, indent=4, ensure_ascii=False) 

Lo tengo trabajando con el código desde aquí: barra de progreso de carga de archivos simple en PyQt . Lo cambié un poco, para usar BytesIO en lugar de StringIO.

 class CancelledError(Exception): def __init__(self, msg): self.msg = msg Exception.__init__(self, msg) def __str__(self): return self.msg __repr__ = __str__ class BufferReader(BytesIO): def __init__(self, buf=b'', callback=None, cb_args=(), cb_kwargs={}): self._callback = callback self._cb_args = cb_args self._cb_kwargs = cb_kwargs self._progress = 0 self._len = len(buf) BytesIO.__init__(self, buf) def __len__(self): return self._len def read(self, n=-1): chunk = BytesIO.read(self, n) self._progress += int(len(chunk)) self._cb_kwargs.update({ 'size' : self._len, 'progress': self._progress }) if self._callback: try: self._callback(*self._cb_args, **self._cb_kwargs) except: # catches exception from the callback raise CancelledError('The upload was cancelled.') return chunk def progress(size=None, progress=None): print("{0} / {1}".format(size, progress)) files = {"upfile": ("file.bin", open("file.bin", 'rb').read())} (data, ctype) = requests.packages.urllib3.filepost.encode_multipart_formdata(files) headers = { "Content-Type": ctype } body = BufferReader(data, progress) requests.post(url, data=body, headers=headers) 

El truco es generar datos y encabezados de la lista de archivos manualmente, utilizando encode_multipart_formdata () de urllib3

Recomiendo usar un paquete de herramientas llamado request-toolbelt , que facilita mucho el monitoreo de bytes de carga, como

 from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor import requests def my_callback(monitor): # Your callback function print monitor.bytes_read e = MultipartEncoder( fields={'field0': 'value', 'field1': 'value', 'field2': ('filename', open('file.py', 'rb'), 'text/plain')} ) m = MultipartEncoderMonitor(e, my_callback) r = requests.post('http://httpbin.org/post', data=m, headers={'Content-Type': m.content_type}) 

Y es posible que desee leer esto para mostrar una barra de progreso.

La solución de falloutboy es agradable. Y encontré otro proyecto llamado requests-toolbelt en el sitio web oficial de requests : http://docs.python-requests.org/en/latest/user/quickstart/#post-a-multipart-encoded-file

Aquí está el documento de request-toolbelt: https://toolbelt.rtfd.org/

Por lo general, construiría una fuente de datos de transmisión (un generador) que lee el archivo fragmentado e informa de su progreso en el camino (vea kennethreitz / peticiones n. ° 663) . Esto no funciona con las solicitudes de api de archivos, ya que las solicitudes no admiten cargas de transmisión por secuencias ( vea kennethreitz / peticiones # 295 ) – un archivo para cargar debe estar completo en la memoria antes de que comience a procesarse.

pero las solicitudes pueden transmitir contenido de un generador como JF Sebastian ha demostrado anteriormente, pero este generador necesita generar el flujo de datos completo, incluida la encoding y los límites de varias partes. Aquí es donde el cartel viene a jugar.

el póster se escribió originalmente para usarse con pythons urllib2 y admite la generación de transmisiones de solicitudes multiparte, lo que proporciona una indicación de progreso a medida que avanza. La página de inicio de carteles proporciona ejemplos de uso junto con urllib2, pero realmente no desea utilizar urllib2. Echa un vistazo a este código de ejemplo sobre cómo realizar una autenticación básica HTTP con urllib2. Horrrrrrrrible.

Así que realmente queremos usar el póster junto con las solicitudes para realizar cargas de archivos con un progreso de seguimiento. Y aquí es cómo:

 # load requests-module, a streamlined http-client lib import requests # load posters encode-function from poster.encode import multipart_encode # an adapter which makes the multipart-generator issued by poster accessable to requests # based upon code from http://stackoverflow.com/a/13911048/1659732 class IterableToFileAdapter(object): def __init__(self, iterable): self.iterator = iter(iterable) self.length = iterable.total def read(self, size=-1): return next(self.iterator, b'') def __len__(self): return self.length # define a helper function simulating the interface of posters multipart_encode()-function # but wrapping its generator with the file-like adapter def multipart_encode_for_requests(params, boundary=None, cb=None): datagen, headers = multipart_encode(params, boundary, cb) return IterableToFileAdapter(datagen), headers # this is your progress callback def progress(param, current, total): if not param: return # check out http://tcd.netinf.eu/doc/classnilib_1_1encode_1_1MultipartParam.html # for a complete list of the properties param provides to you print "{0} ({1}) - {2:d}/{3:d} - {4:.2f}%".format(param.name, param.filename, current, total, float(current)/float(total)*100) # generate headers and gata-generator an a requests-compatible format # and provide our progress-callback datagen, headers = multipart_encode_for_requests({ "input_file": open('recordings/really-large.mp4', "rb"), "another_input_file": open('recordings/even-larger.mp4', "rb"), "field": "value", "another_field": "another_value", }, cb=progress) # use the requests-lib to issue a post-request with out data attached r = requests.post( 'https://httpbin.org/post', auth=('user', 'password'), data=datagen, headers=headers ) # show response-code and -body print r, r.text