2014年5月11日日曜日

pythonでファイルアップロードの進捗をリアルタイム取得する

pythonでサーバに対しurllib2やrequestsなどのモジュールでファイルをアップロードする場合、基本的に
urllib2.urlopen(request)

requests.post()
の形式で投げることになりますが、f.read()のような形で、引数としてファイル全体を与えてしまった場合、アップロードが完了するまで待たなければなりません。そのため、大きなファイルのアップロードなどでは特に、本当にアップロードが進んでいるのか知ることができず不便でした。

そこで、StringIOを使ってファイルをchunkに分割してロードできるようにし、read()が走るたびにコールバックを呼ぶようにしてやることで、アップロード中の進捗状況を知ることができるようになります。こちらを参考にしてStringIOを継承してコールバック関数を呼ぶためには、以下のように書けます。
from StringIO import StringIO
class BufferReader(StringIO):
    def __init__(self, buf='',
                 callback=None,
                 cb_args=(), cb_kwargs={}):
        self._callback = callback
        self._cli_manager = cli_manager
        self._progress = 0
        self._cb_args = cb_args
        self._cb_kwargs = cb_kwargs
        StringIO.__init__(self, buf)

    def __len__(self):
        return self.len

    def read(self, n=-1):
        chunk = StringIO.read(self, n)
        self._progress += int(len(chunk))
        self._cb_kwargs.update({'progress': self._progress})
        if self._callback:
            try:
                self._callback(*self._cb_args, **self._cb_kwargs)
            except Exception, e: # catches exception from the callback
                raise BaseException, e

これで、
req = urllib2.Request(url, BufferReader(f.read(), callback=callback_func))
res = urllib2.urlopen(req)
とすることでcallback_funcにファイルアップロードの進捗を渡すことが可能になります。

ただし、multipartによるPOSTでファイルをアップロードする場合には、requests.post(files={'file': (filename, BufferReader(filebuffer))})の形式ではStringIOを使っても上記のread()が正しくchunkごとに呼ばれないようです。この場合はurllib3のencode_multipart_formdata()を使ってあらかじめmultipart用のデータを作り、この結果をBufferReaderに噛ませたあとrequests.post(files=)に入れてやることでアップロードするなどの方法をとる必要があります。

0 件のコメント:

コメントを投稿