BLOGサブスレッドの日常
2016.08.09
django(Python3) の FileSystemStorage クラスを使って、CSVを読み込む
torikai
月曜日担当の秋山です。よろしくお願いします。
今日のひとネタ
ネタというほどのネタではないんですけども… 過去につまづいたネタ。
便利に使っているdjango
。デフォルトのファイルストレージは、FileSystemStorage
となっています。
django
では、settings.py
のDEFAULT_FILE_STORAGE
に利用するファイルストレージを指定してあげると簡単に切り替わるので、
本番ではS3BotoStorage
クラスを使うけど、開発環境、特にローカルではFileSystemStorage
クラスを使う。なんてことがお手軽に出来ます。
出来るわけですが、このFileSystemStorage
、ちょっとだけ、ちょっとだけハマりどころが。
多分、Python3
を使ってるから、というのもあるんですが、まぁどういう時にハマったかというと。
SJISのCSVファイルを読み込みたかった
こんな感じのCSV
ファイルがあるとして。
"1","大阪","000000","カレー美味しい"
"2","広島","111111","お好み焼き美味しい"
"3","名古屋","222222","手羽先美味しい"
こんな感じのコードを書いていました。
# -*- coding:utf-8 -*-
import csv
from django.core.files.storage import default_storage
from django.shortcuts import render
def netacsv(request):
context = {}
data = []
storage = default_storage
storage.location = 'temp/'
with storage.open('csvtest.csv', mode='r') as fp:
reader = csv.reader(fp, delimiter=',', quotechar='"', lineterminator='\r\n')
for row in reader:
data.append(row)
context['data'] = data
return render(request, 'csv.html', context)
(´・ω・`)…
そりゃそうだ。CSVはSJIS
なのに、プログラムコードがUTF-8
だもの。私が悪かった。
と思って、open
にencoding='cp932'
を渡せばあっさり解決…
しない!
FileSystemStorage
が、というより、FileSystemStorage
の継承元のStorage
クラスが、そもそもopen
メソッドにencoding
の引数をサポートしていない模様。
class Storage(object):
"""
A base storage class, providing some default behaviors that all other
storage systems can inherit or override, as necessary.
"""
# The following methods represent a public interface to private methods.
# These shouldn't be overridden by subclasses unless absolutely necessary.
def open(self, name, mode='rb'):
"""
Retrieves the specified file from storage.
"""
return self._open(name, mode)
...
@deconstructible
class FileSystemStorage(Storage):
"""
Standard filesystem storage
"""
...
def _open(self, name, mode='rb'):
return File(open(self.path(name), mode))
うーんからい(´・ω・`)
return
で返している処理の中の、open()
はencoding
の引数をサポートしているじゃないですかー…
吸収してくれたって良さそうなものなのに。
そもそもストレージクラスを使ってCSV
ファイルを読み込む事自体が微妙なのかしら…?
いや、読みたい場面はそれなりにある、はず。
というわけで
utils.py
みたいなトコにクラスを生やして
# -*- coding:utf-8 -*-
from django.core.files.storage import FileSystemStorage, File
class MyFileSystemStorage(FileSystemStorage):
def open(self, name, mode='rb', *args, **kwargs):
return File(open(self.path(name), mode, *args, **kwargs))
settings.py
でDEFAULT_FILE_STORAGE
を定義すれば…
DEFAULT_FILE_STORAGE = 'neta.utils.MyFileSystemStorage'
1 | 大阪 | 000000 | カレー美味しい |
2 | 広島 | 111111 | お好み焼き美味しい |
3 | 名古屋 | 222222 | 手羽先美味しい |
やったー∩(・ω・)∩ 読み込めた!
因みに
この処理の書き方だと、S3BotoStorage
クラスを利用する時にエラーが出るので、
# -*- coding:utf-8 -*-
import csv
from django.core.files.storage import default_storage
from django.shortcuts import render
from django.conf import settings
from io import StringIO
def netacsv(request):
context = {}
data = []
storage = default_storage
storage.location = 'temp/'
encoding = 'cp932'
kwargs = {}
if settings.DEFAULT_FILE_STORAGE == 'neta.utils.MyFileSystemStorage':
kwargs['encoding'] = encoding
with storage.open('csvtest.csv', mode='r', **kwargs) as fp:
if settings.DEFAULT_FILE_STORAGE == 'neta.utils.FileSystemStorage':
body = fp
else:
body = StringIO(fp.read().decode(encoding))
reader = csv.reader(body, delimiter=',', quotechar='"', lineterminator='\r\n')
for row in reader:
data.append(row)
context['data'] = data
return render(request, 'csv.html', context)
こんな感じで分岐を入れてあげるとどちらの環境でも動作します。
ちょっと無理矢理感有りますが、
(body = StringIO(fp.read().decode(encoding))
してるとことか、プロジェクト毎にMyFileSystemStorage
を用意しなきゃいけないとか)
SJIS
のCSV
を読みたい時はこんな感じで書くようになりました。
今日のひとネタでした(・ω・)ノ
この記事を書いた人
torikai