📑

StreamlitのFile Uploadで文字コードチェック

2023/10/10に公開

結論

  • 文字コードチェック自体は 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