OCI Bucketのファイル存在チェックするAPIはあるのか


OCIのオブジェクト・ストレージについて、バケット内に特定名称のファイルが存在するかチェックするPython APIはありますか？

OCI(Oracle Cloud Infrastructure)の[オブジェクト・ストレージ](https://docs.oracle.com/ja-
jp/iaas/Content/Object/Concepts/objectstorageoverview.htm)をPythonから操作しようとしています。  
ファイルをアップロードする前に特定のバケットをチェックして、同一名称のオブジェクトがあれば上書きせずに処理をスキップしようとしています。  
しかし[ObjectStorageClient](https://docs.oracle.com/en-
us/iaas/tools/python/2.113.0/api/object_storage/client/oci.object_storage.ObjectStorageClient.html)からいわゆる`exists`APIが見つかりません。

代替手段として`list_objects`でオブジェクト一覧を取得できますが、名称の完全一致でフィルタすることができません。  
`head_object`ならばオブジェクト名を指定してヘッダ情報を取得可能ですが、存在しない場合に`ServiceError`例外が発生するので例外処理が必要となります。  
例外処理でtry-exceptを使うとネストが深くなり若干負荷がかかるので可能であれば回避したいです。

数百個のオブジェクトを持つバケットから効率よくオブジェクトの有無をチェックする方法があればご教示ください。

**試したコード**

hogeバケットにfuga.txtオブジェクトが存在すると仮定します。

[code]

    import oci
    
    config = oci.config.from_file()
    client = oci.object_storage.ObjectStorageClient(config)
    namespace = client.get_namespace().data
    bucket = "hoge" 
    
    objects = client.list_objects(namespace, bucket, start="fuga.txt") # 前方一致しかできない
    exists = "fuga.txt" in [o.name for o in objects.data.objects] # 目的は達せられるが、リストを展開してチェックするのが面倒
    print(exists) # True
    
    exists = True if client.head_object(namespace, bucket, "fuga.txt") else False # 存在する時はうまくいく
    exists = True if client.head_object(namespace, bucket, "not_exist_file.txt") else False #存在しない時はServiceErrorが発生する
    
[/code]




## バケット内に特定名称のファイルが存在するかチェックするPython APIがあるか？

公開されている範囲において、 **APIは存在しない** ものとして考えたほうが良いと思います。

