7
3

More than 1 year has passed since last update.

Cloud FunctionsをPythonで書く

Posted at

この記事について

Cloud FunctionでPythonを使用するためのサンプルです
こちらからサンプルコードのダウンロードができます。

なぜPython?

好きだから。これに限ります。Pythonは遅いとよく言われますが、使用するメモリ量などを調整することでマシになりますし、
自分のプロジェクトでは影響が出るほどでもないからです。限界まで速さを求めるなら他の言語が良いでしょう

はじめに

Cloud Functionsのデプロイはかなり遅いです。プログラムミスで再デプロイする時の時間ほど苦痛な時はありません。
なので僕は基本的にVSCode上でテストプログラムを作成してからCloud Functionsにデプロイしています。
コードチェックもありませんので、間違えててもそのまま通過します。(デプロイは失敗します)
ローカルでテストしてデプロイするのがベターでしょう
ローカルではサービスアカウントのファイルを使用します。
Firebase -> 任意のプロジェクト -> プロジェクトの設定 -> サービスアカウント
よりjsonファイルをダウンロードしてください。

requirements

ローカル

googleライブラリがインストールされていない場合はインストールしてください。

pip install google

firebase_adminを使用するパターンも見ますがこちらはCloud Functions上での展開に時間がかかりますので、
ローカルでもgoogleを使用します。

Cloud functions上

requirements.txt
# Function dependencies, for example:
# package>=version
google-cloud-firestore==1.2.0

Cloud functions上にデプロイするときはrequirements.txtにgoogle-cloud-firestoreを追加しなければなりません。
これがないとエラーが出てデプロイできません。

おまじない

import json # 後ほど説明します
from google.cloud import firestore
from google.oauth2.service_account import Credentials

# 以下ローカルで使用する場合
cred = Credentials.from_service_account_file(filename='YOUR_SERVICE_ACCOUNT_FILE_PATH')
db = firestore.Client(credentials=cred)

# Cloud Functionsにデプロイする場合
db = firestore.Client()

# response用ヘッダー
headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET,POST',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Max-Age': '3600'
}

Credentialsはローカルでのみ使用しますので、デプロイ時は削除してください

CORS対応

Webアプリケーションから呼び出す際に、実行前にGoogle Chrome(他もあると思われますが)からpreflight requestというものが送信されます。
このrequestを正しく処理しないとエラーになり、結果を取得することができません。

def main(request):
    # preflight requestのmethodはOPTIONだそう。
    if request.method == 'OPTIONS':
        headers = {
            'Access-Control-Allow-Origin': '*', # ここは自分のドメインに変える
            'Access-Control-Allow-Methods': 'GET', # or 'POST', 'GET,POST' 許可するmethodのタイプを記入 
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Max-Age': '3600'
        }

        return ('', 204, headers)
    """
    ここにメイン処理をかく
    """
    return (jsons, 200, headers)

コレクションからデータを取得する

# エントリポイントをmainにする(自由)
def main(request):

    # ローカル用
    data = request['data']
    
    """ デプロイ用
    request_json = request.get_json()
    data = request_json['data']
    """

    snapshots = db.collection('USERS').get()
    # or
    snapshots = db.collection('USERS').stream()

    for snapshot in snapshots:
        print(snapshot.id) # ドキュメントID
        print(snapshot.to_dict()) # ドキュメント内のフィールド
    
    jsons = json.dumps({'key': 'value'}, ensure_ascii=False)
    return (jsons, 200, headers)

# ローカル用 デプロイ時は削除する
if __name__ == '__main__':
    request = {
    }
    main(request)

ドキュメントからデータを取得する

# エントリポイントをmainにする(自由)
def main(request):

    # ローカル用
    data = request['data']
    
    """ デプロイ用
    request_json = request.get_json()
    data = request_json['data']
    """

    datas = db.collection('USERS').document(data['userID']).get().to_dict()

    for key, value in datas.items():
        print(key, value)

    jsons = json.dumps({'key': 'value'}, ensure_ascii=False)
    return (jsons, 200, headers)

# ローカル用 デプロイ時は削除する
if __name__ == '__main__':
    request = {
        'data': {
            'userID': '12345'
        }
    }
    main(request)

to_dict()メソッドで辞書型としてデータを扱うことができる

getメソッドとstreamメソッドの違い

Firebaseのドキュメントではcollectionからのデータ取得の場合はstream()を使用し、
documentからの取得ではget()を使用していました。
しかし、collectionからデータを取得する際にもget()が使用できますので、違いがわかりません。
逆にdocumentにはstream()が使用できません。
強いていうならば、stream()は一度しか参照できません。以下のコードを実行するとわかります。

def main(request):

    # ローカル用
    data = request['data']
  
    snapshots = db.collection('USERS').get()
    # snapshots = db.collection('USERS').stream()
    print(snapshots)

    i = 1
    while int(i) != 0:
        snapshots
        for snapshot in snapshots:
            print(snapshot.id)
            print(snapshot.to_dict())
        i = input()

if __name__ == '__main__':
    request = {
        'data': {
            'userID': '12345'
        }
    }
    main(request)

get()の時は何度も結果が表示されるのに対して、stream()は一度しか表示されないことがわかります。

フィールド、ドキュメント、コレクションの削除

# エントリポイントをmainにする(自由)
def main(request):

    # ローカル用
    data = request['data']
    
    """ デプロイ用
    request_json = request.get_json()
    data = request_json['data']
    """

    user_ref = db.collection('USERS').document(data['userID'])

    # フィールドを削除する場合
    user_ref.set({'favorite': firestore.DELETE_FIELD}, merge=True)
    # or
    user_ref.update({'favorite': firestore.DELETE_FIELD})

    # ドキュメントを削除する場合
    user_ref.collection('datas').document('books').delete()
    
    # コレクションを削除する
    # 仕様上、コレクションを削除するにはコレクションの配下にある全てのデータを取得する必要がある。
    datas = user_ref.collection('datas').get()
    for data in datas:
        data.reference.delete()
    
    jsons = json.dumps({'key': 'value'}, ensure_ascii=False)
    return (jsons, 200, headers)

# ローカル用 デプロイ時は削除する
if __name__ == '__main__':
    request = {
        'data': {
            'userID': '12345'
        }
    }
    main(request)

レスポンスの文字化け対応

クライアントでデータ受信後、日本語が文字化けしてしまう時があります。
その時はjson.dumps()を使用します。

# レスポンスの部分のみです
jsons = json.dumps({'key': 'ばりゅ〜'}, ensure_ascii=False)
return (jsons, 200, headers)

ensure_ascii=Falseを使うことで日本語の文字化けを防ぐことが可能です。

終わり

今回は以上で終わりにします。
また、続きが出来次第投稿したいと思います。

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3