【Azure Blob Storage】PythonからBlob・コンテナーを操作してみる

# はじめに
AzureのBlobを[公式のクイックスタート](https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-quickstart-blobs-python?tabs=managed-identity%2Croles-azure-portal%2Csign-in-azure-cli#download-blobs)を参考にPythonからアップロード・ダウンロードしてみます。またAutoMLでトレーニングしたベストモデルに出力された`outputs`フォルダなどをローカルにダウンロードしてみます。

# 開発環境
+ OS Windows 10(NVIDIA GTX 1650Ti,16GB RAM, i5-10300H CPU)
+ Visual Studio Code 1.73.1
+ Python 3.8

# パスワードレスでBlobのアクセスを行ってみる
Blobへのアクセスは、パスワードレスの方法と接続文字列を使った2種類のやり方があります。
まずはパスワードレスのやり方でやってみます。

## ロール割り当て
ストレージアカウントを開き、「アクセス制御」＞「追加」＞「ロール割り当ての追加」をクリックします。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/edaa3b3b-a8a7-d7a2-9cc5-57054210223c.png)
目的のロールに絞ります。今回は「ストレージBLOB共同作成者」を選びました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/8a29daeb-b859-5557-20f0-4545a4115f88.png)
そして、メンバーの選択を行います。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/f9225992-856d-3501-b2e1-e96f5d3332d2.png)
これで「レビューと割り当て」を選んで完了です。

これでいよいよPythonからBlobを操作してみます。
まずはコンテナー作成から行います。

## コンテナー作成
Azureの認証を`DefaultAzureCredential`を用いて行います。
そのために、まずはAzure CLIへログインします。
インストールしていない方は[こちら](https://learn.microsoft.com/ja-jp/cli/azure/install-azure-cli)をご覧ください。
```
az login
```
次に下記コードを実行してコンテナーを作成します。
```create_container.py
import os, uuid
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient

try:
    print("Azure Blob Storage Python quickstart sample")
    account_url = "https://<ストレージアカウント>.blob.core.windows.net"
    default_credential = DefaultAzureCredential()
    blob_service_client = BlobServiceClient(account_url, credential=default_credential)
    #コンテナー作成
    container_name = "<コンテナー名>"
    containe_client = blob_service_client.create_container(container_name)

        blob_client.upload_blob(data)
except Exception as ex:
    print("Exception:")
    print(ex)
```
`account_url`の<ストレージアカウント>は赤線部分の文字列をコピペします。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/83eacb59-4264-47b6-92f5-a389d98a5b51.png)
`container_name`には、作成するコンテナーの名前を入れます。今回は「sas-test-tkawano」としました。
```python
container_name = "sas-test-tkawano"
```
Azure Portalで確認したところ、コンテナーが作成されていました。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/778dbe3e-6104-4d57-1dbc-1f4b860f17d1.png)

続いて作成したコンテナーにBLOBをアップロードしてみます。

## BLOBにファイルをアップロード
ローカルにある"train.csv"というファイルをアップロードしてみます。
`Blobclient`クラスの`get_blob_client()`で作成済みのコンテナーを指定できます。
```upload_blob.py
import os, uuid
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient

try:
    account_url = "https://<ストレージアカウント名>.blob.core.windows.net"
    default_credential = DefaultAzureCredential()
    blob_service_client = BlobServiceClient(account_url, credential=default_credential)

    container_name = "<コンテナー名>"

    #アップロードするファイルが格納されたパス・ファイル名
    local_path="./"
    local_file_name = "train.csv"

    blob_client = blob_service_client.get_blob_client(container=container_name, blob=local_file_name)

    #アップロード
    with open(file=upload_file_path, mode="rb") as data:
        blob_client.upload_blob(data)
except Exception as ex:
    print("Exception:")
    print(ex)
```
`local_path`には格納するファイルが入っているパス名を入力し、`local_file_name`にはアップロードするファイル名を入れます。

実行してコンテナーを見ると「train.csv」がアップロードされていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/9d7cfec8-5091-b50d-67d1-fcbfe153d428.png)

今度はアップロードしたBlobをローカルへダウンロードしてみます。

