PythonでGoogle Calendar APIを呼び出す方法

記事タイトルとURLをコピーする

G-gen の杉村です。 Cloud Functions で動作する Python プログラムから Google Calendar API を呼び出す方法をご紹介します。

検証内容

プログラムの内容

Cloud Functions で動作する Python プログラムから Google Calendar API を呼び出す際の認証について検証しました。

今回は単純化のため、以下のようなプログラムとしています。

  1. Google Calendar API をコールして日本の祝日一覧を取得
  2. 取得した祝日一覧を BigQuery テーブルに INSERT

実際のユースケースでは Google Calendar から従業員の予定を取得して BigQuery に投入し、分析するなどの用途が考えられます。

Google API への認証

今回検証したかった内容は Google Calendar を始めとする、 Google API への認証です。

Google Calendar や Gmail は Google Cloud 製品ではなく Google 製品です。そのため Cloud IAM を使った認証・認可がサポートされていません。

API キーによる認証、OAuth 2.0 による認証、サービスアカウントによる認証がサポートされています。

Cloud Functions など Google Cloud 上の実行環境で動作するプログラムではサービスアカウントを用いた認証が最もセキュア・低工数であると考えられるため、この方法を検証します。

検証の流れ

検証の全体の流れは以下のとおりです。

準備

  1. Google Cloud プロジェクトにおいて Google Calendar API を有効化
  2. サービスアカウントを作成
  3. サービスアカウントに BigQuery データ編集者 ログ書き込み ロールを付与 (※)
  4. Python プログラムを Cloud Functions (2nd gen) にデプロイ (Functions にはサービスアカウントをアタッチ)

(※) BigQuery データ編集者 は BigQuery テーブルへのデータ書き込みため、 ログ書き込み は Cloud Logging へのログ出力のため

処理の流れ

  1. google-auth ライブラリでサービスアカウントの認証情報を取得
  2. google-api-python-client ライブラリでサービスインスタンス生成
  3. Google Calendar API をコールして日本の祝日一覧を取得
  4. google-cloud-bigquery ライブラリで祝日一覧を BigQuery テーブルに INSERT

注目すべきは、サービスアカウントを作成すれば、IAM 権限を付与しなくても同じプロジェクトで有効化された Google Calendar API にアクセスできるという点です。

ここからは、各手順を説明していきます。

Google Calendar API 有効化

まずはじめに Google Cloud コンソール > API とサービス > 有効な API とサービス の画面 (リンク) から Google Calendar API を有効化します。

同画面では多数の API のリストが表示されますが、テキストボックスでフィルタをかけることができます。 calendar と入力するとサジェストされるはずです。

もしくは以下のコマンドでも API を有効化できます。

gcloud services enable calendar-json.googleapis.com

サービスアカウント作成・設定

サービスアカウント作成

Google Cloud コンソール > IAM と管理 > サービス アカウント の画面 (リンク) からサービスアカウントを作成します。自分のプロジェクトが正しく選択されていることを確認してください。

サービスアカウントはプロジェクトに所属するリソースです。 Google Calendar API を有効化したのと同じプロジェクトに、サービスアカウントを作成する必要があります。

今回は表示名を get-holidays として作成します。

サービスアカウント ID は get-holidays@${PROJECT}.iam.gserviceaccount.com となります (${PROJECT} はプロジェクト ID です) 。

サービスアカウントへ IAM 権限付与

次にこのサービスアカウントに IAM 権限を付与します。

Google Calendar API をコールするには IAM 権限は必要ありませんが、今回は BigQuery にデータを書き込んだり、Cloud Logging にログ出力するために IAM 権限が必要です。

今回はプロジェクトレベルでの権限付与とします。

Google Cloud コンソール > IAM と管理 > IAM の画面 (リンク) に遷移します。繰り返しになりますが自分のプロジェクトが正しく選択されていることを確認してください。

先程作成したサービスアカウントに BigQuery データ編集者 ログ書き込み の IAM ロールを付与します。

コマンドライン

