Firebase Cloud Firestore
前回Firebase Authenticationを使いました。
今回はFirebase Cloud Firestoreを使ってユーザデータを保存します。
今回の用途的にはRDBでよくないかと言われたら多分そうです。激しく。
SQLiteとかでもいいし、Amazon RDSとか使えばいいと思います。
でもSQLiteは見るのが面倒だし、RDSはお金がかかるのが嫌だし。ついでにFirebaseというかNoSQLの勉強しようかという感じです。
目次
ゴール
Firebase AuthenticationのユーザIDを保管し、ログインユーザのみが閲覧できるようにする。
Firebase Cloud Firestoreにダッシュボードからドキュメントを追加
「FIRESTOREベータ版を試しみる」をクリック。

テストモードで開始します。

コレクションにusersコレクションを追加しましょう。

「次へ」をクリックすると構造を定義します。
フィールド名と値を適当に入力します。
- fb_id: Firebase AuthenticationのID
- st_id: StripeのID
- status: プラン
- email: メールアドレス

「保存」でデータベースに追加されます。

Pythonからドキュメントを確認
秘密鍵の生成
サードパーティ製のライブラリ、pyrebasegはまだFirestoreに対応していないので、Firebase Admin SDKを利用します。
まずはFirebase consoleから「プロジェクトの設定」を開きます。

「サービスアカウント -> 新しい秘密鍵の生成」でjsonファイルを取得します。

これをFlaskプロジェクトに配置します。
このファイルのパスを以下ではKEY_PATHとして表現します。
Cloud Firestoreインスタンスの初期化
pipでSDKをインストールします。
pip install firebase-admin
以下のようにjsonファイルのパスを指定してCloud Firestoreのインスタンスを初期化します。
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
project_id = PROJECT_ID
key_path = KEY_PATH
cred = credentials.Certificate(key_path)
firebase_admin.initialize_app(cred, {
'projectId': project_id,
})
database = firestore.client()
以降このインスタンス(database)を使用してドキュメントの取得や追加を行います。
ドキュメントの取得
先ほどのdatabaseを操作します。
db.collection()でコレクションを指定、get()で全てのドキュメントを取得します。
取得したdocsに含まれるdocは、以下の操作ができます。
- id: ドキュメントのIDを取得
- to_dict(): ドキュメントを辞書形式で取得
users_ref = database.collection('users')
docs = users_ref.get()
for doc in docs:
print(doc.id)
print(doc.to_dict())
詳しくはこちら
ドキュメントの取得 -クエリの実行-
前述のget()メソッドでは全件取得でした。
クエリを実行することで絞り込みができます。
ここでは適当なfb_idに一致するドキュメントを取得してみます。
query = database.collection('users').where('fb_id', '==', FB_ID)
docs = query.get()
for doc in docs:
print(doc.id)
print(doc.to_dict())
詳しくはこちら
ドキュメントの追加
ドキュメントのIDを自動で設定してほしい場合、特に設定する必要がない場合はadd()メソッドを使います。
ドキュメントを辞書にしたものを引数にして実行すれば良いです。
data = {
'fb_id': FB_ID,
'st_id': ST_ID,
'plan': PLAN,
'email': EMAIL
}
database.collection('users').add(data)
ドキュメントIDを指定したい場合はset()メソッドを使います。
database.collection('users').document(DOCUMENT_ID).set(data)
詳しくはこちら
ドキュメントの更新 -ドキュメトIDから対象を取得-
このように取得したドキュメントに対してupdate()メソッドを実行することでドキュメントを更新します。
user_ref = database.collection('users').document(doc_id)
user_ref.update({'plan': plan})
詳しくはこちら
ドキュメントの更新 -検索クエリから対象を取得-
where句で取得したものを直接updateできたら楽なんですが。できるんでしょうか?
どうしてもwhereでdoc_idを取得、doc_idで上述のようにuse_ref取得という流れになる?
試してみました。
上記のようにID指定で取得したドキュメントはDocumentReferenceオブジェクトです。
updateメソッドが用意されています。

一方、検索で取得したものはQueryオブジェクトです。
updateメソッドがありません。

また、getメソッドで取得したものはQuerySnapshotオブジェクトを返します。
QuerySnaphshotオブジェクトは0以上のDocumentSnapshotオブジェクトを包有します。
そしてDocumetnSnapshotオブジェクトはDocumentReferenceオブジェクトのコピーのようなもので、読み取りこそできますがupdateなどの操作はできません。
したがって、検索でヒットしたドキュメントに直接updateすることはできず、ドキュメントのIDを取得することしかできません。
そのドキュメントのIDを使って上述のようにDocumentReferenceオブジェクトを取得してupdateメソッドを実行する必要があるみたいです。二度手間ですが仕方ない。
エラー
ValueError: The default Firebase app already exists. This means you called initialize_app() more than once without providing an app name as the second argument. In most cases you only need to call initialize_app() once. But if you do want to initialize multiple apps, pass a second argument to initialize_app() to give each app a unique name.
以下のようにしてアプリを初期化しました。
このinitialize_app()は一度だけ行えばよく、繰り返し行うと上記のエラーが発生します。
cred = credentials.Certificate('path/to/serviceAccountKey.json')
default_app = firebase_admin.initialize_app(cred)
なので、初期化は関数内で複数回実行されないように外出しするなどしましょう。