はじめに
グラスゴー・コーマ・スケールというGCSもある。nikkieです。
Python製のライブラリGCSFSを使っていて気になったことを、ソースコードを読んで調べました。
目次
- はじめに
- 目次
- 結論(タイトルへの回答)
- バージョン情報
- GCSFS
- Exampleのコードから始めるソースコードリーディング
- google-authのdefaultメソッド(google.auth.default)
- 終わりに
結論(タイトルへの回答)
- GCSFSが独自に、環境変数やapplication default credentialsを読み込む実装をしているわけではない
- google-authライブラリのdefaultメソッドを呼び出している(
google.auth.default
) - defaultメソッドが環境変数もapplication default credentialsも扱う
バージョン情報
以下のバージョンのソースコードを読みました。
- GCSFS 2023.6.0
- google-auth 2.21.0
GCSFS
Pythonic file-system for Google Cloud Storage
GCS(Google Cloud Storage)のファイル操作に使っています。
ドキュメントには「This software is beta, use at your own risk.」(ベータ版です。ご自身の責任において使ってください)とあります1。
pip install gcsfs
でインストールでき、簡単に使い出せます
https://gcsfs.readthedocs.io/en/latest/#examples
>>> import gcsfs >>> fs = gcsfs.GCSFileSystem(project='my-google-project')
GCSFSのCredentials
GCSへの接続情報(クレデンシャル)の設定も簡単です。
例えば以下の2つの方法があります
- 環境変数
GOOGLE_APPLICATION_CREDENTIALS
gcloud auth application-default login
サポートするクレデンシャルについては、ドキュメントにも記載があります。
https://gcsfs.readthedocs.io/en/latest/#credentials
どういう仕組みで環境変数でもgcloud auth
コマンドでもクレデンシャルを設定できているのかが、私、気になりました!
Exampleのコードから始めるソースコードリーディング
GCSFileSystem初期化
GCSFileSystemの実装はcore.pyにあります。
https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/core.py#L153
このdocstringはCredentialsのドキュメントと同内容です。
初期化する際(__init__
)に、GoogleCredentials
を初期化して設定しています。
https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/core.py#L318
self.credentials = GoogleCredentials(project, access, token)
GoogleCredentials初期化
GoogleCredentialsの実装はcredentials.pyにあります。
https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/credentials.py#L39
__init__
で初期化する中でconnectメソッドを呼び出しています。
https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/credentials.py#L50
self.connect(method=token)
ここでtoken
が指す値ですが、
- Examplesのコードで
GCSFileSystem
のtoken引数は指定していない- デフォルト値の
None
2
- デフォルト値の
GoogleCredentials
の初期化に渡るtoken
の値はNone
- つまり、
self.connect(method=None)
と呼び出している
GoogleCredentialsのconnectメソッド
https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/credentials.py#L208
実装は、Credentialsのドキュメントの説明と同様でした(一致しているのは当たり前ですね)。
method
引数の値がNone
の場合の動きを見ていきます。
method is None
ですから、230行目からのブロックが実行されます。
https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/credentials.py#L230
これは(Credentialsのドキュメントに概要があるように)
- google_default
- cache
- cloud
- anon
の順でconnect
メソッドを(再帰的に)呼び出します。
https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/credentials.py#L232
connectメソッド呼び出しで例外が送出されなければ、そこでクレデンシャルが取得できたので反復は終了です(for
文をbreak
)。
どの呼び出しでも例外が送出された場合は、クレデンシャルが取得できなかったということであり、connectメソッドも例外を送出します3。
では、再帰的な呼び出しでself.connect(method="google_default")
のようになった場合はどうなるのでしょうか。
実行されるブロックは以下です。
https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/credentials.py#L249-L250
self.__getattribute__("_connect_" + method)()
self.method = method
この中の__getattribute__
がポイント4!
ここでは_connect_google_default
メソッドを取得し、続くカッコ()
で呼び出します。
文字列を渡して__getattribute__
でメソッドを取得できるのか〜!
GoogleCredentialsの_connect_google_defaultメソッド
https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/credentials.py#L76
google.auth.default
メソッドを呼び出します5。
ここが今回の疑問の答えです。
google-authのdefaultメソッド(google.auth.default
)
https://googleapis.dev/python/google-auth/2.21.0/reference/google.auth.html#google.auth.default
ドキュメントに書かれています。
This function acquires credentials from the environment in the following order:
全部で5つ書かれていますが、今回の疑問に関係するのは次の2つ。
第一に環境変数GOOGLE_APPLICATION_CREDENTIALS
に指定されたパスのサービスアカウントファイル(JSONファイル)を探し、それがない場合はgcloud auth application-default login
で有効にされたapplication default credentialsを探す(どちらもないときの動きは今回は省略)わけですね!
実装は以下にあります。
https://github.com/googleapis/google-auth-library-python/blob/v2.21.0/google/auth/_default.py#L543
_get_explicit_environ_credentials
が環境変数で指定したパスのファイルから読み込む関数です。
その実装の中では6、environment_vars.CREDENTIALS
とGOOGLE_APPLICATION_CREDENTIALS
という名前の環境変数にアクセスしています!
https://github.com/googleapis/google-auth-library-python/blob/v2.21.0/google/auth/environment_vars.py#L36
終わりに
GCSFSを使っていて覚えた疑問からソースコードを読み、google-authライブラリのdefaultメソッドを知りました。
defaultメソッドが、環境変数GOOGLE_APPLICATION_CREDENTIALS
が指すJSONファイルから読み込んだクレデンシャルやapplication default credentialsを返しています。
環境変数からもapplication default credentialsからもという振る舞いはgoogle-authライブラリによるものなので、google-authライブラリを使えばこの振る舞いを再利用できるわけですね!
ソースコードリーディングとしては、connect
メソッドの4回の呼び出しの実装をそれぞれざっと眺めた結果、_connect_google_default
メソッドに当たりがつきました。
__getattribute__
で文字列からメソッドを取得でき、それを呼び出すのはちょっとメタくもあり、学びとなりました。
またconnect
メソッドを再帰的に呼ぶ実装も興味深かったです。
- https://gcsfs.readthedocs.io/en/latest/#gcsfs↩
- https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/core.py#L270↩
- https://github.com/fsspec/gcsfs/blob/2023.6.0/gcsfs/credentials.py#L247↩
- https://docs.python.org/ja/3/reference/datamodel.html#object.__getattribute__↩
- 依存関係にも指定されています。https://github.com/fsspec/gcsfs/blob/2023.6.0/requirements.txt#L4 (setup.pyでrequirements.txtを読み込む実装なのです)↩
- https://github.com/googleapis/google-auth-library-python/blob/v2.21.0/google/auth/_default.py#L249↩