# Cloud FirestoreからPython3.7でdocument一覧、subcollectionのdocument一覧を取得する

Cloud FirestoreをPython3.7からgoogle-cloud-firestoreを使ってデータを取得する。
以下のデータ構造から一覧を取得する方法をまとめる。

- rooms [collection]
    - name
    - messages [subcollection]
        - from
        - msg

# 認証情報を設定する

まずは認証情報を設定する。
認証情報を設定しておかないと以下のようなエラーが表示される。

google.api_core.exceptions.ServiceUnavailable: 503 Getting metadata from plugin failed with error: ('invalid_grant: Not a valid email or user ID.', '{\n  "error": "invalid_grant",\n  "error_description": "Not a valid email or user ID."\n}')

Google Cloud Platformの画面で「APIとサービス」>「認証情報」>「認証情報を作成」>「サービスアカウントキー」から認証情報のJSONをダウンロードし、環境変数GOOGLE_APPLICATION_CREDENTIALSに設定する。

$ export GOOGLE_APPLICATION_CREDENTIALS="/Users/nancy/.config/gcloud/app-2c981dbdb83a.json"

# google-cloud-firestoreのインストールとデータの準備

Pythonからfirestoreへデータを追加したり参照したりするために、google-cloud-firestoreをインストールする。

$ pipenv install google-cloud-firestore==1.4.0
$ pipenv shell

最初に示したデータ構造の初期データを用意する。
app.py

from google.cloud import firestore

db = firestore.Client()

# roomAのデータ準備
room_a_ref = db.collection('rooms').document('roomA')
room_a_ref.set({'name': '総合格闘技の対戦カードについて'})
message_ref = room_a_ref.collection('messages').document('message1')
message_ref.set({'from': 'nancy', 'msg': '試合いよいよ明日ですね!'})

message2_ref = room_a_ref.collection('messages').document('message2')
message2_ref.set({'from': 'kou', 'msg': 'たけぽんの出番やな'})

# roomBのデータ準備
room_b_ref = db.collection('rooms').document('roomB')
room_b_ref.set({'name': 'TWOのサトリCEOの今後'})
message_b_ref = room_b_ref.collection('messages').document('message1')
message_b_ref.set({'from': 'yamada', 'msg': 'サトリCEOは7ヶ国語はなせるらしいですよ。'})

message_b2__ref = room_b_ref.collection('messages').document('message2')
message_b2__ref.set({'from': 'take', 'msg': 'ほぇ〜そりゃすごい'})

# collectionのdocument一覧を取得する(subcollectionは含めない)

rooms = db.collection('rooms')
docs = rooms.stream()
for doc in docs:
    print(docs)
    print(u'{} => {}'.format(doc.id, doc.to_dict()))
$ python app.py 
roomA => {'name': '総合格闘技の対戦カードについて'}
roomB => {'name': 'TWOのサトリCEOの今後'}

# collectionのdocument一覧とsubcollectionのdocument一覧をまとめて取得する

これ、もっとスマートな方法あるのかな。
わたしはループまわす方法しか見つけられなかったです... db.collectionCollectionReferenceクラスが取得できる。

CollectionReferenceクラス
list_documents()メソッドでDocumentReferenceクラスの一覧が取得できる。
stream()メソッドでDocumentSnapshotクラスの一覧を取得できる。

DocumentReferenceクラス
get()メソッドでDocumentSnapshotクラスが取得できる。これがデータ持ってる。
collection()メソッドでCollectionReferenceクラスを取得できる。

from google.cloud import firestore

db = firestore.Client()

rooms_ref = db.collection('rooms')
for doc_ref in rooms_ref.list_documents():
    doc = doc_ref.get()
    print('{} => {}'.format(doc.id, doc.to_dict()))

    messages_ref = doc_ref.collection('messages')
    for sub_doc in messages_ref.stream():
      print('{} => {}'.format(sub_doc.id, sub_doc.to_dict()))
$ python app.py 
roomA => {'name': '総合格闘技の対戦カードについて'}
message1 => {'from': 'nancy', 'msg': '試合いよいよ明日ですね!'}
message2 => {'from': 'kou', 'msg': 'たけぽんの出番やな'}
roomB => {'name': 'TWOのサトリCEOの今後'}
message1 => {'from': 'yamada', 'msg': 'サトリCEOは7ヶ国語はなせるらしいですよ。'}
message2 => {'msg': 'ほぇ〜そりゃすごい', 'from': 'take'}

# subcollectionを指定して、そのsubcollectionのdocument一覧を取得する

親のcollectionから、document、subcollectinをたどれば取得できる。

messages_ref = db.collection('rooms').document('roomA').collection('messages')
docs = messages_ref.stream()
for doc in docs:
    print(u'{} => {}'.format(doc.id, doc.to_dict()))
$ python app.py 
message1 => {'from': 'nancy', 'msg': '試合いよいよ明日ですね!'}
message2 => {'from': 'kou', 'msg': 'たけぽんの出番やな'}

# 親のcollectionをまたいでsubcollectionのdocument一覧を取得する

collection_group()メソッドで取得できる。

messages_ref = db.collection_group('messages')
docs = messages_ref.stream()
for doc in docs:
    print(u'{} => {}'.format(doc.id, doc.to_dict()))
$ python app.py 
message1 => {'from': 'nancy', 'msg': '試合いよいよ明日ですね!'}
message2 => {'from': 'kou', 'msg': 'たけぽんの出番やな'}
message1 => {'from': 'yamada', 'msg': 'サトリCEOは7ヶ国語はなせるらしいですよ。'}
message2 => {'from': 'take', 'msg': 'ほぇ〜そりゃすごい'}

# 関連記事

Python3.7でCloud Firestoreを操作する

# 参考

Data modelの説明
https://cloud.google.com/firestore/docs/data-model (opens new window)

サンプルコード
https://github.com/GoogleCloudPlatform/python-docs-samples/blob/1625f8160939c5ead5951e2b15cd39f05ad585b5/firestore/cloud-client/snippets.py (opens new window)

APIのドキュメント
https://googleapis.github.io/google-cloud-python/latest/index.html (opens new window)