山pの楽しいお勉強生活

勉強の成果を垂れ流していきます

Pythonからs3fsを使用してS3を操作した際にハマった件について

はじめに

PythonsからS3をいじる際に、これまではaws cliを直接叩いていたのですが、s3fsを使用するとexistsやlsみたいなわかりやすい名称で使えるというのをどこかで知ったので使ってみた。

同名でS3をマウントするもの( https://github.com/s3fs-fuse/s3fs-fuse )もあるようですが、本記事でのs3fs( https://github.com/dask/s3fs )とは別物です。 で、キャッシュのせいかファイルを置いた直後にexistsするとFalseだったりと整合性がとれなくてハマった話です。

s3fsとは

リポジトリTOPには下記のように記載されています。

S3FS builds on boto3 to provide a convenient Python filesystem interface for S3.

Google翻訳すると↓

S3FSはboto3をベースにしており、S3用の便利なPythonファイルシステムインタフェースを提供します。

「はじめに」にも書きましたが、同名でS3をマウントするものもあるようですが、別物です。 むしろ、GitHubの☆を見てもマウントする奴が3000以上、本題の方が130位なので、どちらかというとこの記事に書くライブラリの方が無名。

使い方

インストールして(詳細

pip install s3fs

以下のように書けばOK

import s3fs
fs = s3fs.S3FileSystem(anon=True)
list = fs.ls('my-bucket')

ドキュメントがしっかりしているので、以下に気をつければ?簡単に使えて良いです。 が、気をつけるなんて現実的ではないので個人的にはあまりオススメしません。

ハマったことと対応方法

内容としては、ファイル配置後に上位のフォルダを見た際にFileNotFoundErrorになるという事。
で、キャッシュが悪さしているので、キャッシュを更新すれば正しく動作します。 (キャッシュ更新するにはlsのrefreshオプションをTrueにする)

具体的には下記のコード参照。 (実際に試したい場合には↓↓に全コード書いたので、そちらをオススメ。)

import s3fs

fs = s3fs.S3FileSystem(key=key, secret=secret)
# 特定のPATHの下にフォルダを挟んでファイルを配置
output_path = '{}/hoge/{}'.format(s3_path, 'test.txt')
fs.put('./test.txt', output_path)

# ファイルを配置したフォルダではなく上位のフォルダでlsするとFileNotFoundError
try:
    fs.ls(s3_path) # FileNotFoundError
except FileNotFoundError:
    print('ls : FileNotFoundError')
# existsでも同様に取得できない
if not fs.exists(s3_path):
    print('exists : False')
# 上位のフォルダから再帰的に削除しようとしてもFileNotFoundError
try:
    fs.rm(s3_path, recursive=True) # FileNotFoundError
except FileNotFoundError:
    print('rm : FileNotFoundError')

# lsにrefreshオプションがあったので付与すると取得できる
try:
    fs.ls(s3_path, refresh=True) # refresh=Trueをつける
    print('ls : refreshしたら例外なし')
except FileNotFoundError:
    pass

# 1度refreshするとexistsも問題なし
if fs.exists(s3_path):
    print('exists : refresh後はTrue')

# 同様にrefresh後はlsにrefreshつけなくても例外なし    
try:
    fs.ls(s3_path)
    print('ls : FileNotFoundErrorは発生しない')
except FileNotFoundError:
    pass

考察とか

全コード

import s3fs

key = 'xxxxxxxxxx'
secret = 'yyyyyyyyyyyyyyy'
s3_path = 'my-bucket/aaa/bbb'

# 前準備
try:
    fs.ls(s3_path, refresh=True)
    fs.rm(s3_path, recursive=True)
except FileNotFoundError:
    pass
try:
    fs.ls(s3_path)
except FileNotFoundError:
    pass
try:
    fs.ls('{}/{}'.format(s3_path, 'hoge'))
except FileNotFoundError:
    pass

# 特定のPATHの下にフォルダを挟んでファイルを配置
output_path = '{}/hoge/{}'.format(s3_path, 'test.txt')
fs.put('./test.txt', output_path)

# ファイルが置かれていることを確認
try:
    fs.ls(output_path)
    print('ls : FileNotFoundErrorは発生しない')
except FileNotFoundError:
    pass
if fs.exists(output_path):
    print('exists : True')

# ここから本番
# ファイルを配置したフォルダではなく上位のフォルダでlsするとFileNotFoundError
try:
    fs.ls(s3_path) # FileNotFoundError
except FileNotFoundError:
    print('ls : FileNotFoundError')
# existsでも同様に取得できない
if not fs.exists(s3_path):
    print('exists : False')
# 上位のフォルダから再帰的に削除しようとしてもFileNotFoundError
try:
    fs.rm(s3_path, recursive=True) # FileNotFoundError
except FileNotFoundError:
    print('rm : FileNotFoundError')

# lsにrefreshオプションがあったので付与すると取得できる
try:
    fs.ls(s3_path, refresh=True) # refresh=Trueをつける
    print('ls : refreshしたら例外なし')
except FileNotFoundError:
    pass

# 1度refreshするとexistsも問題なし
if fs.exists(s3_path):
    print('exists : refresh後はTrue')

# 同様にrefresh後はlsにrefreshつけなくても例外なし    
try:
    fs.ls(s3_path)
    print('ls : FileNotFoundErrorは発生しない')
except FileNotFoundError:
    pass

参考URL