[Python]Google Calendar APIのQuickstartのサンプルコードを、分解して解説してみる

## Google Calendar APIって？

- Googleカレンダーをいじれる
- クイックスタートがあるためとっても簡単
- と思ったら英語なので、自分のためも兼ねて解説する

解説記事が過去にありましたが、二年前でコードも変わっていました。

https://blowup-bbs.com/google-calender-api-python-quickstart/

## ソースコード

https://developers.google.com/calendar/api/quickstart/python

https://github.com/googleworkspace/python-samples/blob/master/calendar/quickstart/quickstart.py

Apache License 2.0

## 前提

Python 2.6以降

pipで諸々をダウンロード

```sh
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
```

プロジェクトを作って認証情報をダウンロードしてある

→参考

https://www.y-hakopro.com/entry/google_oauth_api

## まずは動かしてみる

1. ソースコードを[Google公式](https://developers.google.com/calendar/api/quickstart/python)からコピー

1. コードを`quickstart.py`に貼り付けする

1. ダウンロードしてきたJSON形式の認証情報を、`credentials.json`にリネームして、`quickstart.py`と同じディレクトリに入れる

1. 実行！動くはず

## 出力結果

イベントの開始時間と、イベントの名前が出力されます

```
2021-08-03T09:00:00+09:00 バイト
2021-08-04T09:00:00+09:00 バイト
2021-08-10T09:00:00+09:00 バイト
2021-08-11T09:00:00+09:00 バイト
2021-08-12T09:00:00+09:00 バイト
2021-08-17T09:00:00+09:00 バイト
2021-08-18T09:00:00+09:00 バイト
2021-08-23T09:00:00+09:00 バイト
2021-08-24T09:00:00+09:00 バイト
2021-08-25T09:00:00+09:00 バイト
```

悲しきバイト漬け人生

さらに、同じディレクトリ内に`token.json`が出現しました。

この出力結果を思い出しつつ、ソースコードを理解していきましょう。

## ソースコードを分解して理解

重要そうな部分は:star:マークをつけています。

### ライブラリのインポート

#### print_function

```py
from __future__ import print_function
```

Python2と3の互換性を維持するためのライブラリです。
2系でも3系のコードが動くようになります。

#### datetime


```py
import datetime
```

Pythonで日付や時刻を扱うためのライブラリです。

#### os.path

```py
import os.path
```

パス名を操作するためのモジュール

#### :star:Googleのモジュール達

```py
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
```
pipでインストールしたら動くはず

[googleapiclient.discoveryのリファレンス](https://googleapis.github.io/google-api-python-client/docs/epy/googleapiclient.discovery-module.html)
[InstalledAppFlowのリファレンス](https://google-auth-oauthlib.readthedocs.io/en/latest/reference/google_auth_oauthlib.flow.html#google_auth_oauthlib.flow.InstalledAppFlow)
[Credentialsのリファレンス](https://google-auth.readthedocs.io/en/stable/reference/google.oauth2.credentials.html)

### スコープ

#### :star:スコープ

```py
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
```

スコープとは直訳すると範囲のことです。
許可して欲しい範囲を選択します。
API毎に指定されているため、用途によって変更するのが良いでしょう。

コメントによると変更した場合は`token.json`を削除する必要があります。

### 認証プロセス

#### cred

```py
    creds = None
```

`cred`はcredentials（資格情報）の略です。

この後の条件分岐によってここに情報が入ります。

#### 一度通信したことがある場合

```py
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
```

os.path.exists()は存在するかどうかを確かめる関数です。
`token.json`がある = 以前通信したことがある場合になります

その場合、`creds`には`token.json`から生成された`Credentials`インスタンスが入ります。

#### 有効でない認証情報だった場合

```py
    if not creds or not creds.valid:
```

`creds`がまだ`None` = 以前通信したことがない、もしくは`creds.valid`が無効 = 期限切れ等の場合この条件に入ります。

#### 期限切れの場合

```py
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
```

さらに、無効の理由が期限切れで、リフレッシュトークンがある場合は、トークンを更新します。

#### :star:それ以外の場合

```py
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
```

多くの場合、ここに来るのは初めての通信でしょう。
あるいはリフレッシュトークンの有効期限も切れるほど古い場合です。

`flow`には、資格情報ファイルの`credentials.json`から作られた`Flow`インスタンスが入ります。
`Flow`インスタンスには、ブラウザを開いて認証画面を通させる`run_local_server`メゾットがあります。

つまり、アクセストークンがないから認証を新しく行わせているということです。

#### 次回以降のためにトークンを保存

```py
        with open('token.json', 'w') as token:
            token.write(creds.to_json())
```

次回以降のために、`token.json`を保存します。

最初に実行した際、新しく`token.json`ができたのはこれが理由なんです。

### APIを利用

#### :star:APIを利用する準備

```py
    service = build('calendar', 'v3', credentials=creds)
```

APIを使用するための`Resorce`インスタンスがserviceに入ります。
ここでは、バージョン3を指定してcalendarAPIを使用します。

[Resorceクラスのリファレンス](https://googleapis.github.io/google-api-python-client/docs/epy/googleapiclient.discovery.Resource-class.html)

#### 時刻を設定

```py
    now = datetime.datetime.utcnow().isoformat() + 'Z'
```

`datetime.datetime.utcnow().isoformat()`まででUTCの時刻が`YYYY-MM-DDTHH:MM:SS.ffffff`の文字列として生成されます。
`'z'`はUTCを表します。

#### :star:カレンダーを10個取得

```py
    events_result = service.events().list(calendarId='primary', timeMin=now,
                                        maxResults=10, singleEvents=True,
                                        orderBy='startTime').execute()
```

serviceインスタンスを用いてイベントを取得します
これに関しては、公式リファレンスとサンプルを見るのがわかりやすいです。

https://developers.google.com/calendar/api/v3/reference/events/list

今回は`events` = 予定の取得でしたが、例えば`calendars` = 予定の集まりを消去などは`service.calendars().delete`となります。

#### :star:イベントの詳細を取得

```py
    events = events_result.get('items', [])
```

`events_result`には、要求した内容の他に種類の識別子や時間などが入っています。

そこから内容である`'item'`キーの値を取り出します。

内容はリファレンスに書いてあります

https://developers.google.com/calendar/api/v3/reference/events#resource-representations

#### itemがなかった場合

```py
    if not events:
        print('No upcoming events found.')
```

[イベントの詳細を取得](#イベントの詳細を取得)で、`item`がなかった場合は`[]`が入ります。
その場合の処理です。

#### itemを取得できた場合

```py
    for event in events:
        start = event['start'].get('dateTime', event['start'].get('date'))
        print(start, event['summary'])
```

まず、`start`に始まりの時間を入れます。

帰ってきた`dict`の中の`start`に、`dateTime`キーの値がある場合はそれを、ない場合(おそらく終日予定の場合)は`date`を取得します。

その後、出力しています。

#### main文を実行

```py
if __name__ == '__main__':
    main()
```

解説するまでもないことかもしれませんが、ここまできたので書きましょう
`main`関数を実行しています。

## 終わりに

編集リクエスト、コメントぜひお願いします。

公式リファレンスがやはり一番役に立ちますね。

## 参考文献

https://qiita.com/ping2shi2/items/6cf5ba9f148898b539bc

https://developers.google.com/calendar

https://googleapis.github.io/google-api-python-client/docs/epy/googleapiclient-module.html

https://google-auth-oauthlib.readthedocs.io/en/latest/index.html

https://blowup-bbs.com/google-calender-api-python-quickstart/