今回はAzure Blob Storage SDKを使って、Python3で簡単なファイル共有ツールを実装してみたいと思います。
BLOB Storageはざっくり言うとオブジェクトストレージ。ここで使っているブロックBLOBは、容量の観点で見れば普通のオンラインストレージ(ファイル)よりも安上がりです。まあS3みたいなやつ。
ちょっとしたファイルを保管しておきたいとか、参照の性能を要求しないバックアップに使うとか、他のアプリケーションのバックエンドに使いたい場合に便利です。
OneDriveやGoogle Driveなどのいわゆるオンラインストレージと比較してAPIもシンプルで使いやすい反面、Office/Windows連携や検索機能などオフィス用途の機能や画像管理、ユーザ招待型のファイル共有機能などの付加価値的な機能はありません。
Azureにはファイルストレージのサービスもあるので、SMBで接続したい場合などにはそちらを使えばいいのですが、とにかくシンプルに容量があればいい、という場合にはブロックBLOBのほうが使いやすいです。
ここでは、ストレージとして使うために基本的と思われるアップロードとダウンロードを試します。
謹製のGUIクライアントも提供されていますが、スクリプトから叩けると便利ですよね。ということでMSから提供されているPython向けSDKで実装します。
環境はPython3.6+Windows 10 Pro、Python 3系のみで検証しています。
長くなってしまったので分けますが、もうちょっと賢い実装は別記事で。
もくじ:
はじめに
Azureのストレージサービスを使うには、ストレージアカウントなるものが必要です。
ストレージアカウント毎にキー(Base64的な文字列)が発行され、今回使うブロックBLOBや、その他ファイル、ページなどのリソースにアクセスする際の認証に必要となります。
ストレージアカウントはAzure Portalから作成できます。色々設定項目はありますが、デフォルトで大丈夫です。アカウントにもいくつか種類がありますが、デフォルトの汎用v2にしておけば特に困ることはないと思います。
キーは「アクセスキー」の画面で管理できます。後ほど使うのでどこかに保存しておいて下さい。
Blob Storage
スキーマを感覚的に図にするとこんな感じ。
各アカウントに複数のコンテナを作ることができ、コンテナの中にBLOBを格納します。
URIはこんな感じ。
1 | https://<strage_account>.blob.core.windows.net/<container_name>/<blob_name> |
コンテナの中はフラットになっており、サブディレクトリなどのツリー構造はありません。
コンテナはPortalの「BLOB SERVICE」-「コンテナー」の画面から作成できます。
もちろんAPI経由でも作ることができますが、今回は特定のコンテナを使うので何か作っておきます。
上に書いたURIもエンドポイントとして表示されていますね。
BLOBを公開すれば普通のhttpでもアクセスできるようになりますが、ここではオンラインストレージとして使いたいので非公開のプライベート(デフォルト)にしておきます。
非公開の場合は、BLOBへのアクセスがアカウント毎にキーで制御されますので、最低限アカウント名とキーがあればデータの読み書きが可能です。
SDK
MSからSDKが提供されているので、Python向けのものを使います。もちろんC#など他の言語もサポートされており、概念的には同じで、使い方もよく似ています。
もともとAzure StorageはREST API(http/https)を持っていますが、SDKはそれをラップするもののようです。
SDKはいつも通りpipで簡単にインストールできます。
1 | > pip install azure-storage-blob |
いくつか依存モジュールがあるので、環境によってはちょっと時間がかかるかもしれません。
ファイル同期ツール
今回作成するのは、ローカルのファイルシステム上の特定のディレクトリにあるファイルと、Azure上のコンテナを同期するツールです。
ストレージアカウント名とキーさえ知っていればどこからでも使えるものができるはずです。
Azure Portalからコンテナの情報を見たり、BLOBをダウンロード・アップロードすることができるのでツールが多少バグっても大丈夫。
やや注意が必要な点として、上記のようにBLOBはフラットな構造になっている(コンテナを入れ子にできない)ので、配置はちょっと工夫する必要があります。
今回は最も単純に、ローカルのディレクトリ直下にあるファイルのみを対象にすることにします。
サブディレクトリ対応は次の記事で。
実装の実体はWeb APIなので普通にhttp/httpsを使える環境ならば大丈夫ですが、ここでは認証つきプロキシ環境も想定することにします。
BLOBの基本操作
今回のスクリプトです。_blob_client
クラスを定義しています。
原則的に例外はクラスの中で捕捉せず、問題があれば止まるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | import os from azure.storage.blob import BlockBlobService class _blob_client: def __init__(self, conf): # check if the home directory path is valid self.path = conf["home_path"] if not os.path.isdir(conf["home_path"]): raise Exception("home directory is not found") # build client self.client = BlockBlobService( account_name=conf["account_name"], account_key=conf["account_key"]) # set proxy if enabled if conf["proxy_enabled"]: self.client.set_proxy(conf["proxy_addr"], conf["proxy_port"], user=conf["proxy_user"], password=conf["proxy_pass"]) # set container name with checking if conf["container"] in [x.name for x in self.client.list_containers()]: self.__container = conf["container"] else: raise Exception("invalid container name") def download(self): # download all blobs to local file for blob in self.fetch_remote(): file_path = os.path.join(self.path, blob) # download blob print("receiving blob: {}".format(blob)) self.client.get_blob_to_path(container_name=self.__container, blob_name=blob, file_path=file_path) def upload(self): # upload all local file to remote blob for item in os.listdir(self.path): local_path = os.path.join(self.path, item) if os.path.isfile(local_path): # upload file to blob print("uploading file: {}".format(item)) self.client.create_blob_from_path(container_name=self.__container, blob_name=item, file_path=local_path) def fetch_remote(self): # list all blob name in the container print("getting blob list...") return [x.name for x in self.client.list_blobs(self.__container)] def clear(self): # remove all blobs in the container for blob in self.fetch_remote(): print("removing blob: {}".format(blob)) self.client.delete_blob(container_name=self.__container, blob_name=blob) |
今回使うAPI (azure.storage.blob
モジュールBlockBlobService
クラス)はこんな感じ。
メソッド | 用途 |
---|---|
BlockBlobService() | ブロックBLOBサービス |
.list_containers() | コンテナ一覧の取得 |
.list_blobs() | BLOB一覧の取得 |
.create_blob_from_path() | ローカルパスからBLOBをアップロード |
.get_blob_to_path() | BLOBをローカルパスへダウンロード |
.delete_blob() | BLOBの削除 |
以下により詳細な使い方を記載しておきます。
Block BLOB Serviceへの接続
コンストラクタに必要な設定を辞書(dict)でまとめて渡します。
# 辞書にする必要はないのですが、まあ基本的なパラメータが明確になると思いますので。。。
Azure上のコンテナ("container"
)と同期対象のローカルディレクトリ("home_path"
)のパスを指定します。
1 2 3 4 5 6 7 | config = {"account_name": "strg_account", "account_key": "...==", "container": "test", "proxy_enabled": True, "proxy_addr": "proxy.server.name", "proxy_port": "80", "proxy_user": "username", "proxy_pass": "password", "home_path": "C:\\path\\to\\dir"} |
基本となるのはBLOBストレージを扱うためのBlockBlobService
クラスです。
必要な項目を指定してBlockBlobService
クラスのインスタンスを作成します。上にも書きましたが、アカウント名とキーを正しく指定すれば接続可能です。
内部的に使用するプロトコルはprotocol
で指定しますが、デフォルトでhttpsになっています。
1 2 3 | # build client self.client = BlockBlobService( account_name=conf["account_name"], account_key=conf["account_key"]) |
このBlockBlobService
オブジェクトの各メソッドを呼び出してコンテナやBLOBを操作します。
認証つきプロキシを通す場合には、BLOBの操作を行う前にset_proxy()
メソッドを使っておきます。
1 2 3 | self.client.set_proxy(conf["proxy_addr"], conf["proxy_port"], user=conf["proxy_user"], password=conf["proxy_pass"]) |
上記のクラスはストレージ内の任意のコンテナにアクセスできるので必須ではありませんが、今回は専用にコンテナを用意するので、コンストラクタで保存しておきます。
せっかくなのでちゃんとアクセスできるかチェックします。
list_containers()
メソッドがコンテナオブジェクトのリストを返してくるので、指定された名前のコンテナがあるかどうか確認します。
1 2 | if conf["container"] in [x.name for x in self.client.list_containers()]: self.__container = conf["container"] |
一覧表示
_blob_client.fetch_remote()
メソッドでBLOB一覧の取得を実装しています。
list_blobs()
メソッドはコンテナ名を指定すると、BLOB情報に相当するオブジェクトを指すリスト(正確にはジェネレータ)を返してきますので、BLOB名(blob.name
)のみを取り出しています。
1 | return [x.name for x in self.client.list_blobs(self.__container)] |
アップロード
_blob_client.upload()
メソッドでBLOBのアップロードを実装しています。
create_blob_from_path()
メソッドにコンテナ名とBLOB名、ローカルファイルへのパスを指定してアップロードします。
1 2 3 | self.client.create_blob_from_path(container_name=self.__container, blob_name=item, file_path=local_path) |
今回は対称のディレクトリ直下のすべてのファイルをアップロードします。ディレクトリがあった場合は単純に無視される実装です。BLOB名はファイルと同じにしています。
アップロードされていないファイルのみをアップロードする実装はまた次回。
ダウンロード
_blob_client.download()
メソッドでBLOBのダウンロードを実装しています。
get_blob_to_path()
メソッドにコンテナ名とBLOB名、ローカルファイルへのパスを指定して同期的にダウンロードします。ローカルファイルが既に存在する場合は上書きされます。
1 2 3 | self.client.get_blob_to_path(container_name=self.__container, blob_name=blob, file_path=file_path) |
今回は対象のコンテナにある全てのBLOBがダウンロードされる実装です。ローカルにないファイルのみをダウンロードする実装はまた次回。
削除
_blob_client.clear()
メソッドでBLOBの削除を実装しています。
delete_blob()
メソッドにコンテナ名とBLOB名を指定することで同期的に削除します。今回は対象のコンテナにある全てのコンテナを削除する実装です。
1 2 | self.client.delete_blob(container_name=self.__container, blob_name=blob) |
テスト
さて、今回作成したスクリプトをテストしてみたいと思います。
_blob_client
クラスをインスタンス化して、各メソッドを呼ぶと、ちゃんとAzureのブロックBLOBストレージを操作できていることがわかりますね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # "blobclient.py"と同じディレクトリで Python シェルを起動 > python # 設定を定義して_blob_clientインスタンスを作成 >>> from blobclient import _blob_client >>> conf = {'account_name': 'strg_account', 'account_key': '...==', 'container': 'test', 'proxy_enabled': False, 'home_path': 'C:\\path\\to\\dir'} >>> client = _blob_client(conf) # 全てのローカルファイルをアップロード >>> client.upload() uploading file: image01.jpg uploading file: 新しいテキスト ドキュメント.txt # アップロードされているBLOB 名を参照 >>> client.fetch_remote() getting blob list... ['image01.jpg', '新しいテキスト ドキュメント.txt'] # BLOB をローカルへダウンロード >>> client.download() getting blob list... receiving blob: image01.jpg receiving blob: 新しいテキスト ドキュメント.txt # 全てのBLOB を削除 >>> client.clear() getting blob list... removing blob: image01.jpg removing blob: 新しいテキスト ドキュメント.txt |
次回に続きます。