Flaskの使い方の基本を知っていることを前提とする。
ファイルを処理するスクリプトを走らせたいが、ベタ張りで保存したくないのは当然のこと、データベースに格納する必要もないという状況にあった。そこで、一回のPOSTリクエストでファイルをアップロード、処理、ダウンロードまでやってしまえばいいのではということで調べて見た結果のメモ。
アップロード
結論として、IOクラスのオブジェクトとして受け取れば良い123
Formタグでファイルを受け取れば、StringIOのバイナリファイルとしてオブジェクトにできる。4
この段階までを実行するプログラム例:
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html')
else:
f = request.files['file']
return f'uploaded {f.filename}'
if __name__ == '__main__':
app.run(debug=True)
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<body>
<form action="/" method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit">
</form>
</body>
</html>
ダウンロード
公式ドキュメント5が一番参考になったが、結論として、ダウンロードというより、send_fileを使えば良い
ドキュメントの情報の注意点としては、
StringIOモジュールは3系ではなくなっており、ioクラスからio.StringIOを利用する必要がある。6
また、Python3以降では、Flaskの要求でStringIOではなくBytesIOを用いる
+ from flask import Flask, render_template, request, send_file
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html')
else:
f = request.files['file']
# return f'uploaded {f.filename}'
+ return send_file(f,
+ attachment_filename=f.filename,
+ as_attachment=True)
if __name__ == '__main__':
app.run(debug=True)
サンプルなので上げたファイルをまた落とすだけという中身空っぽな処理になっているが、f = request.files['file']
とreturn send_file(...)
の間に処理を書けばいい。
デプロイ編
蛇足ながら、uwsgi + nginxで構成したサーバに実装した際に遭遇した問題とその解決を記す。若干手抜き。
nginxでpermission denied
2017/11/01 06:46:19 [crit] 25062#0: *1 open() "/usr/local/var/run/nginx/client_body_temp/0000000001" failed (13: Permission denied), client: 124.27.107.250, server: (your_domain), request: "POST /sbi HTTP/1.1", host: "(your_domain)", referrer: "http://(your_domain)/(uri)"
error_logにこんなのが残っていました。
こちらの記事を参考にしました
https://qiita.com/paranishian/items/5ca43e13e9711a0a1ae6
uwsgiでsend_fileがうまくいかない
エラー内容:
[2017-11-01 06:59:20,352] ERROR in app: Exception on /(uri) [POST]
io.UnsupportedOperation: fileno
The above exception was the direct cause of the following exception:
(中略)
SystemError: <built-in function uwsgi_sendfile> returned a result with an error set
[pid: 25653|app: 0|req: 3/3] 124.27.107.250 () {52 vars in 992 bytes}[Wed Nov 1 06:59:20 2017] POST /(uri) => generated 291 bytes in 81 msecs (HTT
P/1.1 500) 2 headers in 84 bytes (9 switches on core 0)
こちらを参考に、uwsgi.iniを編集しました
https://github.com/unbit/uwsgi/issues/1126#issuecomment-166687767
wsgi-disable-file-wrapper = true
参照・注釈
-
公式ドキュメント http://flask.pocoo.org/docs/0.12/patterns/fileuploads/ ↩
-
参考にしたブログ http://nekoyukimmm.hatenablog.com/entry/2016/05/27/162736 ↩
-
参考サイト https://www.tutorialspoint.com/flask/flask_file_uploading.htm ↩
-
正確にStringIOなのかBytesIOなのか理解が若干曖昧だが、
f = open(fname, 'r')
と同じように扱えることは確かだ。 ↩ -
公式ドキュメント 一番参考になった http://flask.pocoo.org/snippets/32/ ↩