📑
StreamlitのFile Uploadで文字コードチェック
結論
- 文字コードチェック自体は
file_content = uploaded_file.read()
した後にfile_content.decode("utf-8")
してUnicodeDecodeError
を検知するなどで簡単に可能 - ファイルオブジェクトをreadしているので、ファイルの読み込みカーソルが最下部まで行ってしまうので、
uploaded_file.seek(0)
でカーソルを先頭に戻してやる必要がある - もっと楽に文字コードのバリデーションができる方法があれば知りたい
今回起きたこと・ハマったポイント
仕事で、「streamlitでアップロードしたファイル、UTF-8のみを受け付けて、それ以外は弾くようにしたいなー」となった。
文字コードチェック自体はすぐにできた。
import streamlit as st
# [前段の処理…]
uploaded_file = st.file_uploader(
"CSVデータアップロード", type="csv", accept_multiple_files=False,
)
# 文字コードチェック
try:
file_content = uploaded_file.read()
file_content.decode("utf-8")
except UnicodeDecodeError:
# 文字コードがutf-8じゃないよー、の処理。例えば↓など
st.error("アップロードされたファイルの文字コードがUTF-8ではありません。")
# [後続の処理…]
が、 [後続の処理…]
の部分でうまく行かなかった。
(今回の場合、AWS SDK for pandas (awswrangler)を使って wr.s3.upload(uploaded_file)
をしようとしたが、「エラーにならず、S3へのアップロードもできていない状態」になってしまった。)
色々動作チェックしていくと、 file_content = uploaded_file.read()
をした後にうまく行かないことがわかった。
streamlitのfile_uploaderは UploadedFile を返すが、元ソースの通り UploadedFile
クラスはPython標準の io.BytesIO
を継承しており、 read()
は標準機能でしかない。
「あれ、こんなところでバグることある…?」と思っていろいろ調べていたら下記のStack Overflowに行き当たった。
Why can't I call read() twice on an open file?
「そうか、ファイルの読み込みカーソルが最下部まで行ってしまうのか…そんなのあったな…」ということで、記事の通り下記で解決。
import streamlit as st
# [前段の処理…]
uploaded_file = st.file_uploader(
"CSVデータアップロード", type="csv", accept_multiple_files=False,
)
# 文字コードチェック
try:
file_content = uploaded_file.read()
file_content.decode("utf-8")
uploaded_file.seek(0)
except UnicodeDecodeError:
# 文字コードがutf-8じゃないよー、の処理。例えば↓など
st.error("アップロードされたファイルの文字コードがUTF-8ではありません。")
# [後続の処理…]
※後続の処理を read()
後の file_content
を使う形で実装する方法もあったが、I/Oの見直しが大変そうだったので妥協。
感想
- ファイルの読み込みカーソルをちゃんと考える、なんて久しぶりすぎた
- 「ファイルオブジェクトと文字コード名を入れたらバリデーションしてくれる」みたいなものがあったら使いたいので、情報あればご共有いただけると助かります🙇♂
Discussion