前述の「サービスアカウント作成」と「IAM 権限付与」の作業は以下のコマンドでも実施できます。

PROJECT="<プロジェクト ID に置き換えてください>"
ACCOUNT_NAME="get-holidays"
 
gcloud iam service-accounts create ${ACCOUNT_NAME} --display-name="${ACCOUNT_NAME}"
 
gcloud projects add-iam-policy-binding ${PROJECT} --member="serviceAccount:${ACCOUNT_NAME}@${PROJECT}.iam.gserviceaccount.com" --role="roles/logging.logWriter"
 
gcloud projects add-iam-policy-binding ${PROJECT} --member="serviceAccount:${ACCOUNT_NAME}@${PROJECT}.iam.gserviceaccount.com" --role="roles/bigquery.dataEditor"

ソースコードの解説

ソースコード

以下のソースコードを使います。

今回は Cloud Functions の HTTP 関数 を想定して用意しました。

#!/usr/bin/env python
 
import datetime
import logging
 
from flask import abort
 
import google.auth
from googleapiclient.discovery import build
 
import google.cloud.bigquery
import google.cloud.logging
 
# ロギング設定
logging.basicConfig(
        format = "[%(asctime)s][%(levelname)s] %(message)s"
    )
logger = logging.getLogger()
 
# Cloud Logging への連携
logging_client = google.cloud.logging.Client()
logging_client.setup_logging()
logger.setLevel(logging.INFO)
 
# BigQuery Data Transfer Service のクライアント生成
client = google.cloud.bigquery.Client()
 
 
def get_holidays(dataset, table, year):
    """
    特定年の祝日一覧の取得とテーブルへの書き込み
    """
 
    logger.info(f"Getting holidays for year: {year}")
 
    # 実行環境のデフォルトクレデンシャル = Cloud Functions にアタッチされているサービスアカウントを取得
    credentials, project = google.auth.default()
 
    # サービスを生成
    service = build('calendar', 'v3', credentials=credentials, cache_discovery=False)
 
    # Google Calendar API 呼び出し
    result = service.events().list(
        calendarId='japanese__ja@holiday.calendar.google.com',
        timeMin=str(year) + '-01-01T00:00:00.000000Z',
        timeMax=str(year) + '-12-31T23:59:59.999999Z',
        singleEvents=True,
        orderBy='startTime'
    ).execute()
    holiday_info = result.get('items', [])
 
    # INSERT するリスト作成
    holidays = []
    for holiday in holiday_info:
        name = holiday['summary']
        date = holiday['start']['date']
        holidays.append([name, date])
 
    # スキーマ定義
    schema = [
        google.cloud.bigquery.SchemaField("name", "STRING", "REQUIRED", "祝日の名称"),
        google.cloud.bigquery.SchemaField("date", "DATE", "REQUIRED", "祝日の日付")
    ]
 
    # テーブルの定義
    table_id = f"{project}.{dataset}.{table}"
    table = google.cloud.bigquery.Table(table_id, schema=schema)
 
    # テーブルにINSERT
    client.insert_rows(table=table, rows=holidays)
 
    return None
 
 
def main(request):
    # リクエストからパラメータを取得
    request_dict = request.get_json()
    logger.info(request_dict)
 
    # パラメータチェック
    if ('dataset' in request_dict):
        dataset = request_dict['dataset']
    else:
        error_message = "Parameter dataset is missing."
        logger.error(error_message)
        return abort(400)
 
    if ('table' in request_dict):
        table = request_dict['table']
    else:
        error_message = "Parameter table is missing."
        logger.error(error_message)
        return abort(400)
 
    # メイン処理
    try:
        # 現在の西暦を取得
        this_year = datetime.date.today().year
 
        # Google Calendar から祝日を取得して BigQuery に書き込み
        get_holidays(dataset, table, this_year)
 
    except Exception as e:
        logger.error(e)
        return abort(500)
 
    return "OK"

このソースコードの認証に関わる部分をご説明します。

パッケージのインポート

#!/usr/bin/env python
 
import datetime
import logging
 