Python SDKも実態としてはOCIのREST APIへリクエストを行うラッパーとして実装されています。[このREST
APIの情報も公開されてはいる](https://docs.oracle.com/en-
us/iaas/api/#/en/objectstorage/20160918/)のですが、そもそもObject
StorageのObjectに関わるURLパスの中に、存在確認のみを目的とするものが存在しないようです。  
※左メニュー経由で配下を確認しても、oci-python-sdkのメソッドと同等のものしか無いことが分かります。

[REST APIのページ](https://docs.oracle.com/ja-
jp/iaas/Content/API/Concepts/usingapi.htm#REST_APIs)でも「典型的なREST
API」と標榜している以上、基本的にはエンティティの存在確認は「HTTPステータスが404か否か」で判定をしてもらう前提なのでしょう。

## 数百個のオブジェクトを持つバケットから効率よくオブジェクトの有無をチェックする方法

自分の見解としては、下記のような「`ServiceError`の適切な処理をした上で存在有無の結果だけ返す関数」を実装するしかないかなと思います。

[code]

    # 適当な実装イメージ
    def exists_object(client: str, namespace: str, bucket: str, object_name: str) -> bool:
       try:
          client.head_object(namespace, bucket, object_name)
          return True
       except ServiceError as err:
          if err.status == 404:
              return False
          # 目的外のServiceErrorはそのまま例外を再度投げる
          raise
    
    
    exists = exists_object(namespace, bucket, "fuga.txt")  # 存在するのでTrue
    exists = exists_object(namespace, bucket, "not_exist_file.txt")  # 存在しないのでFalse
    
[/code]

これは、oci-python-sdkの構造が下記のようになっており、`ServiceError`の回避が非常に難しいためです。

  * サービスごとの各種メソッドは、内部的にはREST APIのリクエストを[`oci.base_client.BaseClient.call_api`](https://github.com/oracle/oci-python-sdk/blob/v2.113.0/src/oci/base_client.py#L437)で実行しています。
  * 更に内部で[`oci.base_client.BaseClient.request`](https://github.com/oracle/oci-python-sdk/blob/v2.113.0/src/oci/base_client.py#L652)を呼び出し、こちらが実際のHTTPリクエストを処理しています。
  * `request()`内部で「[APIからのレスポンスが200番台以外だった](https://github.com/oracle/oci-python-sdk/blob/v2.113.0/src/oci/base_client.py#L700)」時点で、（若干分類はあれど）`ServiceError`が確定でraiseされます。

上記の前提をもとに考えれば、実質的に「HTTPリクエスト～HTTPステータスから判定」までを自前実装でもしないと難しそうなので、流石に割に合わないかなと。

※これらは、OCIを普段使っておらずドキュメントとソースを読んだ範囲で考えて記述しています。もしかしたら`list_objects()`の引数の工夫次第でより良い手法が出てくるかもしれませんが、回答としては考慮外となります。




## バケット内に特定名称のファイルが存在するかチェックするPython APIがあるか？

公開されている範囲において、 **APIは存在しない** ものとして考えたほうが良いと思います。

Python SDKも実態としてはOCIのREST APIへリクエストを行うラッパーとして実装されています。[このREST
APIの情報も公開されてはいる](https://docs.oracle.com/en-
us/iaas/api/#/en/objectstorage/20160918/)のですが、そもそもObject
StorageのObjectに関わるURLパスの中に、存在確認のみを目的とするものが存在しないようです。  
※左メニュー経由で配下を確認しても、oci-python-sdkのメソッドと同等のものしか無いことが分かります。

[REST APIのページ](https://docs.oracle.com/ja-
jp/iaas/Content/API/Concepts/usingapi.htm#REST_APIs)でも「典型的なREST
API」と標榜している以上、基本的にはエンティティの存在確認は「HTTPステータスが404か否か」で判定をしてもらう前提なのでしょう。

## 数百個のオブジェクトを持つバケットから効率よくオブジェクトの有無をチェックする方法

自分の見解としては、下記のような「`ServiceError`の適切な処理をした上で存在有無の結果だけ返す関数」を実装するしかないかなと思います。

[code]

    # 適当な実装イメージ
    def exists_object(client: str, namespace: str, bucket: str, object_name: str) -> bool:
       try:
          client.head_object(namespace, bucket, object_name)
          return True
       except ServiceError as err:
          if err.status == 404:
              return False
          # 目的外のServiceErrorはそのまま例外を再度投げる
          raise
    
    
    exists = exists_object(namespace, bucket, "fuga.txt")  # 存在するのでTrue
    exists = exists_object(namespace, bucket, "not_exist_file.txt")  # 存在しないのでFalse
    
[/code]

これは、oci-python-sdkの構造が下記のようになっており、`ServiceError`の回避が非常に難しいためです。

  * サービスごとの各種メソッドは、内部的にはREST APIのリクエストを[`oci.base_client.BaseClient.call_api`](https://github.com/oracle/oci-python-sdk/blob/v2.113.0/src/oci/base_client.py#L437)で実行しています。
  * 更に内部で[`oci.base_client.BaseClient.request`](https://github.com/oracle/oci-python-sdk/blob/v2.113.0/src/oci/base_client.py#L652)を呼び出し、こちらが実際のHTTPリクエストを処理しています。
  * `request()`内部で「[APIからのレスポンスが200番台以外だった](https://github.com/oracle/oci-python-sdk/blob/v2.113.0/src/oci/base_client.py#L700)」時点で、（若干分類はあれど）`ServiceError`が確定でraiseされます。

上記の前提をもとに考えれば、実質的に「HTTPリクエスト～HTTPステータスから判定」までを自前実装でもしないと難しそうなので、流石に割に合わないかなと。

※これらは、OCIを普段使っておらずドキュメントとソースを読んだ範囲で考えて記述しています。もしかしたら`list_objects()`の引数の工夫次第でより良い手法が出てくるかもしれませんが、回答としては考慮外となります。