# ダウンロード
ローカルのパスを指定し、そこへBLOBをダウンロードします。
```download.py
import os, uuid
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient

try:
    account_url = "https://<ストレージアカウント名>.blob.core.windows.net"
    default_credential = DefaultAzureCredential()
    blob_service_client = BlobServiceClient(account_url, credential=default_credential)
    #コンテナー
    container_name = "<コンテナー名>"

    #ファイル
    local_path="./"
    local_file_name="train.csv"

    container_client=blob_service_client.get_container_client(container_name)

    #ダウンロード
    download_file_path = os.path.join(local_path,str.replace(local_file_name, ".csv","DOWNLOAD.csv"))
    print("\nDownloading blob to \n\t")

    with open(file=download_file_path, mode="wb") as download_file:
        download_file.write(container_client.download_blob(local_file_name).readall())

except Exception as ex:
    print("Exception:")
    print(ex)

```
実行すると、ローカルに`trainDOWNLOAD.csv`ができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/b9e10793-1f2a-e34d-196b-087a060a2dcb.png)

さて、ファイルのアップロード・ダウンロードはできたので、今度はフォルダのダウンロードを行います。

## フォルダのダウンロード
前回AutoMLで作成した一番精度の良いモデルの`outputs`フォルダの中身をダウンロードしてみます。フォルダのダウンロードについては流行りのChat GPTにやり方を教えてもらいました^^
![MicrosoftTeams-image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/dd2c364f-1930-d618-6df4-6956c493dc76.png)

フォルダの中にあるファイルをfor文で1つずつ取り出していけば、フォルダのダウンロードができるようです。
```folder.py
import os, uuid
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient

try:
    account_url = "https://<ストレージアカウント名>.blob.core.windows.net"
    default_credential = DefaultAzureCredential()
    blob_service_client = BlobServiceClient(account_url, credential=default_credential)

    container_name = "<コンテナー名>"
    container_client = blob_service_client.get_container_client(container=container_name)

    outputs_path = "<BLOB名>"
    blob_list = container_client.list_blobs(name_starts_with=outputs_path)

    outputs_folder = "<ダウンロード先のフォルダのパス>"
    if not os.path.exists(outputs_folder):
        os.makedirs(outputs_folder)

    for blob in blob_list:
        blob_client = container_client.get_blob_client(blob.name)
        file_path=os.path.join(outputs_folder,(blob.name.split("/")[-1]))
        with open(file_path, mode="wb") as download_file:
            download_file.write(container_client.download_blob(blob.name).readall())

except Exception as ex:
    print("Exception:")
    print(ex)
```
`outputs_path`にはダウンロードするフォルダがあるパスを入れます。
今回の「outputs」フォルダは「ExperimentRun/dcid.careful_ant_rbvh6tjqm5_3/」の中にあったので次のように書きます。
```
outputs_path = "ExperimentRun/dcid.careful_ant_rbvh6tjqm5_3/outputs/"
```
あとは`outputs_path`から始まる名前のBLOBのリストを`list_blobs()`で取り出し、1つずつファイルをローカルへダウンロードします。

ChatGPTから教えてもらったコードをほぼそのまま実行しただけですが、フォルダごとダウンロードできてました！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/a92c4aec-4e7b-4a3f-9b53-7cf943c59586.png)

以上がパスワードレスのやり方でPythonからコンテナー作成やBLOBをアップロード・ダウンロードする方法でした。続いて接続文字列を用いてBlob操作を行います。

# 接続文字列を使ったやり方
1. ストレージアカウントから接続文字列取得
「アクセス キー」をクリックすると「接続文字列」があるのでこれを保存します。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2976188/eb76703d-8781-20ea-a6dd-d4a382c640c3.png)

2. 文字列の構成
コマンドプロンプトで以下の<yourconnectionstring>に接続文字列をコピペして実行し、環境変数を追加します。
```
setx AZURE_STORAGE_CONNECTION_STRING "<yourconnectionstring>"
```
追加したら一度コマンドプロンプトを再起動します。

それでは接続文字列を使って今度はBLOBリストを作成しましょう。
リストは`BlobServiceClient`クラスの`list_blobs()`から得られます。
```list.py
import os, uuid
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient

try:
    connect_str = os.getenv("AZURE_STORAGE_CONNECTION_STRING")
    blob_service_client = BlobServiceClient.from_connection_string(connect_str)
    container_name = "<コンテナー名>"

    container_client = blob_service_client.get_container_client(container_name)

    blob_list=container_client.list_blobs()
    for blob in blob_list:
        print("\t"+blob.name)

except Exception as ex:
    print('Exception:')
    print(ex)
```
実行後、train.csvのみ返ってきました。
```
        train.csv
```

公式ではパスワードレスのやり方が推奨されていますが、接続文字列を使っても同じようにBlob Storageへアクセスできました。

次の記事ではSASを生成し、SAS URIを使ってBLOBをダウンロードしてみます。