from flask import abort
 
import google.auth
from googleapiclient.discovery import build
 
import google.cloud.bigquery
import google.cloud.logging

必要なパッケージのインポートを行います。

Cloud Functions では必要なパッケージを requirements.txt に記載してデプロイパッケージに含めることで自動的に環境がビルドされます。 requirements.txt の作成を含めた Python の環境構築手順は後述します。

import google.authfrom googleapiclient.discovery import build が認証に関わるライブラリです。

認証情報取得

get_holidays 関数で Google Calendar API を呼び出しています。

        # 実行環境のデフォルトクレデンシャル = Cloud Functions にアタッチされているサービスアカウントを取得
        credentials, project = google.auth.default()

google-auth は Google API の認証のためのライブラリです。

default() 関数により実行環境のデフォルトクレデンシャル = 今回は Cloud Functions にアタッチされているサービスアカウントの認証情報を取得します。この書き方では credentials 変数にはライブラリ独自の Credentials 型で認証情報が代入され project 変数には str 型で実行環境のデフォルトプロジェクトのプロジェクト ID が代入されます (参考)。

なおかつては oauth2client と言う名称のライブラリが存在しましたがこちらは deprecated (廃止予定・非推奨) となり現在は google-auth が推奨です。

サービスオブジェクト作成

Google API Python Client の build() によりサービスオブジェクトを生成します。Google の API を呼ぶためのインターフェイスを生成するイメージです。

# サービスを生成
        service = build('calendar', 'v3', credentials=credentials, cache_discovery=False)

cache_discovery=Falseoauth2client のバージョン 4 以前でサポートされていた機能を無効化するための記述です。これが無いと、以下のようなログメッセージが出力されます (実動作には影響ありません) 。

file_cache is only supported with oauth2client<4.0.0

API 呼び出し

        # Google Calendar API 呼び出し
        result = service.events().list(
            calendarId='japanese__ja@holiday.calendar.google.com',
            timeMin=str(year) + '-01-01T00:00:00.000000Z',
            timeMax=str(year) + '-12-31T23:59:59.999999Z',
            singleEvents=True,
            orderBy='startTime'
        ).execute()

execute() で実際に API を呼び出しています。 japanese__ja@holiday.calendar.google.com は日本の祝日を保持しているカレンダーリソースで、 Google Calendar がデフォルトで持っています。

BigQuery への書き込み

BigQuery Python Client の insert_rows() でテーブルにデータを INSERT します。

        # INSERT するリスト作成
        holidays = []
        for holiday in holiday_info:
            name = holiday['summary']
            date = holiday['start']['date']
            holidays.append([name, date])

入力する値は [ ["カラムAの値1", "カラムBの値1"], ["カラムAの値2", "カラムBの値2"], ...] のように二次元配列で渡すため、このように整形しています。

以下で実際に API を実行し、テーブルにデータを挿入します。

        client.insert_rows(table=table, rows=holidays)

Python 環境の準備

ソースコードの解説はここまでです。

ここからは、ローカルで Python 環境を準備する方法を説明します。

必要に応じ以下のように venv 環境を作成し activate します。

python -m venv venv
 
source venv/bin/activate

今回のプログラムで使うパッケージをインストールし requirements.txt を作成します。

pip install google-auth google-api-python-client google-cloud-logging google-cloud-bigquery
 
pip freeze > requirements.txt

なおソースコード中で flask のモジュールを import していますが Cloud Functions の python 実行環境には Flask パッケージが予め含まれているため、明示的に pip install したり requirements.txt に含める必要はありません。

Cloud Functions のデプロイ

以下のコマンドで Cloud Functions をデプロイします。

ソースコードと requirements.txt が存在するディレクトリでコマンド実行してください。またデプロイのパラメータは適宜設定ください。

PROJECT="<プロジェクト ID に置き換え>"
ACCOUNT_NAME="get-holidays"
FUNCTION="get-holidays"
 
