はじめに
pythonで一時的にファイルをつくり、CSVファイル書き出し
それをHTTPレスポンスでZIPした状態で返すコードを書いている最中で
UnsupportedOperation: not readableが出て困っていました。
という訳で、ちょい嵌りしたポイントをご紹介します。
pythonで一時ファイルを作成する
「11.6. tempfile — 一時ファイルやディレクトリの作成」のサンプルを参考にすれば一時ファイルはすぐに作れます。
しかもcloseすると勝手に消してくれる優れもの(^^♪
import tempfile
fp = tempfile.TemporaryFile()
fp.write(b'Hello world!')
fp.seek(0)
fp.read()
fp.close()
こいつは優れものだなぁ~と思い次の様に流用しました。
CSVファイルを一時ファイルに作成しZIPでレスポンスを返す(失敗)
contentに辞書型でデータ列が入っていると仮定してみてください。
するとCSV出力するのはこんな感じになると思います。
import csv
import tempfile
fp = tempfile.NamedTemporaryFile(mode='w', newline='', encoding='shift_jis')
fname = fp.name
header=[]
for key in content:
header.append(key)
writer = csv.DictWriter(fp, fieldnames=header)
writer.writeheader()
writer.writerows(content)
出来上がったCSVファイルをZIP圧縮してHTTPレスポンスに返すには…
このファイルをPythonのマニュアルの例通りseek(0)してread()すれば値が取れるかなと思っていたのですが…
import os
import io
import csv
import tempfile
import zipfile
from django.http import HttpResponse
fp = tempfile.NamedTemporaryFile(mode='w', newline='', encoding='shift_jis', delete=False)
fname = fp.name
header=[]
for key in content:
header.append(key)
writer = csv.DictWriter(fp, fieldnames=header)
writer.writeheader()
writer.writerows(content)
fp.seek(0)
zip_io = io.BytesIO()
with zipfile.ZipFile(zip_io, mode='w', compression=zipfile.ZIP_DEFLATED) as backup_zip:
backup_zip.writestr('CSVファイル.csv', fp.read())
response = HttpResponse(zip_io.getvalue(), content_type='application/x-zip-compressed')
response['Content-Disposition'] = 'attachment; filename=%s' % 'your_zipfilename' + ".zip"
response['Content-Length'] = zip_io.tell()
fp.close()
return response
こんな感じかなと実行してみると…
fp.read()
の段階でUnsupportedOperation: not readableの例外が発生
色々ネットを徘徊しましたが、どうやら'w'
モードで開いた状態でread()
は出来ないらしいことが判明
どうする俺(笑)
CSVファイルを一時ファイルに作成しZIPでレスポンスを返す(成功)
結局一時ファイルを自動的に消してくれるclose()
の恩恵を無視し(delete=False
)
一時ファイルに書き込みclose()
し、読み取りモードで開いて読み込みHTTPレスポンスへ流し、最後に消すって流れになってしまいました。
import os
import io
import csv
import tempfile
import zipfile
from django.http import HttpResponse
fp = tempfile.NamedTemporaryFile(mode='w', newline='', encoding='shift_jis', delete=False)
fname = fp.name
header=[]
for key in content:
header.append(key)
writer = csv.DictWriter(fp, fieldnames=header)
writer.writeheader()
writer.writerows(content)
fp.close()
fp = open(fname, 'r')
fp.seek(0) # これはいらないかもね
zip_io = io.BytesIO()
with zipfile.ZipFile(zip_io, mode='w', compression=zipfile.ZIP_DEFLATED) as backup_zip:
backup_zip.writestr('CSVファイル.csv', fp.read())
response = HttpResponse(zip_io.getvalue(), content_type='application/x-zip-compressed')
response['Content-Disposition'] = 'attachment; filename=%s' % 'your_zipfilename' + ".zip"
response['Content-Length'] = zip_io.tell()
fp.close()
os.unlink(fname)
return response
本当は
一時ファイルすら作りたくなくって、直接ZIP化できればよいんですけどね。