Boto 3 で、指定した key が指定した bucket の中にあるかを確かめるには


Boto 3 では、Boto 2 の頃まであった key の存在をたしかめる exists() にあたるメソッドが無くなっています。ですが、以下のように list_objects() を使うことで同様の機能を実現できます。

exists.py
from boto3 import Session
s3client = Session().client('s3')

def exists(bucket: str, key: str) -> bool:
    """
    指定した key が指定した bucket の中に存在するか

    :param bucket: (str) bucket name
    :param key: (str) key
    :return: (bool)
    """
    contents = s3client.list_objects(Prefix=key, Bucket=bucket).get("Contents")
    if contents:
        for content in contents:
            if content.get("Key") == key:
                return True
    return False

解説

list_objects() は以下のような形のハッシュを返します。

{
    'IsTruncated': True|False,  # 結果が寸断されたか。されていれば True
    'Marker': 'string',
    'NextMarker': 'string',
    'Contents': [
        {
            'Key': 'string',
            'LastModified': datetime(2015, 1, 1),
            'ETag': 'string',
            'Size': 123,
            'StorageClass': 'STANDARD'|'REDUCED_REDUNDANCY'|'GLACIER',
            'Owner': {
                'DisplayName': 'string',
                'ID': 'string'
            }
        },
    ],
    'Name': 'string',
    'Prefix': 'string',
    
}

'Contents' がキモで、ここに該当する key の配列が入ります。該当する key がないと 'Contents' は返り値に含まれません。したがって、'Contents' が含まれるかを調べて、そのリスト内の辞書の 'Key' の値と、引数で与えた key が一致するものがあれば exists() にあたる機能を再現できます。

Prefix に key のフルパス? を指定するのは若干違和感を覚えますが、Prefix とはそういうものだと理解しました。

2017/06/29 追記

以下のケースで正しく動作しないことがあったので、修正しました (thx, @masahiro_toriumi さん)

Prefixはあくまでもキーの接頭辞を指定するものなので、例えば、"MyBucket/aaa.txt"というPrefixでlist_objectをコールした場合、"MyBucket/aaa.txt.bak"などにも引っかかるため、"MyBucket/aaa.txt"というキーが存在するかどうかは正確には判断できない