gcloud functions deploy ${FUNCTION} \
--quiet --gen2 \
--project=${PROJECT} \
--region=asia-northeast1 \
--runtime=python39 \
--service-account=${ACCOUNT_NAME}@${PROJECT}.iam.gserviceaccount.com \
--entry-point main \
--trigger-http

動作確認

デプロイが成功すると、標準出力にエンドポイント URL が表示されます。Google Cloud コンソールの Cloud Functions 画面から確認することもできますし、以下のコマンドで取得することもできます。

FUNCTION="get-holidays"
URL=`gcloud functions describe ${FUNCTION} --region=asia-northeast1 --gen2 --format="value(serviceConfig.uri)"`
 
echo ${URL}

以下の curl コマンドで function の動作確認をします。 INSERT 先のデータセットとテーブルは予め作成しておき、コマンド内の文字列を置き換えてください。

curl -X POST \
  -H "Authorization: bearer $(gcloud auth print-identity-token)" \
  -H "Content-Type: application/json" \
  -d '{"dataset": "<データセット名に置き換え>", "table": "<テーブル名に置き換え>"}' \
  ${URL}

なお当 function は呼び出し時に IAM 認証を必要とする設定になっていますので Authorization ヘッダを付与しています。 $(gcloud auth print-identity-token) によりローカル環境に設定されている Google アカウント権限でトークンを取得しています。

実行できたら、以下のように BigQuery テーブルにデータが INSERT されたことを確認します。

BigQuery テーブルにデータが挿入された

ローカル環境でのテスト

ローカル環境での Functions のテスト

Cloud Functions のデプロイには 2 分程度の時間がかかります。コード修正後に動作確認したいとき、いちいちデプロイしていたのでは時間がかかりすぎてしまいます。

functions-framework というライブラリを使うことで、ローカル PC 環境で Cloud Functions を動作させ、テストすることができます。

ここからは、その手順をご紹介します。

サービスアカウントのキーのダウンロード

まずローカルの仮想的な Functions から実際に Google Cloud API をコールする際の認証のため、サービスアカウントのキーをダウンロードします。

Google Cloud コンソール > IAM と管理 > サービス アカウント の画面 (リンク) から今回作成した get-holidays サービスアカウントを選択し、詳細画面へ遷移します。

キー というタブから「鍵を追加」を押下して「新しい鍵を作成」を選択します。

JSON 形式で鍵を「作成」し、ダウンロードします。

今回はローカルのソースコードと同じディレクトリに test-cred.json として保存します。

このファイルが漏洩すると、サービスアカウントが持つ権限で好きに Google Cloud 環境を操作できてしまうことになるので、十分お気をつけください。

functions-framework インストール

functions-framework を使うことでローカル環境で HTTP 関数をテストすることができます。

pip でパッケージをインストールします。手順は以下を参考にしてください。

blog.g-gen.co.jp

仮想 Cloud Functions 実行

ソースコードと同じディレクトリで以下を実行してください。

GOOGLE_APPLICATION_CREDENTIALS="./test-cred.json" functions-framework --target main --debug

これでダウンロードしたサービスアカウントキーを実行環境のデフォルト認証情報として設定したうえで仮想的な Cloud Functions を起動できます。仮想的な Functions は 8080/tcp ポートで待ち受けします。

なお本来、ローカルの仮想 function から Google Cloud API を呼ぶだけであれば Google アカウントの権限で一度 gcloud auth application-default login (参考) を実行すればキーのダウンロードや環境変数での指定は不要です。

しかし今回は Google Calendar API の呼び出しがあり、これが上記コマンドによる認証情報の設定 (Google アカウントによるアプリケーションデフォルトクレデンシャル設定) に対応していないため、本来はできれば避けるべきですがキーのダウンロード・指定を行いました。

リクエスト

以下の curl リクエストで実際に動作させることができます。データセット名とテーブル名は実際のものに置き換えてください。

curl localhost:8080 -X POST -H "Content-Type: application/json" -d '{"dataset": "testdataset", "table": "holidays"}'

杉村 勇馬 (記事一覧)

執行役員 CTO / クラウドソリューション部 部長

元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。