Flaskを使ってみる

進捗状況付きのファイルコピー

Flaskとは直接関係ないですが、pythonで巨大なファイルをコピーするときに、進捗状況を知る必要があり、
色々調べたのでその結果を残しておきます。
検証用に1GBのダミーファイルを使います。
$ fallocate -l 1g big_file

$ ls -l big_file
-rw-rw-r-- 1 nop nop 1073741824  2月 11 14:49 big_file

1つはprogrressを使うやり方です。
このライブラリはcpだけでなく、tar、mv、dd、gzip/gunzipなどの進捗状況をスキャンして教えてくれます。
pythonではPopenと組み合わせて使います。
#!/usr/bin/env python
import time
import subprocess

# sudo apt install progress
def file_copy_progress(src, dst):
    """進捗状況付きでファイルコピー実行"""
    command = 'cp {} {}'.format(src, dst)
    proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    while True:
        status = proc.poll()
        # print("poll status={}".format(status))
        if status is not None:
            break
        progress = subprocess.check_output('progress')
        if len(progress) == 0:
            time.sleep(1)
            continue
        print("progress0={} {}".format(progress, type(progress)))
        progress = progress.decode('utf-8')
        # print("progress1={} {}".format(progress, type(progress)))
        lines = progress.splitlines()
        # print("lines={}".format(lines))
        #progress = lines[1]
        progress = lines[1].strip()
        print("progress2={} {}".format(progress, type(progress)))
        """
        progress = progress.split('%')
        print("progress4={} {}".format(progress, type(progress)))
        progress = float(progress[0])
        print("progress5={} {}".format(progress, type(progress)))
        """
        time.sleep(1)

    print("copy done")

def main():
    src = "./big_file"
    dst = "./dst_file"
    file_copy_progress(src, dst)

if __name__ == '__main__':
    main()

このスクリプトを実行すると以下の様に進捗状況を表示します。
100%が続くのはファイルシステムのバッファフラッシュに時間が掛かるためです。
progressが報告する進捗率はbytes型なので、decodeを使ってstr型に変換し、split()を使って成形すれば進捗率を取り 出すことができます。
$ python3 file_copy1.py
progress0=b'[630534] cp /home/nop/test/big_file\n\t0.0% (0 / 1 GiB)\n\n' <class 'bytes'>
progress2=0.0% (0 / 1 GiB) <class 'str'>
progress0=b'[630534] cp /home/nop/test/big_file\n\t60.9% (623.8 MiB / 1 GiB)\n\n' <class 'bytes'>
progress2=60.9% (623.8 MiB / 1 GiB) <class 'str'>
progress0=b'[630534] cp /home/nop/test/big_file\n\t78.1% (799.5 MiB / 1 GiB)\n\n' <class 'bytes'>
progress2=78.1% (799.5 MiB / 1 GiB) <class 'str'>
progress0=b'[630534] cp /home/nop/test/big_file\n\t89.5% (916.6 MiB / 1 GiB)\n\n' <class 'bytes'>
progress2=89.5% (916.6 MiB / 1 GiB) <class 'str'>
progress0=b'[630534] cp /home/nop/test/big_file\n\t100.0% (1 GiB / 1 GiB)\n\n' <class 'bytes'>
progress2=100.0% (1 GiB / 1 GiB) <class 'str'>
progress0=b'[630534] cp /home/nop/test/big_file\n\t100.0% (1 GiB / 1 GiB)\n\n' <class 'bytes'>
progress2=100.0% (1 GiB / 1 GiB) <class 'str'>
progress0=b'[630534] cp /home/nop/test/big_file\n\t100.0% (1 GiB / 1 GiB)\n\n' <class 'bytes'>
progress2=100.0% (1 GiB / 1 GiB) <class 'str'>
progress0=b'[630534] cp /home/nop/test/big_file\n\t100.0% (1 GiB / 1 GiB)\n\n' <class 'bytes'>
progress2=100.0% (1 GiB / 1 GiB) <class 'str'>
progress0=b'[630534] cp /home/nop/test/big_file\n\t100.0% (1 GiB / 1 GiB)\n\n' <class 'bytes'>
progress2=100.0% (1 GiB / 1 GiB) <class 'str'>
copy done

$ diff big_file dst_file



もう1つの方法はpvコマンドを使う方法です。
これはフィルターとして他のコマンドと組み合わせて使うことができます。
こ ちらこ ちらに使用例が公開されています。
pythonではPopenと組み合わせて使います。
コピー先ファイルを書き込みモードでオープンし、stdoutに設定するのがポイントです。
#!/usr/bin/env python
import time
import subprocess

# sudo apt insall pv
def file_copy_progress(src, dst):
    args = ['pv', '-f', src]
    with open(dst, "w") as outfile:
        proc = subprocess.Popen(args, stdout=outfile, stderr=subprocess.PIPE, universal_newlines=True)
        for line in iter(proc.stderr.readline, ""):
            #print(line[:-1])
            progress = line[:-1]
            print(progress)
            """
            progress = progress.split(']')
            # print(progress)
            # print(progress[2])
            progress = progress[2]
            print(progress)
            progress = progress.split('%')
            print(progress[0])
            """
            time.sleep(1)

        print("copy done")

def main():
    src = "./big_file"
    dst = "./dst_file"
    file_copy_progress(src, dst)

if __name__ == '__main__':
    main()

このスクリプトを実行すると以下の様に進捗状況を表示します。
split()を使って成形すれば進捗率を取り出すことができます。
$ python3 file_copy2.py
 579MiB 0:00:01 [ 577MiB/s] [==================>               ] 56% ETA 0:00:00
 736MiB 0:00:02 [ 157MiB/s] [=======================>          ] 71% ETA 0:00:00
 851MiB 0:00:03 [ 114MiB/s] [===========================>      ] 83% ETA 0:00:00
 965MiB 0:00:04 [ 113MiB/s] [==============================>   ] 94% ETA 0:00:00
1.00GiB 0:00:04 [ 227MiB/s] [================================>] 100%
copy done

$ diff big_file dst_file

次回はFlaskとよく似たフレームワークのBottleを紹介します。